├── .gitignore ├── README.md ├── addin-project.xml ├── VimAddin ├── Properties │ ├── AddinInfo.cs │ ├── Manifest.addin.xml │ └── AssemblyInfo.cs ├── VimAddinOptionsPanelWidget.cs ├── gtk-gui │ ├── gui.stetic │ ├── VimAddin.VimAddinOptionsPanelWidget.cs │ └── generated.cs ├── Mono.TextEditor.Vi │ ├── ViModeAbortException.cs │ ├── ViEditorActions.cs │ ├── ViMacro.cs │ ├── ViCommandMap.cs │ ├── ViMark.cs │ ├── NewViEditMode.cs │ ├── ViStatusArea.cs │ ├── ViBuilders.cs │ ├── ViWordFindStrategy.cs │ ├── ViActionMaps.cs │ ├── ViEditor.cs │ ├── ViBuilderContext.cs │ ├── ViKeyNotation.cs │ ├── ViActions.cs │ └── ViMode.cs ├── VimTextEditorExtension.cs ├── IdeViMode.cs └── VimAddin.csproj └── VimAddin.sln /.gitignore: -------------------------------------------------------------------------------- 1 | *.userprefs 2 | VimAddin/bin 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VimAddin 2 | ======== 3 | 4 | Fork of the vi modes core addin in MonoDevelop. 5 | 6 | See the project page and [wiki](https://github.com/alextsui05/VimAddin/wiki) for the latest issues and updates on the development of this addin's features. 7 | -------------------------------------------------------------------------------- /addin-project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VimAddin/bin/Release/VimAddin.dll 4 | VimAddin.sln 5 | Release 6 | 7 | 8 | -------------------------------------------------------------------------------- /VimAddin/Properties/AddinInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Mono.Addins; 3 | using Mono.Addins.Description; 4 | 5 | [assembly:Addin ( 6 | "VimAddin", 7 | Namespace = "VimAddin", 8 | Version = "1.1.11" 9 | )] 10 | 11 | [assembly:AddinName ("VimAddin")] 12 | [assembly:AddinCategory ("IDE extensions")] 13 | [assembly:AddinDescription ("A fork of MonoDevelop vi modes. See https://github.com/alextsui05/VimAddin")] 14 | [assembly:AddinAuthor ("atsui")] 15 | 16 | [assembly:AddinDependency ("::MonoDevelop.Core", MonoDevelop.BuildInfo.Version)] 17 | [assembly:AddinDependency ("::MonoDevelop.Ide", MonoDevelop.BuildInfo.Version)] 18 | -------------------------------------------------------------------------------- /VimAddin.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VimAddin", "VimAddin\VimAddin.csproj", "{6193DC45-BC2C-422C-A9B2-233E961D83FE}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {6193DC45-BC2C-422C-A9B2-233E961D83FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {6193DC45-BC2C-422C-A9B2-233E961D83FE}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {6193DC45-BC2C-422C-A9B2-233E961D83FE}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {6193DC45-BC2C-422C-A9B2-233E961D83FE}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(MonoDevelopProperties) = preSolution 18 | StartupItem = VimAddin\VimAddin.csproj 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /VimAddin/Properties/Manifest.addin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /VimAddin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle ("VimAddin")] 8 | [assembly: AssemblyDescription ("")] 9 | [assembly: AssemblyConfiguration ("")] 10 | [assembly: AssemblyCompany ("")] 11 | [assembly: AssemblyProduct ("")] 12 | [assembly: AssemblyCopyright ("atsui")] 13 | [assembly: AssemblyTrademark ("")] 14 | [assembly: AssemblyCulture ("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion ("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /VimAddin/VimAddinOptionsPanelWidget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using System.Diagnostics; 6 | 7 | using MonoDevelop.Ide.Gui.Dialogs; // for OptionsPanel 8 | using MonoDevelop.Core; // for PropertyService 9 | 10 | using Mono.Addins; 11 | 12 | namespace VimAddin 13 | { 14 | [System.ComponentModel.ToolboxItem (true)] 15 | public partial class VimAddinOptionsPanelWidget : Gtk.Bin 16 | { 17 | public VimAddinOptionsPanelWidget () 18 | { 19 | this.Build (); 20 | Assembly assembly = Assembly.GetExecutingAssembly (); 21 | AddinAttribute addinAttr = assembly.GetCustomAttribute (); 22 | string labelText = String.Format ("VimAddin version {0}", 23 | addinAttr.Version); 24 | this.label1.Text = labelText; 25 | this.checkbutton1.Active = (bool)PropertyService.Get ("UseViModes", false); 26 | } 27 | 28 | public void Store() 29 | { 30 | PropertyService.Set ("UseViModes", this.checkbutton1.Active); 31 | } 32 | } 33 | 34 | public class VimAddinOptionsPanel : OptionsPanel 35 | { 36 | VimAddinOptionsPanelWidget w; 37 | public override Gtk.Widget CreatePanelWidget() 38 | { 39 | w = new VimAddinOptionsPanelWidget (); 40 | return w; 41 | } 42 | 43 | public override void ApplyChanges() 44 | { 45 | w.Store (); 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /VimAddin/gtk-gui/gui.stetic: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | False 13 | 14 | 15 | 16 | False 17 | 18 | 19 | 20 | True 21 | Use vi modes 22 | True 23 | True 24 | True 25 | True 26 | 27 | 28 | 14 29 | 41 30 | 31 | 32 | 33 | 34 | 35 | label1 36 | 37 | 38 | 13 39 | 10 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /VimAddin/gtk-gui/VimAddin.VimAddinOptionsPanelWidget.cs: -------------------------------------------------------------------------------- 1 | 2 | // This file has been generated by the GUI designer. Do not modify. 3 | namespace VimAddin 4 | { 5 | public partial class VimAddinOptionsPanelWidget 6 | { 7 | private global::Gtk.Fixed fixed1; 8 | 9 | private global::Gtk.CheckButton checkbutton1; 10 | 11 | private global::Gtk.Label label1; 12 | 13 | protected virtual void Build () 14 | { 15 | global::Stetic.Gui.Initialize (this); 16 | // Widget VimAddin.VimAddinOptionsPanelWidget 17 | global::Stetic.BinContainer.Attach (this); 18 | this.Name = "VimAddin.VimAddinOptionsPanelWidget"; 19 | // Container child VimAddin.VimAddinOptionsPanelWidget.Gtk.Container+ContainerChild 20 | this.fixed1 = new global::Gtk.Fixed (); 21 | this.fixed1.Name = "fixed1"; 22 | this.fixed1.HasWindow = false; 23 | // Container child fixed1.Gtk.Fixed+FixedChild 24 | this.checkbutton1 = new global::Gtk.CheckButton (); 25 | this.checkbutton1.CanFocus = true; 26 | this.checkbutton1.Name = "checkbutton1"; 27 | this.checkbutton1.Label = global::Mono.Unix.Catalog.GetString ("Use vi modes"); 28 | this.checkbutton1.Active = true; 29 | this.checkbutton1.DrawIndicator = true; 30 | this.checkbutton1.UseUnderline = true; 31 | this.fixed1.Add (this.checkbutton1); 32 | global::Gtk.Fixed.FixedChild w1 = ((global::Gtk.Fixed.FixedChild)(this.fixed1 [this.checkbutton1])); 33 | w1.X = 14; 34 | w1.Y = 41; 35 | // Container child fixed1.Gtk.Fixed+FixedChild 36 | this.label1 = new global::Gtk.Label (); 37 | this.label1.Name = "label1"; 38 | this.label1.LabelProp = global::Mono.Unix.Catalog.GetString ("label1"); 39 | this.fixed1.Add (this.label1); 40 | global::Gtk.Fixed.FixedChild w2 = ((global::Gtk.Fixed.FixedChild)(this.fixed1 [this.label1])); 41 | w2.X = 13; 42 | w2.Y = 10; 43 | this.Add (this.fixed1); 44 | if ((this.Child != null)) { 45 | this.Child.ShowAll (); 46 | } 47 | this.Hide (); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViModeAbortException.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViModeAbortException.cs 3 | // 4 | // Author: 5 | // Tim Kellogg 6 | // 7 | // Copyright (c) 2013 Tim Kellogg 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | using System; 27 | 28 | //namespace Mono.TextEditor.Vi 29 | namespace VimAddin 30 | { 31 | /// 32 | /// Thrown to indicate that the vi parser was unable to recognize the key 33 | /// sequence and should abort quietly. 34 | /// 35 | public class ViModeAbortException : Exception 36 | { 37 | /// 38 | /// Thrown to indicate that the vi parser was unable to recognize the key 39 | /// sequence and should abort quietly. 40 | /// 41 | public ViModeAbortException () 42 | { 43 | } 44 | 45 | /// 46 | /// Thrown to indicate that the vi parser was unable to recognize the key 47 | /// sequence and should abort quietly. 48 | /// 49 | /// The reason for the abort. May be displayed to the user 50 | public ViModeAbortException (string reason) : base(reason) 51 | { 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /VimAddin/gtk-gui/generated.cs: -------------------------------------------------------------------------------- 1 | 2 | // This file has been generated by the GUI designer. Do not modify. 3 | namespace Stetic 4 | { 5 | internal class Gui 6 | { 7 | private static bool initialized; 8 | 9 | internal static void Initialize (Gtk.Widget iconRenderer) 10 | { 11 | if ((Stetic.Gui.initialized == false)) { 12 | Stetic.Gui.initialized = true; 13 | } 14 | } 15 | } 16 | 17 | internal class BinContainer 18 | { 19 | private Gtk.Widget child; 20 | 21 | private Gtk.UIManager uimanager; 22 | 23 | public static BinContainer Attach (Gtk.Bin bin) 24 | { 25 | BinContainer bc = new BinContainer (); 26 | bin.SizeRequested += new Gtk.SizeRequestedHandler (bc.OnSizeRequested); 27 | bin.SizeAllocated += new Gtk.SizeAllocatedHandler (bc.OnSizeAllocated); 28 | bin.Added += new Gtk.AddedHandler (bc.OnAdded); 29 | return bc; 30 | } 31 | 32 | private void OnSizeRequested (object sender, Gtk.SizeRequestedArgs args) 33 | { 34 | if ((this.child != null)) { 35 | args.Requisition = this.child.SizeRequest (); 36 | } 37 | } 38 | 39 | private void OnSizeAllocated (object sender, Gtk.SizeAllocatedArgs args) 40 | { 41 | if ((this.child != null)) { 42 | this.child.Allocation = args.Allocation; 43 | } 44 | } 45 | 46 | private void OnAdded (object sender, Gtk.AddedArgs args) 47 | { 48 | this.child = args.Widget; 49 | } 50 | 51 | public void SetUiManager (Gtk.UIManager uim) 52 | { 53 | this.uimanager = uim; 54 | this.child.Realized += new System.EventHandler (this.OnRealized); 55 | } 56 | 57 | private void OnRealized (object sender, System.EventArgs args) 58 | { 59 | if ((this.uimanager != null)) { 60 | Gtk.Widget w; 61 | w = this.child.Toplevel; 62 | if (((w != null) && typeof(Gtk.Window).IsInstanceOfType (w))) { 63 | ((Gtk.Window)(w)).AddAccelGroup (this.uimanager.AccelGroup); 64 | this.uimanager = null; 65 | } 66 | } 67 | } 68 | } 69 | 70 | internal class ActionGroups 71 | { 72 | public static Gtk.ActionGroup GetActionGroup (System.Type type) 73 | { 74 | return Stetic.ActionGroups.GetActionGroup (type.FullName); 75 | } 76 | 77 | public static Gtk.ActionGroup GetActionGroup (string name) 78 | { 79 | return null; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViEditorActions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViEditorActions.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (c) 2010 Novell, Inc. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using Mono.TextEditor; 29 | 30 | //namespace Mono.TextEditor.Vi 31 | namespace VimAddin 32 | { 33 | public class ViEditorActions 34 | { 35 | public static void CaretToScreenCenter (ViEditor ed) 36 | { 37 | var line = ed.Editor.PointToLocation (0, ed.Editor.Allocation.Height/2).Line; 38 | if (line < 0) 39 | line = ed.Data.Document.LineCount; 40 | ed.Data.Caret.Line = line; 41 | } 42 | 43 | public static void CaretToScreenBottom (ViEditor ed) 44 | { 45 | int line = ed.Editor.PointToLocation (0, ed.Editor.Allocation.Height - ed.Editor.LineHeight * 2 - 2).Line; 46 | if (line < 0) 47 | line = ed.Data.Document.LineCount; 48 | ed.Data.Caret.Line = line; 49 | } 50 | 51 | public static void CaretToScreenTop (ViEditor ed) 52 | { 53 | ed.Data.Caret.Line = System.Math.Max (0, ed.Editor.PointToLocation (0, ed.Editor.LineHeight - 1).Line); 54 | } 55 | 56 | public static void CaretToLineNumber (int lineNumber, ViEditor ed) 57 | { 58 | ed.Data.Caret.Line = System.Math.Max (1, lineNumber); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViMacro.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViMacro.cs 3 | // 4 | // Author: 5 | // Sanjoy Das 6 | // 7 | // Copyright (c) 2010 Sanjoy Das 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using System.Collections.Generic; 29 | 30 | //namespace Mono.TextEditor.Vi 31 | namespace VimAddin 32 | { 33 | /// 34 | /// Implements a Vi macro. Only the keys pressed need to be stored. Though this class 35 | /// is not exactly required, it provides a possible place to extend the code. 36 | /// 37 | public class ViMacro { 38 | 39 | /// 40 | /// One of these determines a complete set of arguments passed to HandleKeyPress. I am 41 | /// still not sure about whether each of these fields is indepedent in itself or may 42 | /// be eliminated and re-constructed later. 43 | /// 44 | public struct KeySet { 45 | public Gdk.Key Key { get; set;} 46 | public uint UnicodeKey {get; set;} 47 | public Gdk.ModifierType Modifiers {get; set;} 48 | } 49 | 50 | /// 51 | /// This of KeySets determine the ultimate functionality 52 | /// of the macro this ViMacro object represents. 53 | /// 54 | public Queue KeysPressed {get; set;} 55 | public char MacroCharacter {get; set;} 56 | 57 | public ViMacro (char macroCharacter) { 58 | MacroCharacter = MacroCharacter; 59 | } 60 | 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViCommandMap.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViCommandMap.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (c) 2010 Novell, Inc. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using System.Collections.Generic; 29 | using Mono.TextEditor; 30 | 31 | //namespace Mono.TextEditor.Vi 32 | namespace VimAddin 33 | { 34 | class ViCommandMap : IEnumerable>> 35 | { 36 | Dictionary builders = new Dictionary (); 37 | Dictionary> actions = new Dictionary> (); 38 | 39 | public bool Builder (ViBuilderContext ctx) 40 | { 41 | Action a; 42 | if (actions.TryGetValue (ctx.LastKey, out a)) { 43 | ctx.RunAction (a); 44 | return true; 45 | } else { 46 | BuilderAction b; 47 | if (builders.TryGetValue (ctx.LastKey, out b)) { 48 | ctx.Builder = b.Builder; 49 | if (b.RunInstantly) 50 | ctx.Builder (ctx); 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | public void Add (ViKey key, ViCommandMap map) 58 | { 59 | Add (key, map.Builder); 60 | } 61 | 62 | public void Add (ViKey key, Action action) 63 | { 64 | this.actions[key] = action; 65 | } 66 | 67 | public void Add (ViKey key, Action action) 68 | { 69 | this.actions[key] = (ViEditor ed) => action (ed.Data); 70 | } 71 | 72 | public void Add (ViKey key, ViBuilder builder) 73 | { 74 | Add (key, builder, false); 75 | } 76 | 77 | public void Add (ViKey key, ViBuilder builder, bool runInstantly) 78 | { 79 | this.builders[key] = new BuilderAction (builder, runInstantly); 80 | } 81 | 82 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () 83 | { 84 | return builders.GetEnumerator (); 85 | } 86 | 87 | public IEnumerator>> GetEnumerator () 88 | { 89 | return actions.GetEnumerator (); 90 | } 91 | 92 | struct BuilderAction 93 | { 94 | public BuilderAction (ViBuilder builder, bool runInstantly) 95 | { 96 | this.Builder = builder; 97 | this.RunInstantly = runInstantly; 98 | } 99 | 100 | public readonly ViBuilder Builder; 101 | public readonly bool RunInstantly; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViMark.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViMark.cs 3 | // 4 | // Author: 5 | // Sanjoy Das 6 | // 7 | // Copyright (c) 2010 Sanjoy Das 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using Mono.TextEditor; 29 | using Mono.TextEditor.Highlighting; 30 | 31 | //namespace Mono.TextEditor.Vi 32 | namespace VimAddin 33 | { 34 | public class ViMark : Mono.TextEditor.TextLineMarker 35 | { 36 | 37 | public char MarkCharacter {get; set;} 38 | public bool Initialized { get; set; } 39 | 40 | /// 41 | /// Only way to construct a ViMark. 42 | /// 43 | /// 44 | /// The with which the ViMark object needs to be 45 | /// associated. 46 | /// 47 | public ViMark (char markCharacter, bool initialized = true) { 48 | MarkCharacter = markCharacter; 49 | Initialized = initialized; 50 | } 51 | 52 | public int ColumnNumber {get; protected set;} 53 | 54 | public void SaveMark (TextEditorData data) { 55 | if (base.LineSegment != null) { 56 | // Remove the marker first 57 | data.Document.RemoveMarker (this); 58 | } 59 | 60 | // Is there a better way of doing this? 61 | int lineNumber = 1; 62 | if (!Initialized) { 63 | Initialized = true; 64 | } else { 65 | lineNumber = data.IsSomethingSelected ? data.MainSelection.MinLine : data.Caret.Line; 66 | } 67 | base.LineSegment = data.Document.GetLine (lineNumber); 68 | ColumnNumber = data.Caret.Column; 69 | data.Document.AddMarker(lineNumber, this); 70 | 71 | data.Document.RequestUpdate (new UpdateAll ()); 72 | data.Document.CommitDocumentUpdate (); 73 | } 74 | 75 | public void LoadMark (TextEditorData data) { 76 | // remember where the caret currently is 77 | int lineNumber = data.Caret.Line; 78 | int colNumber = data.Caret.Column; 79 | 80 | // get the line number stored with this mark 81 | int x = (base.LineSegment == null)? 1 : base.LineSegment.LineNumber; 82 | 83 | // reposition the caret on the stored line 84 | data.Caret.Line = x; 85 | int len = base.LineSegment.LengthIncludingDelimiter; 86 | if (ColumnNumber >= len) { 87 | // Check if the line has been truncated after the setting the mark 88 | data.Caret.Column = len - 1; 89 | } else { 90 | data.Caret.Column = ColumnNumber; 91 | } 92 | 93 | if (MarkCharacter == '`') { 94 | data.Document.RemoveMarker (this); 95 | base.LineSegment = data.Document.GetLine(lineNumber); 96 | ColumnNumber = colNumber; 97 | Console.WriteLine ("Line before jump: {0}", lineNumber); 98 | data.Document.AddMarker (lineNumber, this); 99 | data.Document.RequestUpdate (new UpdateAll ()); 100 | data.Document.CommitDocumentUpdate (); 101 | } 102 | } 103 | 104 | public override ChunkStyle GetStyle (ChunkStyle baseStyle) { 105 | return baseStyle; 106 | } 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/NewViEditMode.cs: -------------------------------------------------------------------------------- 1 | // 2 | // NewViEditMode.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (c) 2013 Xamarin Inc. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using System.Text; 29 | using System.Text.RegularExpressions; 30 | using System.Collections.Generic; 31 | using System.Linq; 32 | using Mono.TextEditor; 33 | 34 | //namespace Mono.TextEditor.Vi 35 | namespace VimAddin 36 | { 37 | public class NewViEditMode : Mono.TextEditor.EditMode 38 | { 39 | ViStatusArea statusArea; 40 | TextEditor viTextEditor; 41 | 42 | protected ViEditor ViEditor { get ; private set ;} 43 | 44 | public NewViEditMode () 45 | { 46 | ViEditor = new ViEditor (this); 47 | ViEditor.ModeChanged += (sender, e) => { 48 | if (statusArea != null) 49 | statusArea.ShowCaret = ViEditor.Mode == ViEditorMode.Command; 50 | }; 51 | ViEditor.MessageChanged += (sender, e) => { 52 | if (statusArea != null) 53 | statusArea.Message = ViEditor.Message; 54 | }; 55 | } 56 | 57 | protected override void OnAddedToEditor (TextEditorData data) 58 | { 59 | ViEditor.SetMode (ViEditorMode.Normal); 60 | SetCaretMode (CaretMode.Block, data); 61 | ViActions.RetreatFromLineEnd (data); 62 | 63 | viTextEditor = data.Parent; 64 | if (viTextEditor != null) { 65 | statusArea = new ViStatusArea (viTextEditor); 66 | } 67 | } 68 | 69 | protected override void OnRemovedFromEditor (TextEditorData data) 70 | { 71 | SetCaretMode (CaretMode.Insert, data); 72 | 73 | if (viTextEditor != null) { 74 | statusArea.RemoveFromParentAndDestroy (); 75 | statusArea = null; 76 | viTextEditor = null; 77 | } 78 | } 79 | 80 | public override void AllocateTextArea (TextEditor textEditor, TextArea textArea, Gdk.Rectangle allocation) 81 | { 82 | statusArea.AllocateArea (textArea, allocation); 83 | } 84 | 85 | protected override void HandleKeypress (Gdk.Key key, uint unicodeKey, Gdk.ModifierType modifier) 86 | { 87 | ViEditor.ProcessKey (modifier, key, (char)unicodeKey); 88 | } 89 | 90 | public new TextEditor Editor { get { return base.Editor; } } 91 | public new TextEditorData Data { get { return base.Data; } } 92 | 93 | public override bool WantsToPreemptIM { 94 | get { 95 | switch (ViEditor.Mode) { 96 | case ViEditorMode.Insert: 97 | case ViEditorMode.Replace: 98 | return false; 99 | case ViEditorMode.Normal: 100 | case ViEditorMode.Visual: 101 | case ViEditorMode.VisualLine: 102 | default: 103 | return true; 104 | } 105 | } 106 | } 107 | 108 | protected override void CaretPositionChanged () 109 | { 110 | ViEditor.OnCaretPositionChanged (); 111 | } 112 | 113 | public void SetCaretMode (CaretMode mode) 114 | { 115 | SetCaretMode (mode, Data); 116 | } 117 | 118 | static void SetCaretMode (CaretMode mode, TextEditorData data) 119 | { 120 | if (data.Caret.Mode == mode) 121 | return; 122 | data.Caret.Mode = mode; 123 | data.Document.RequestUpdate (new SinglePositionUpdate (data.Caret.Line, data.Caret.Column)); 124 | data.Document.CommitDocumentUpdate (); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /VimAddin/VimTextEditorExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MonoDevelop.Ide.Gui.Content; 3 | using MonoDevelop.SourceEditor; 4 | using MonoDevelop.Core; 5 | using MonoDevelop.Ide; 6 | using MonoDevelop.Components.Commands; 7 | using Mono.TextEditor; 8 | 9 | namespace VimAddin 10 | { 11 | public enum VimAddinCommands 12 | { 13 | PageDown, 14 | PageUp 15 | } 16 | 17 | public class VimTextEditorExtension : TextEditorExtension 18 | { 19 | bool isEnabled; 20 | 21 | public bool IsEnabled { 22 | get { return isEnabled; } 23 | } 24 | 25 | public VimTextEditorExtension () 26 | { 27 | PropertyService.PropertyChanged += UpdatePreferences; 28 | isEnabled = PropertyService.Get("UseViModes", false); 29 | } 30 | 31 | public void UpdatePreferences( object sender, PropertyChangedEventArgs args ) 32 | { 33 | isEnabled = PropertyService.Get("UseViModes", false); 34 | SetupMode (); 35 | } 36 | 37 | public void SetupMode( ) 38 | { 39 | var doc = this.Document; 40 | var sourceEditorView = doc.GetContent (); 41 | var textEditor = sourceEditorView.TextEditor; 42 | if (IsEnabled) { 43 | textEditor.CurrentMode = new VimAddin.IdeViMode (textEditor, doc); 44 | IdeApp.Workbench.StatusBar.ShowMessage ("VimAddin activated"); 45 | } else { 46 | //Console.WriteLine ("Currently VimAddin is disabled"); 47 | IdeApp.Workbench.StatusBar.ShowMessage ("VimAddin deactivated"); 48 | } 49 | } 50 | 51 | public override void Initialize( ) 52 | { 53 | base.Initialize (); 54 | Console.WriteLine ("VimAddin.VimTextEditorExtension.Initialize"); 55 | SetupMode (); 56 | } 57 | 58 | public override void Dispose( ) 59 | { 60 | PropertyService.PropertyChanged -= UpdatePreferences; 61 | base.Dispose (); 62 | } 63 | } 64 | 65 | public class VimAddinCommandHandler : CommandHandler 66 | { 67 | protected bool VimAddinIsEnabled() 68 | { 69 | MonoDevelop.Ide.Gui.Document doc = IdeApp.Workbench.ActiveDocument; 70 | if (doc != null && doc.GetContent () != null) 71 | { 72 | var editorView = doc.GetContent (); 73 | var textEditor = editorView.TextEditor; 74 | return textEditor.CurrentMode.GetType () == typeof(VimAddin.IdeViMode); 75 | } 76 | return false; 77 | } 78 | 79 | protected override void Update (CommandInfo info) 80 | { 81 | info.Enabled = VimAddinIsEnabled (); 82 | } 83 | } 84 | 85 | public class PageUpHandler : VimAddinCommandHandler 86 | { 87 | protected override void Run() 88 | { 89 | MonoDevelop.Ide.Gui.Document doc = IdeApp.Workbench.ActiveDocument; 90 | var textEditorData = doc.GetContent ().GetTextEditorData (); 91 | var vimEditor = (VimAddin.IdeViMode) textEditorData.CurrentMode; 92 | vimEditor.InternalHandleKeypress (null, textEditorData, 93 | Gdk.Key.u, (uint)'u', Gdk.ModifierType.ControlMask); 94 | } 95 | } 96 | 97 | public class PageDownHandler : VimAddinCommandHandler 98 | { 99 | protected override void Run() 100 | { 101 | MonoDevelop.Ide.Gui.Document doc = IdeApp.Workbench.ActiveDocument; 102 | // 103 | var textEditorData = doc.GetContent ().GetTextEditorData (); 104 | // var editorView = doc.GetContent (); 105 | //// if (textEditorData.GetType () == typeof(TextEditorData)) { 106 | //// Console.WriteLine ("textEditorData is the expected type"); 107 | //// } 108 | // 109 | // var textEditor = editorView.TextEditor; 110 | //// textEditor.CurrentMode.InternalHandleKeypress (textEditor, textEditorData, 111 | //// Gdk.Key.f, 112 | //// (char)'f', 113 | //// Gdk.ModifierType.ControlMask); 114 | var vimEditor = (VimAddin.IdeViMode) textEditorData.CurrentMode; 115 | // if (vimEditor.HasTextEditorData ()) { 116 | // Console.WriteLine ("Has textEditorData"); 117 | // } else { 118 | // Console.WriteLine ("Has no textEditorData"); 119 | // } 120 | // if (vimEditor.HasData ()) { 121 | // vimEditor.SendKeys (Gdk.Key.f, 'f', Gdk.ModifierType.ControlMask); 122 | // } else { 123 | // Console.WriteLine ("Data is null"); 124 | // Console.WriteLine(vimEditor.ToString ()); 125 | // } 126 | // if (textEditorData == null) { 127 | // Console.WriteLine ("textEditorData is null"); 128 | // } else { 129 | // Console.WriteLine ("textEditorData is not null"); 130 | // } 131 | vimEditor.InternalHandleKeypress (null, textEditorData, Gdk.Key.d, (uint)'d', Gdk.ModifierType.ControlMask); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /VimAddin/IdeViMode.cs: -------------------------------------------------------------------------------- 1 | // 2 | // IdeViMode.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (C) 2008 Novell, Inc (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | using System; 30 | using System.Text.RegularExpressions; 31 | using Mono.TextEditor; 32 | using MonoDevelop.Ide.Gui; 33 | using MonoDevelop.Ide; 34 | using MonoDevelop.SourceEditor; 35 | 36 | //namespace MonoDevelop.SourceEditor 37 | namespace VimAddin 38 | { 39 | public class NewIdeViMode : VimAddin.NewViEditMode 40 | { 41 | public NewIdeViMode (ExtensibleTextEditor editor) 42 | { 43 | this.editor = editor; 44 | } 45 | 46 | protected override void HandleKeypress (Gdk.Key key, uint unicodeKey, Gdk.ModifierType modifier) 47 | { 48 | base.HandleKeypress (key, unicodeKey, modifier); 49 | IdeApp.Workbench.StatusBar.ShowMessage (ViEditor.Message); 50 | } 51 | } 52 | 53 | public class IdeViMode : VimAddin.ViEditMode 54 | { 55 | new ExtensibleTextEditor editor; 56 | TabAction tabAction; 57 | 58 | public IdeViMode (ExtensibleTextEditor editor, MonoDevelop.Ide.Gui.Document doc) : 59 | base (doc) 60 | { 61 | this.editor = editor; 62 | tabAction = new TabAction (editor); 63 | } 64 | 65 | protected override void OnAddedToEditor (TextEditorData data) 66 | { 67 | base.OnAddedToEditor (data); 68 | 69 | ViMark special1 = new ViMark ('`', false); 70 | marks ['`'] = special1; 71 | special1.SaveMark (data); 72 | } 73 | 74 | protected override Action GetInsertAction (Gdk.Key key, Gdk.ModifierType modifier) 75 | { 76 | if (modifier == Gdk.ModifierType.None) { 77 | switch (key) { 78 | case Gdk.Key.BackSpace: 79 | return EditActions.AdvancedBackspace; 80 | case Gdk.Key.Tab: 81 | return tabAction.Action; 82 | } 83 | } 84 | return base.GetInsertAction (key, modifier); 85 | } 86 | 87 | protected override string RunExCommand (string command) 88 | { 89 | if (':' != command[0] || 2 > command.Length) 90 | return base.RunExCommand (command); 91 | base.CurState = State.Normal; 92 | 93 | switch (command[1]) { 94 | case 'w': 95 | if (2 < command.Length) { 96 | switch (command[2]) { 97 | case 'q': // :wq 98 | saveableDocument.Save (); 99 | saveableDocument.Close (); 100 | return "Saved and closed file."; 101 | case '!': // :w! 102 | saveableDocument.Save (); 103 | break; 104 | default: 105 | return base.RunExCommand (command); 106 | } 107 | } 108 | else saveableDocument.Save (); 109 | return "Saved file."; 110 | 111 | case 'q': 112 | bool force = false; 113 | if (2 < command.Length) { 114 | switch (command [2]) { 115 | case '!': // :q! 116 | force = true; 117 | break; 118 | default: 119 | return base.RunExCommand (command); 120 | } 121 | } 122 | 123 | saveableDocument.Close (); 124 | return force? "Closed file without saving.": "Closed file."; 125 | 126 | 127 | case 'm': 128 | if (!Regex.IsMatch (command, "^:mak[e!]", RegexOptions.Compiled)) 129 | break; 130 | //MonoDevelop.Projects.Project proj = editor.View.Project; 131 | MonoDevelop.Projects.Project proj = editor.Project; 132 | if (proj != null) { 133 | IdeApp.ProjectOperations.Build (proj); 134 | return string.Format ("Building project {0}", proj.Name); 135 | } 136 | return "File is not part of a project"; 137 | case 'c': 138 | // Error manipulation 139 | if (3 == command.Length) { 140 | switch (command[2]) { 141 | case 'n': 142 | // :cn - jump to next error 143 | IdeApp.CommandService.DispatchCommand (MonoDevelop.Ide.Commands.ViewCommands.ShowNext); 144 | return string.Empty; 145 | case 'N': 146 | case 'p': 147 | // :c[pN] - jump to previous error 148 | IdeApp.CommandService.DispatchCommand (MonoDevelop.Ide.Commands.ViewCommands.ShowPrevious); 149 | return string.Empty; 150 | } 151 | } 152 | break; 153 | } 154 | return base.RunExCommand (command); 155 | } 156 | 157 | protected override void HandleKeypress (Gdk.Key key, uint unicodeKey, Gdk.ModifierType modifier) 158 | { 159 | if (0 != (Gdk.ModifierType.ControlMask & modifier)) { 160 | switch (key) { 161 | case Gdk.Key.bracketright: 162 | // ctrl-] => Go to declaration 163 | // HACK: since the SourceEditor can't link the Refactoring addin the command is provided as string. 164 | IdeApp.CommandService.DispatchCommand ("MonoDevelop.Refactoring.RefactoryCommands.GotoDeclaration"); 165 | return; 166 | } 167 | }// ctrl+key 168 | 169 | base.HandleKeypress (key, unicodeKey, modifier); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViStatusArea.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViStatusArea.cs 3 | // 4 | // Author: 5 | // Mike Krüger 6 | // Michael Hutchinson 7 | // 8 | // Copyright (c) 2013 Xamarin Inc. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in 18 | // all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | // THE SOFTWARE. 27 | 28 | using System; 29 | using Mono.TextEditor; 30 | 31 | //namespace Mono.TextEditor.Vi 32 | namespace VimAddin 33 | { 34 | class ViStatusArea : Gtk.DrawingArea 35 | { 36 | TextEditor editor; 37 | bool showCaret; 38 | string statusText; 39 | 40 | public ViStatusArea (TextEditor editor) 41 | { 42 | this.editor = editor; 43 | //editor.TextViewMargin.CaretBlink += HandleCaretBlink; 44 | editor.Caret.PositionChanged += HandlePositionChanged; 45 | 46 | editor.AddTopLevelWidget (this, 0, 0); 47 | ((TextEditor.EditorContainerChild)editor[this]).FixedPosition = true; 48 | Show (); 49 | } 50 | 51 | void HandlePositionChanged (object sender, DocumentLocationEventArgs e) 52 | { 53 | QueueDraw (); 54 | } 55 | 56 | void HandleCaretBlink (object sender, EventArgs e) 57 | { 58 | QueueDraw (); 59 | } 60 | 61 | public void RemoveFromParentAndDestroy () 62 | { 63 | editor.Remove (this); 64 | Destroy (); 65 | } 66 | 67 | protected override void OnDestroyed () 68 | { 69 | editor.Caret.PositionChanged -= HandlePositionChanged; 70 | //editor.TextViewMargin.CaretBlink -= HandleCaretBlink; 71 | base.OnDestroyed (); 72 | } 73 | 74 | Gdk.Rectangle lastAllocation; 75 | public void AllocateArea (TextArea textArea, Gdk.Rectangle allocation) 76 | { 77 | if (!Visible) 78 | Show (); 79 | allocation.Height -= (int)textArea.LineHeight; 80 | if (lastAllocation.Width == allocation.Width && 81 | lastAllocation.Height == allocation.Height || allocation.Height <= 1) 82 | return; 83 | lastAllocation = allocation; 84 | 85 | if (textArea.Allocation != allocation) { 86 | textArea.SizeAllocate (allocation); 87 | SetSizeRequest (allocation.Width, (int)editor.LineHeight); 88 | var pos = ((TextEditor.EditorContainerChild)editor [this]); 89 | if (pos.X != 0 || pos.Y != allocation.Height) 90 | editor.MoveTopLevelWidget (this, 0, allocation.Height); 91 | } 92 | } 93 | 94 | public bool ShowCaret { 95 | get { return showCaret; } 96 | set { 97 | if (showCaret != value) { 98 | showCaret = value; 99 | editor.Caret.IsVisible = !showCaret; 100 | //editor.RequestResetCaretBlink (); 101 | QueueDraw (); 102 | } 103 | } 104 | } 105 | 106 | public string Message { 107 | get { return statusText; } 108 | set { 109 | if (statusText == value) 110 | return; 111 | statusText = value; 112 | if (showCaret) { 113 | //editor.RequestResetCaretBlink (); 114 | } 115 | QueueDraw (); 116 | } 117 | } 118 | 119 | protected override bool OnExposeEvent (Gdk.EventExpose evnt) 120 | { 121 | using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) { 122 | cr.Rectangle (evnt.Region.Clipbox.X, evnt.Region.Clipbox.Y, evnt.Region.Clipbox.Width, evnt.Region.Clipbox.Height); 123 | cr.SetSourceColor (editor.ColorStyle.PlainText.Background); 124 | cr.Fill (); 125 | using (var layout = PangoUtil.CreateLayout (editor)) { 126 | layout.FontDescription = editor.Options.Font; 127 | 128 | layout.SetText ("000,00-00"); 129 | int minstatusw, minstatush; 130 | layout.GetPixelSize (out minstatusw, out minstatush); 131 | 132 | var line = editor.GetLine (editor.Caret.Line); 133 | var visColumn = line.GetVisualColumn (editor.GetTextEditorData (), editor.Caret.Column); 134 | 135 | if (visColumn != editor.Caret.Column) { 136 | layout.SetText (editor.Caret.Line + "," + editor.Caret.Column + "-" + visColumn); 137 | } else { 138 | layout.SetText (editor.Caret.Line + "," + editor.Caret.Column); 139 | } 140 | 141 | int statusw, statush; 142 | layout.GetPixelSize (out statusw, out statush); 143 | 144 | statusw = System.Math.Max (statusw, minstatusw); 145 | 146 | statusw += 8; 147 | cr.MoveTo (Allocation.Width - statusw, 0); 148 | statusw += 8; 149 | cr.SetSourceColor (editor.ColorStyle.PlainText.Foreground); 150 | cr.ShowLayout (layout); 151 | 152 | layout.SetText (statusText ?? ""); 153 | int w, h; 154 | layout.GetPixelSize (out w, out h); 155 | var x = System.Math.Min (0, -w + Allocation.Width - editor.TextViewMargin.CharWidth - statusw); 156 | cr.MoveTo (x, 0); 157 | cr.SetSourceColor (editor.ColorStyle.PlainText.Foreground); 158 | cr.ShowLayout (layout); 159 | if (ShowCaret) { 160 | #if false 161 | if (editor.TextViewMargin.caretBlink) { 162 | cr.Rectangle (w + x, 0, (int)editor.TextViewMargin.CharWidth, (int)editor.LineHeight); 163 | cr.Fill (); 164 | } 165 | #endif 166 | } 167 | } 168 | } 169 | return true; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /VimAddin/VimAddin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {6193DC45-BC2C-422C-A9B2-233E961D83FE} 7 | Library 8 | VimAddin 9 | VimAddin 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | false 21 | 22 | 23 | full 24 | true 25 | bin\Release 26 | prompt 27 | 4 28 | false 29 | 30 | 31 | 32 | 33 | False 34 | gtk-sharp-2.0 35 | 36 | 37 | False 38 | gtk-sharp-2.0 39 | 40 | 41 | False 42 | glib-sharp-2.0 43 | 44 | 45 | False 46 | glade-sharp-2.0 47 | 48 | 49 | False 50 | gtk-sharp-2.0 51 | 52 | 53 | False 54 | gtk-sharp-2.0 55 | 56 | 57 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/bin/Mono.Addins.dll 58 | ../../monodevelop/main/build/bin/Mono.Addins.dll 59 | mono-addins 60 | 61 | 62 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/bin/ICSharpCode.NRefactory.dll 63 | ../../monodevelop/main/build/bin/ICSharpCode.NRefactory.dll 64 | monodevelop 65 | 66 | 67 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/bin/Mono.TextEditor.dll 68 | ../../monodevelop/main/build/bin/Mono.TextEditor.dll 69 | monodevelop 70 | 71 | 72 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/bin/MonoDevelop.Core.dll 73 | ../../monodevelop/main/build/bin/MonoDevelop.Core.dll 74 | monodevelop 75 | 76 | 77 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/AddIns/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.dll 78 | ../../monodevelop/main/build/AddIns/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.dll 79 | monodevelop-core-addins 80 | 81 | 82 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/bin/MonoDevelop.Ide.dll 83 | ../../monodevelop/main/build/bin/MonoDevelop.Ide.dll 84 | monodevelop 85 | 86 | 87 | /Applications/Xamarin Studio.app/Contents/MacOS/lib/monodevelop/AddIns/DisplayBindings/SourceEditor/MonoDevelop.SourceEditor2.dll 88 | ../../monodevelop/main/build/AddIns/DisplayBindings/SourceEditor/MonoDevelop.SourceEditor2.dll 89 | monodevelop-core-addins 90 | 91 | 92 | 93 | 94 | 95 | 96 | gui.stetic 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViBuilders.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViBuilders.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (c) 2010 Novell, Inc. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using System.Linq; 29 | using Gdk; 30 | using System.Text; 31 | 32 | //namespace Mono.TextEditor.Vi 33 | namespace VimAddin 34 | { 35 | class ViBuilders 36 | { 37 | public static bool Mark (ViBuilderContext ctx) 38 | { 39 | char c = ctx.LastKey.Char; 40 | if (!char.IsLetterOrDigit (c)) { 41 | ctx.SetError ("Invalid Mark"); 42 | return true; 43 | } 44 | 45 | ctx.RunAction ((ViEditor ed) => { 46 | ViMark mark; 47 | if (!ed.Marks.TryGetValue (c, out mark)) 48 | ed.Marks [c] = mark = new ViMark (c); 49 | mark.SaveMark (ed.Data); 50 | }); 51 | return true; 52 | } 53 | 54 | public static ViBuilder InsertBuilder (ViBuilder preInsertActions) 55 | { 56 | var lastInserted = new StringBuilder (); 57 | bool inUndoTx = false; 58 | return (ViBuilderContext ctx) => { 59 | var l = ctx.LastKey; 60 | bool noModifiers = l.Modifiers == ModifierType.None; 61 | 62 | ctx.Message = ctx.Mode == ViEditorMode.Replace? "-- REPLACE --" : "-- INSERT --"; 63 | 64 | // FIXME: Figure out how to handle EndAtomicUndo and BeginAtomicUndo 65 | if ((noModifiers && l.Key == Key.Escape) || (l.Char == 'c' && (l.Modifiers & ModifierType.ControlMask) != 0)) { 66 | ctx.RunAction ((ViEditor ed) => { 67 | if (inUndoTx) 68 | { 69 | Console.WriteLine("TODO: EndAtomicUndo"); 70 | //ed.Document.EndAtomicUndo (); 71 | inUndoTx = false; 72 | } 73 | ed.LastInsertedText = lastInserted.ToString (); 74 | ed.SetMode (ViEditorMode.Normal); 75 | }); 76 | return true; 77 | } 78 | 79 | //keypad motions etc 80 | if (preInsertActions (ctx)) { 81 | if (inUndoTx) 82 | { 83 | Console.WriteLine("TODO: EndAtomicUndo"); 84 | //ctx.RunAction ( (ed) => ed.Document.EndAtomicUndo () ); 85 | } 86 | inUndoTx = false; 87 | ctx.SuppressCompleted (); 88 | lastInserted.Length = 0; 89 | return true; 90 | } 91 | 92 | if (l.Char != '\0' && noModifiers) { 93 | if (!inUndoTx) 94 | { 95 | Console.WriteLine("TODO: BeginAtomicUndo"); 96 | //ctx.RunAction ( (ed) => ed.Document.BeginAtomicUndo () ); 97 | } 98 | inUndoTx = true; 99 | ctx.SuppressCompleted (); 100 | lastInserted.Append (l.Char); 101 | ctx.InsertChar (l.Char); 102 | return true; 103 | } 104 | 105 | return false; 106 | }; 107 | } 108 | 109 | public static bool GoToMark (ViBuilderContext ctx) 110 | { 111 | char c = ctx.LastKey.Char; 112 | if (!char.IsLetterOrDigit (c)) { 113 | ctx.SetError ("Invalid Mark"); 114 | return true; 115 | } 116 | 117 | ctx.RunAction ((ViEditor ed) => { 118 | ViMark mark; 119 | if (ed.Marks.TryGetValue (c, out mark)) 120 | mark.LoadMark (ed.Data); 121 | else 122 | ed.Reset ("Unknown Mark"); 123 | }); 124 | return true; 125 | } 126 | 127 | 128 | public static bool ReplaceChar (ViBuilderContext ctx) 129 | { 130 | if (ctx.LastKey.Char != '\0') 131 | ctx.RunAction ((ViEditor ed) => ed.Data.Replace (ed.Data.Caret.Offset, 1, ctx.LastKey.Char.ToString ())); 132 | else 133 | ctx.SetError ("Expecting a character"); 134 | return true; 135 | } 136 | 137 | static void StartRegisterBuilder (ViBuilderContext ctx, ViBuilder nextBuilder) 138 | { 139 | if (ctx.Register != '\0') { 140 | ctx.SetError ("Register already set"); 141 | return; 142 | } 143 | ctx.Builder = (ViBuilderContext x) => { 144 | char c = x.LastKey.Char; 145 | if (!ViEditor.IsValidRegister (c)) { 146 | x.SetError ("Invalid register"); 147 | return true; 148 | } 149 | x.Register = c; 150 | x.Builder = nextBuilder; 151 | return true; 152 | }; 153 | } 154 | 155 | static void StartMultiplierBuilder (ViBuilderContext ctx, ViBuilder nextBuilder) 156 | { 157 | int factor = 1; 158 | int multiplier = 0; 159 | ctx.Builder = (ViBuilderContext x) => { 160 | int c = (int)x.LastKey.Char; 161 | if (c >= (int)'0' && c <= (int)'9') { 162 | //don't eat '0' if not preceded by non-zero digit 163 | //pass on to interpret as goto line start 164 | if (c == (int)'0' && factor == 1) { 165 | ctx.Multiplier *= multiplier; 166 | ctx.Builder = nextBuilder; 167 | return ctx.Builder (ctx); 168 | } 169 | int d = c - (int)'0'; 170 | multiplier = multiplier * factor + d; 171 | factor *= 10; 172 | return true; 173 | } else { 174 | ctx.Multiplier *= multiplier; 175 | ctx.Builder = nextBuilder; 176 | return ctx.Builder (ctx); 177 | } 178 | }; 179 | ctx.Builder (ctx); 180 | } 181 | 182 | public static ViBuilder MultiplierBuilder (ViBuilder nextBuilder) 183 | { 184 | return (ViBuilderContext ctx) => { 185 | var k = ctx.LastKey; 186 | if (char.IsDigit (k.Char)) { 187 | ViBuilders.StartMultiplierBuilder (ctx, nextBuilder); 188 | return true; 189 | } else { 190 | ctx.Builder = nextBuilder; 191 | return ctx.Builder (ctx); 192 | } 193 | }; 194 | } 195 | 196 | public static ViBuilder RegisterBuilder (ViBuilder nextBuilder) 197 | { 198 | return (ViBuilderContext ctx) => { 199 | var k = ctx.LastKey; 200 | if (k.Char == '"') { 201 | ViBuilders.StartRegisterBuilder (ctx, nextBuilder); 202 | return true; 203 | } else { 204 | ctx.Builder = nextBuilder; 205 | return ctx.Builder (ctx); 206 | } 207 | }; 208 | } 209 | 210 | public static ViBuilder First (params ViBuilder[] builders) 211 | { 212 | return (ViBuilderContext ctx) => builders.Any (b => b (ctx)); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViWordFindStrategy.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViWordFindStrategy.cs 3 | // 4 | // Author: 5 | // Levi Bard 6 | // 7 | // Copyright (c) 2009 Levi Bard 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using Mono.TextEditor; 29 | 30 | //namespace Mono.TextEditor.Vi 31 | namespace VimAddin 32 | { 33 | /// 34 | /// A word find strategy to mimic vi's 35 | /// 36 | public class ViWordFindStrategy: WordFindStrategy 37 | { 38 | 39 | #region IWordFindStrategy implementation 40 | 41 | /// 42 | /// Move to next non-whitespace change in character class. 43 | /// 44 | public override int FindNextSubwordOffset (TextDocument doc, int offset) 45 | { 46 | int myoffset = offset; 47 | if (0 > myoffset || doc.TextLength-1 <= myoffset){ return myoffset; } 48 | 49 | char c = doc.GetCharAt (myoffset); 50 | CharacterClass initialClass = GetCharacterClass (c); 51 | 52 | while (GetCharacterClass (c) == initialClass && 0 <= myoffset && doc.TextLength-1 > myoffset) { 53 | c = doc.GetCharAt (++myoffset); 54 | } 55 | for (c = doc.GetCharAt (myoffset); 56 | char.IsWhiteSpace (c) && 0 <= myoffset && doc.TextLength-1 > myoffset; 57 | c = doc.GetCharAt (++myoffset)); 58 | 59 | return (myoffset == offset)? myoffset+1: myoffset; 60 | } 61 | 62 | /// 63 | /// Move past next whitespace group. 64 | /// 65 | public override int FindNextWordOffset (TextDocument doc, int offset) 66 | { 67 | int myoffset = offset; 68 | if (0 > myoffset || doc.TextLength-1 <= myoffset){ return myoffset; } 69 | 70 | for (char c = doc.GetCharAt (myoffset); 71 | !char.IsWhiteSpace (c) && 0 <= myoffset && doc.TextLength-1 > myoffset; 72 | c = doc.GetCharAt (++myoffset)); 73 | for (char c = doc.GetCharAt (myoffset); 74 | char.IsWhiteSpace (c) && 0 <= myoffset && doc.TextLength-1 > myoffset; 75 | c = doc.GetCharAt (++myoffset)); 76 | 77 | return (myoffset == offset)? myoffset+1: myoffset; 78 | } 79 | 80 | /// 81 | /// Move to previous non-whitespace change in character class. 82 | /// 83 | public override int FindPrevSubwordOffset (TextDocument doc, int offset) 84 | { 85 | int myoffset = offset-1; 86 | char c; 87 | if (0 > myoffset || doc.TextLength-1 <= myoffset){ return myoffset; } 88 | 89 | for (c = doc.GetCharAt (myoffset); 90 | char.IsWhiteSpace (c) && 0 <= myoffset && doc.TextLength-1 > myoffset; 91 | c = doc.GetCharAt (--myoffset)); 92 | 93 | CharacterClass initialClass = GetCharacterClass (c); 94 | 95 | for (; GetCharacterClass (c) == initialClass && 96 | 0 <= myoffset && doc.TextLength-1 > myoffset; 97 | c = doc.GetCharAt (--myoffset)); 98 | 99 | return (0 == myoffset)? myoffset: myoffset+1; 100 | } 101 | 102 | /// 103 | /// Move to end of previous whitespace group. 104 | /// 105 | public override int FindPrevWordOffset (TextDocument doc, int offset) 106 | { 107 | --offset; 108 | if (0 > offset || doc.TextLength-1 <= offset){ return offset; } 109 | 110 | for (char c = doc.GetCharAt (offset); 111 | char.IsWhiteSpace (c) && 0 < offset && doc.TextLength > offset; 112 | c = doc.GetCharAt (--offset)); 113 | for (char c = doc.GetCharAt (offset); 114 | !char.IsWhiteSpace (c) && 0 < offset && doc.TextLength > offset; 115 | c = doc.GetCharAt (--offset)); 116 | 117 | return (0 == offset)? offset: offset+1; 118 | } 119 | 120 | #endregion 121 | 122 | private static bool OffsetIsWithinBounds (TextDocument doc, int offset) 123 | { 124 | return (offset >= 0 && offset <= doc.TextLength - 1); 125 | } 126 | 127 | public static int FindNextSubwordEndOffset (TextDocument doc, int offset) 128 | { 129 | int myoffset = offset + 1; 130 | 131 | if (!OffsetIsWithinBounds (doc, myoffset)) { 132 | return myoffset; 133 | } 134 | 135 | char c = doc.GetCharAt (myoffset); 136 | // skip whitespace 137 | while (char.IsWhiteSpace (c)) { 138 | if (OffsetIsWithinBounds (doc, ++myoffset)) { 139 | c = doc.GetCharAt (myoffset); 140 | } else { 141 | return offset; 142 | } 143 | } 144 | var initialClass = ViWordFindStrategy.GetCharacterClass (c); 145 | while (ViWordFindStrategy.GetCharacterClass (c) == initialClass && 0 <= myoffset && doc.TextLength-1 > myoffset) { 146 | c = doc.GetCharAt (++myoffset); 147 | } 148 | 149 | return System.Math.Max (offset, myoffset - 1); 150 | } 151 | 152 | public static int FindNextWordEndOffset (TextDocument doc, int offset) 153 | { 154 | int myoffset = offset + 1; 155 | 156 | if (!OffsetIsWithinBounds (doc, myoffset)) { 157 | return myoffset; 158 | } 159 | 160 | char c = doc.GetCharAt (myoffset); 161 | // skip whitespace 162 | while (char.IsWhiteSpace (c)) { 163 | if (OffsetIsWithinBounds (doc, ++myoffset)) { 164 | c = doc.GetCharAt (myoffset); 165 | } else { 166 | return offset; 167 | } 168 | } 169 | 170 | while (!char.IsWhiteSpace (c) && 0 <= myoffset && doc.TextLength-1 > myoffset) { 171 | c = doc.GetCharAt (++myoffset); 172 | } 173 | 174 | return System.Math.Max (offset, myoffset - 1); 175 | } 176 | 177 | /// 178 | /// Gets the character class for a given character. 179 | /// 180 | new static CharacterClass GetCharacterClass (char c) 181 | { 182 | if (char.IsLetterOrDigit (c) || '_' == c) { 183 | return CharacterClass.AlphaNumeric; 184 | } else if (char.IsWhiteSpace (c)) { 185 | return CharacterClass.Whitespace; 186 | } else { 187 | return CharacterClass.Symbol; 188 | } 189 | } 190 | 191 | new enum CharacterClass 192 | { 193 | AlphaNumeric, // Should be roughly equivalent to [\w\d] 194 | Whitespace, 195 | Symbol // !(AlphaNumeric || Whitespace) 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViActionMaps.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViActionMaps.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (C) 2008 Novell, Inc (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | using System; 30 | using Mono.TextEditor; 31 | 32 | //namespace Mono.TextEditor.Vi 33 | namespace VimAddin 34 | { 35 | 36 | 37 | public static class ViActionMaps 38 | { 39 | 40 | public static Action GetEditObjectCharAction (char c, Motion motion) 41 | { 42 | if (motion == Motion.None) return GetEditObjectCharAction(c); 43 | 44 | switch (c) { 45 | case 'w': 46 | return ViActions.InnerWord; 47 | case ')': 48 | case '}': 49 | case ']': 50 | case '>': 51 | if (motion == Motion.Inner) 52 | return ViActions.InnerSymbol (c); 53 | else if (motion == Motion.Outer) 54 | return ViActions.OuterSymbol (c); 55 | else 56 | return null; 57 | case '"': 58 | case '\'': 59 | case '`': 60 | if (motion == Motion.Inner) 61 | return ViActions.InnerQuote (c); 62 | else if (motion == Motion.Outer) 63 | return ViActions.OuterQuote (c); 64 | else 65 | return null; 66 | default: 67 | return null; 68 | } 69 | } 70 | 71 | public static Action GetEditObjectCharAction (char c) 72 | { 73 | switch (c) { 74 | case 'W': 75 | case 'w': 76 | return ViActions.WordEnd; 77 | case 'B': 78 | case 'b': 79 | return ViActions.WordStart; 80 | } 81 | return GetNavCharAction (c, false); 82 | } 83 | 84 | /// 85 | /// Gets the nav char action. 86 | /// 87 | /// The nav char action. 88 | /// C. 89 | /// If set to true c is a command instead of motion is command. 90 | public static Action GetNavCharAction (char c, bool isCommand) 91 | { 92 | switch (c) { 93 | case 'h': 94 | return ViActions.Left; 95 | case 'b': 96 | return CaretMoveActions.PreviousSubword; 97 | case 'B': 98 | return CaretMoveActions.PreviousWord; 99 | case 'l': 100 | return ViActions.Right; 101 | case 'e': 102 | if (isCommand) { 103 | return ViActions.NextSubwordEnd; 104 | } else { 105 | return ViActions.NextSubwordEndPlus1; 106 | } 107 | case 'E': 108 | if (isCommand) { 109 | return ViActions.NextWordEnd; 110 | } else { 111 | return ViActions.NextWordEndPlus1; 112 | } 113 | case 'w': 114 | return CaretMoveActions.NextSubword; 115 | case 'W': 116 | return CaretMoveActions.NextWord; 117 | case 'k': 118 | return ViActions.Up; 119 | case 'j': 120 | return ViActions.Down; 121 | case '%': 122 | return MiscActions.GotoMatchingBracket; 123 | case '0': 124 | return CaretMoveActions.LineStart; 125 | case '^': 126 | case '_': 127 | return CaretMoveActions.LineFirstNonWhitespace; 128 | case '$': 129 | return ViActions.LineEnd; 130 | case 'G': 131 | return CaretMoveActions.ToDocumentEnd; 132 | case '{': 133 | return ViActions.MoveToPreviousEmptyLine; 134 | case '}': 135 | return ViActions.MoveToNextEmptyLine; 136 | } 137 | return null; 138 | } 139 | 140 | public static Action GetDirectionKeyAction (Gdk.Key key, Gdk.ModifierType modifier) 141 | { 142 | // 143 | // NO MODIFIERS 144 | // 145 | if ((modifier & (Gdk.ModifierType.ShiftMask | Gdk.ModifierType.ControlMask)) == 0) { 146 | switch (key) { 147 | case Gdk.Key.Left: 148 | case Gdk.Key.KP_Left: 149 | return ViActions.Left; 150 | 151 | case Gdk.Key.Right: 152 | case Gdk.Key.KP_Right: 153 | return ViActions.Right; 154 | 155 | case Gdk.Key.Up: 156 | case Gdk.Key.KP_Up: 157 | return ViActions.Up; 158 | 159 | case Gdk.Key.Down: 160 | case Gdk.Key.KP_Down: 161 | return ViActions.Down; 162 | 163 | //not strictly vi, but more useful IMO 164 | case Gdk.Key.KP_Home: 165 | case Gdk.Key.Home: 166 | return CaretMoveActions.LineHome; 167 | 168 | case Gdk.Key.KP_End: 169 | case Gdk.Key.End: 170 | return ViActions.LineEnd; 171 | 172 | case Gdk.Key.Page_Up: 173 | case Gdk.Key.KP_Page_Up: 174 | return CaretMoveActions.PageUp; 175 | 176 | case Gdk.Key.Page_Down: 177 | case Gdk.Key.KP_Page_Down: 178 | return CaretMoveActions.PageDown; 179 | } 180 | } 181 | // 182 | // === CONTROL === 183 | // 184 | else if ((modifier & Gdk.ModifierType.ShiftMask) == 0 185 | && (modifier & Gdk.ModifierType.ControlMask) != 0) 186 | { 187 | switch (key) { 188 | case Gdk.Key.Left: 189 | case Gdk.Key.KP_Left: 190 | return CaretMoveActions.PreviousWord; 191 | 192 | case Gdk.Key.Right: 193 | case Gdk.Key.KP_Right: 194 | return CaretMoveActions.NextWord; 195 | 196 | case Gdk.Key.Up: 197 | case Gdk.Key.KP_Up: 198 | return ScrollActions.Up; 199 | 200 | // usually bound at IDE level 201 | case Gdk.Key.u: 202 | return CaretMoveActions.PageUp; 203 | 204 | case Gdk.Key.Down: 205 | case Gdk.Key.KP_Down: 206 | return ScrollActions.Down; 207 | 208 | case Gdk.Key.d: 209 | return CaretMoveActions.PageDown; 210 | 211 | case Gdk.Key.KP_Home: 212 | case Gdk.Key.Home: 213 | return CaretMoveActions.ToDocumentStart; 214 | 215 | case Gdk.Key.KP_End: 216 | case Gdk.Key.End: 217 | return CaretMoveActions.ToDocumentEnd; 218 | } 219 | } 220 | return null; 221 | } 222 | 223 | public static Action GetInsertKeyAction (Gdk.Key key, Gdk.ModifierType modifier) 224 | { 225 | // 226 | // NO MODIFIERS 227 | // 228 | if ((modifier & (Gdk.ModifierType.ShiftMask | Gdk.ModifierType.ControlMask)) == 0) { 229 | switch (key) { 230 | case Gdk.Key.Tab: 231 | return MiscActions.InsertTab; 232 | 233 | case Gdk.Key.Return: 234 | case Gdk.Key.KP_Enter: 235 | return MiscActions.InsertNewLine; 236 | 237 | case Gdk.Key.BackSpace: 238 | return DeleteActions.Backspace; 239 | 240 | case Gdk.Key.Delete: 241 | case Gdk.Key.KP_Delete: 242 | return DeleteActions.Delete; 243 | 244 | case Gdk.Key.Insert: 245 | return MiscActions.SwitchCaretMode; 246 | } 247 | } 248 | // 249 | // CONTROL 250 | // 251 | else if ((modifier & Gdk.ModifierType.ControlMask) != 0 252 | && (modifier & Gdk.ModifierType.ShiftMask) == 0) 253 | { 254 | switch (key) { 255 | case Gdk.Key.BackSpace: 256 | return DeleteActions.PreviousWord; 257 | 258 | case Gdk.Key.Delete: 259 | case Gdk.Key.KP_Delete: 260 | return DeleteActions.NextWord; 261 | } 262 | } 263 | // 264 | // SHIFT 265 | // 266 | else if ((modifier & Gdk.ModifierType.ControlMask) == 0 267 | && (modifier & Gdk.ModifierType.ShiftMask) != 0) 268 | { 269 | switch (key) { 270 | case Gdk.Key.Tab: 271 | return MiscActions.RemoveTab; 272 | 273 | case Gdk.Key.BackSpace: 274 | return DeleteActions.Backspace; 275 | 276 | case Gdk.Key.Return: 277 | case Gdk.Key.KP_Enter: 278 | return MiscActions.InsertNewLine; 279 | } 280 | } 281 | return null; 282 | } 283 | 284 | public static Action GetCommandCharAction (char c) 285 | { 286 | switch (c) { 287 | case 'u': 288 | return MiscActions.Undo; 289 | } 290 | return null; 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViEditor.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViEditorContext.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (c) 2010 Novell, Inc. (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using System.Collections.Generic; 29 | using System.Linq; 30 | using System.Text; 31 | using Gdk; 32 | using Mono.TextEditor; 33 | 34 | //namespace Mono.TextEditor.Vi 35 | namespace VimAddin 36 | { 37 | public class ViEditor 38 | { 39 | NewViEditMode editMode; 40 | 41 | #region Register constants 42 | const char REG_YANK_DEFAULT = '0'; 43 | const char REG_DELETE_DEFAULT = '1'; 44 | const char REG_PASTE_DEFAULT = '"'; 45 | const char REG_DELETE_SMALL = '-'; 46 | const char REG_BLACKHOLE = '_'; 47 | const char REG_LAST_INSERTED_TEXT = '.'; 48 | const char REG_FILENAME_CURRENT = '%'; 49 | const char REG_FILENAME_ALTERNATE = '#'; 50 | const char REG_LAST_COMMANDLINE = ':'; 51 | const char REG_X11_PRIMARY = '*'; 52 | const char REG_X11_CLIPBOARD = '+'; 53 | const char REG_X11_LAST_DROP = '~'; 54 | const char REG_LAST_SEARCH = '/'; 55 | #endregion 56 | 57 | Dictionary registers = new Dictionary (); 58 | Dictionary marks = new Dictionary (); 59 | 60 | public TextEditor Editor { get { return editMode.Editor; } } 61 | public TextEditorData Data { get { return editMode.Data; } } 62 | public TextDocument Document { get { return Data.Document; } } 63 | ViBuilderContext Context { get; set; } 64 | 65 | public event EventHandler ModeChanged; 66 | public event EventHandler MessageChanged; 67 | 68 | string message; 69 | public string Message { 70 | get { 71 | return message; 72 | } 73 | private set { 74 | message = value; 75 | var e = MessageChanged; 76 | if (e != null) 77 | e (this, EventArgs.Empty); 78 | } 79 | } 80 | 81 | ViEditorMode mode; 82 | public ViEditorMode Mode { 83 | get { 84 | return mode; 85 | } 86 | private set { 87 | mode = value; 88 | var e = ModeChanged; 89 | if (e != null) 90 | e (this, EventArgs.Empty); 91 | } 92 | } 93 | 94 | //shared between editors in the same process 95 | //TODO: maybe move these into some kind of shared context to pass around explicitly 96 | static char lastRegisterUsed = '0'; 97 | static string lastPattern; 98 | static string lastCommandline; 99 | static string lastInsertedText; 100 | 101 | public string LastCommandline { 102 | get { return lastCommandline; } 103 | set { lastCommandline = value; } 104 | } 105 | 106 | public string LastPattern { 107 | get { return lastPattern; } 108 | set { lastPattern = value; } 109 | } 110 | 111 | public string LastInsertedText { 112 | get { return lastInsertedText; } 113 | set { lastInsertedText = value; } 114 | } 115 | 116 | public void Reset (string message) 117 | { 118 | Context = ViBuilderContext.Create (this); 119 | Message = message; 120 | 121 | if (Data.Caret.Mode != CaretMode.Block) { 122 | Data.Caret.Mode = CaretMode.Block; 123 | if (Data.Caret.Column > DocumentLocation.MinColumn) 124 | Data.Caret.Column--; 125 | } 126 | ViActions.RetreatFromLineEnd (Data); 127 | } 128 | 129 | public void SetMode (ViEditorMode mode) 130 | { 131 | if (this.Mode == mode) 132 | return; 133 | this.Mode = mode; 134 | 135 | if (Data == null) 136 | return; 137 | 138 | switch (mode) { 139 | case ViEditorMode.Insert: 140 | Message = "-- INSERT --"; 141 | editMode.SetCaretMode (CaretMode.Insert); 142 | break; 143 | case ViEditorMode.Normal: 144 | editMode.SetCaretMode (CaretMode.Block); 145 | break; 146 | case ViEditorMode.Replace: 147 | Message = "-- REPLACE --"; 148 | editMode.SetCaretMode (CaretMode.Underscore); 149 | break; 150 | } 151 | } 152 | 153 | public void OnCaretPositionChanged () 154 | { 155 | switch (Mode) { 156 | case ViEditorMode.Insert: 157 | case ViEditorMode.Replace: 158 | case ViEditorMode.Visual: 159 | return; 160 | case ViEditorMode.Normal: 161 | ViActions.RetreatFromLineEnd (Data); 162 | break; 163 | default: 164 | Reset (""); 165 | break; 166 | } 167 | } 168 | 169 | public void ProcessKey (Gdk.ModifierType modifiers, Key key, char ch) 170 | { 171 | if (Context == null) 172 | Reset (""); 173 | 174 | Context.ProcessKey (key, ch, modifiers); 175 | if (Context.Completed) { 176 | Reset (Context.Message); 177 | } else { 178 | Message = Context.Message; 179 | } 180 | } 181 | 182 | public Dictionary Marks { get { return marks; } } 183 | 184 | public bool SearchBackward { get; set; } 185 | public string LastReplacement { get; set; } 186 | 187 | public ViEditor (NewViEditMode editMode) 188 | { 189 | this.editMode = editMode; 190 | SetMode (ViEditorMode.Normal); 191 | } 192 | 193 | public string GetRegisterContents (char register) 194 | { 195 | if (register == '"') 196 | register = lastRegisterUsed; 197 | 198 | switch (register) { 199 | case REG_LAST_INSERTED_TEXT: 200 | return LastInsertedText; 201 | 202 | case REG_FILENAME_ALTERNATE: 203 | case REG_FILENAME_CURRENT: 204 | return Document.FileName; 205 | 206 | case REG_LAST_COMMANDLINE: 207 | return LastCommandline; 208 | 209 | case REG_LAST_SEARCH: 210 | return LastPattern; 211 | 212 | case REG_X11_PRIMARY: 213 | case REG_X11_CLIPBOARD: 214 | case REG_X11_LAST_DROP: 215 | throw new NotImplementedException("X11 registers"); 216 | 217 | case REG_DELETE_SMALL: 218 | break; 219 | 220 | default: 221 | int regInt = (int)register; 222 | if (((int)'a' <= regInt && regInt <= (int)'z') 223 | || ((int)'A' <= regInt && regInt <= (int)'Z') 224 | || ((int)'0' <= regInt && regInt <= (int)'9')) 225 | break; 226 | else 227 | throw new InvalidOperationException ("Invalid register '" + register + "'."); 228 | } 229 | 230 | string value; 231 | registers.TryGetValue (register, out value); 232 | return value; 233 | } 234 | 235 | //FIXME: add others when implemented 236 | static string validSetRegisters = "\"-"; 237 | static string validGetRegisters = ".%#:/-"; 238 | static string validRegisters = "\".%#:/-"; 239 | 240 | public static bool IsValidRegister (char c) 241 | { 242 | int regInt = (int) c; 243 | return (((int)'a' <= regInt && regInt <= (int)'z') 244 | || ((int)'A' <= regInt && regInt <= (int)'Z') 245 | || ((int)'0' <= regInt && regInt <= (int)'9') 246 | || validRegisters.Contains (c)); 247 | } 248 | 249 | public static bool IsValidSetRegister (char c) 250 | { 251 | int regInt = (int) c; 252 | return (((int)'a' <= regInt && regInt <= (int)'z') 253 | || ((int)'A' <= regInt && regInt <= (int)'Z') 254 | || ((int)'0' <= regInt && regInt <= (int)'9') 255 | || validSetRegisters.Contains (c)); 256 | } 257 | 258 | public static bool IsValidGetRegister (char c) 259 | { 260 | int regInt = (int) c; 261 | return (((int)'a' <= regInt && regInt <= (int)'z') 262 | || ((int)'A' <= regInt && regInt <= (int)'Z') 263 | || ((int)'0' <= regInt && regInt <= (int)'9') 264 | || validGetRegisters.Contains (c)); 265 | } 266 | 267 | public void SetRegisterContents (char register, string value) 268 | { 269 | switch (register) { 270 | case REG_X11_PRIMARY: 271 | case REG_X11_CLIPBOARD: 272 | throw new NotImplementedException ("GUI clipboard"); 273 | 274 | //delete/change registers 275 | case '1': case '2': case '3': case '4': case '5': 276 | case '6': case '7': case '8': 277 | //move registers 1-8 downwards if needed 278 | for (int i = (int)'8'; i >= (int)register; i--) 279 | registers[(char)(i + 1)] = registers[(char)i]; 280 | break; 281 | 282 | case '0': 283 | case '9': 284 | case REG_DELETE_SMALL: 285 | break; 286 | 287 | case REG_PASTE_DEFAULT: 288 | register = '0'; 289 | break; 290 | 291 | default: 292 | int regInt = (int) register; 293 | if (((int)'a' <= regInt && regInt <= (int)'z') 294 | || ((int)'A' <= regInt && regInt <= (int)'Z')) 295 | break; 296 | else 297 | throw new InvalidOperationException ("Cannot write to register '" + register + "'."); 298 | } 299 | 300 | lastRegisterUsed = register; 301 | registers[register] = value; 302 | } 303 | } 304 | 305 | public enum ViEditorMode 306 | { 307 | Normal = 0, 308 | Insert, // I 309 | Replace, // R 310 | Visual, // v 311 | VisualLine, // V 312 | VisualBlock, // ^V TODO 313 | Command, 314 | } 315 | } 316 | 317 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViBuilderContext.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // ViBuilderContext.cs 4 | // 5 | // Author: 6 | // Michael Hutchinson 7 | // 8 | // Copyright (c) 2010 Novell, Inc. 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in 18 | // all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | // THE SOFTWARE. 27 | 28 | using System; 29 | using Gdk; 30 | using System.Collections.Generic; 31 | using System.Text; 32 | using Mono.TextEditor; 33 | using System.Linq; 34 | 35 | //namespace Mono.TextEditor.Vi 36 | namespace VimAddin 37 | { 38 | 39 | /// 40 | /// Returns true if it handled the keystroke. 41 | /// 42 | public delegate bool ViBuilder (ViBuilderContext ctx); 43 | 44 | public class ViBuilderContext 45 | { 46 | readonly ViEditor editor; 47 | readonly List keys = new List (); 48 | 49 | ViBuilderContext (ViEditor editor) 50 | { 51 | this.editor = editor; 52 | Multiplier = 1; 53 | } 54 | 55 | public void ProcessKey (Key key, char ch, ModifierType modifiers) 56 | { 57 | var k = ch == '\0'? new ViKey (modifiers, key) : new ViKey (modifiers, ch); 58 | Keys.Add (k); 59 | if (!Builder (this)) { 60 | SetError ("Unknown command"); 61 | } 62 | } 63 | 64 | public void SetError (string error) 65 | { 66 | Completed = Error = true; 67 | Message = error; 68 | } 69 | 70 | public string Message { get; set; } 71 | public int Multiplier { get; set; } 72 | public char Register { get; set; } 73 | protected ViEditor Editor { get { return editor; } } 74 | public List Keys { get { return keys; } } 75 | public bool Completed { get; private set; } 76 | public bool Error { get; private set; } 77 | 78 | //copied from EditMode.cs, with the undo stack handling code removed 79 | public void InsertChar (char ch) 80 | { 81 | var data = Editor.Data; 82 | var doc = Editor.Document; 83 | var caret = Editor.Data.Caret; 84 | 85 | if (!data.CanEdit (data.Caret.Line)) 86 | return; 87 | 88 | #if false 89 | if (Editor.Editor != null) 90 | Editor.Editor.HideMouseCursor (); 91 | #endif 92 | 93 | data.DeleteSelectedText (data.IsSomethingSelected ? data.MainSelection.SelectionMode != SelectionMode.Block : true); 94 | 95 | if (!char.IsControl (ch) && data.CanEdit (caret.Line)) { 96 | DocumentLine line = doc.GetLine (caret.Line); 97 | if (caret.IsInInsertMode || caret.Column >= line.Length + 1) { 98 | string text; 99 | if (data.HasIndentationTracker) { 100 | text = caret.Column > line.Length + 1 ? data.GetIndentationString (caret.Location) + ch.ToString () : ch.ToString (); 101 | } else { 102 | text = ch.ToString (); 103 | } 104 | if (data.IsSomethingSelected && data.MainSelection.SelectionMode == SelectionMode.Block) { 105 | int length = 0; 106 | for (int lineNumber = data.MainSelection.MinLine; lineNumber <= data.MainSelection.MaxLine; lineNumber++) { 107 | length = data.Insert (doc.GetLine (lineNumber).Offset + caret.Column, text); 108 | } 109 | caret.PreserveSelection = true; 110 | caret.Column += length - 1; 111 | data.MainSelection = data.MainSelection.WithRange ( 112 | new DocumentLocation (data.MainSelection.Anchor.Line, caret.Column + 1), 113 | new DocumentLocation (data.MainSelection.Lead.Line, caret.Column + 1) 114 | ); 115 | doc.CommitMultipleLineUpdate (data.MainSelection.MinLine, data.MainSelection.MaxLine); 116 | } else { 117 | int length = data.Insert (caret.Offset, text); 118 | caret.Column += length - 1; 119 | } 120 | } else { 121 | data.Replace (caret.Offset, 1, ch.ToString ()); 122 | } 123 | caret.Column++; 124 | if (caret.PreserveSelection) 125 | caret.PreserveSelection = false; 126 | } 127 | if (data.IsSomethingSelected && data.MainSelection.SelectionMode == SelectionMode.Block) 128 | data.Caret.PreserveSelection = false; 129 | } 130 | 131 | public void RunAction (Action action) 132 | { 133 | Completed = true; 134 | 135 | //FALLBACK for builders that don't handler multipliers directly 136 | //we cap these at 100, to reduce the length of time MD could be unresponsive 137 | if (Multiplier > 1) { 138 | for (int i = 0; i < System.Math.Min (100, Multiplier); i++) 139 | action (editor); 140 | } else { 141 | action (editor); 142 | } 143 | } 144 | 145 | public ViEditorMode Mode { get { return Editor.Mode; } } 146 | 147 | //HACK: this is really inelegant 148 | public void SuppressCompleted () 149 | { 150 | Completed = false; 151 | } 152 | 153 | private ViBuilder _builder; 154 | 155 | public ViBuilder Builder { 156 | get { return _builder; } 157 | set { 158 | if (_builder == value) 159 | return; 160 | if (value == null) 161 | throw new ArgumentException ("builder cannot be null"); 162 | if (Completed) 163 | throw new InvalidOperationException ("builder cannot be set after context is completed"); 164 | _builder = value; 165 | } 166 | } 167 | 168 | public ViKey LastKey { 169 | get { return Keys[Keys.Count - 1]; } 170 | } 171 | 172 | public static ViBuilderContext Create (ViEditor editor) 173 | { 174 | return new ViBuilderContext (editor) { 175 | Builder = normalBuilder 176 | }; 177 | } 178 | 179 | static ViBuilderContext () 180 | { 181 | normalBuilder = 182 | ViBuilders.RegisterBuilder ( 183 | ViBuilders.MultiplierBuilder ( 184 | ViBuilders.First (normalActions.Builder, motions.Builder, nonCharMotions.Builder))); 185 | insertActions = ViBuilders.First (nonCharMotions.Builder, insertEditActions.Builder); 186 | } 187 | 188 | static ViBuilder normalBuilder, insertActions; 189 | 190 | static ViCommandMap normalActions = new ViCommandMap () { 191 | { 'J', ViActions.Join }, 192 | { 'z', new ViCommandMap () { 193 | { 'A', FoldActions.ToggleFoldRecursive }, 194 | { 'C', FoldActions.CloseFoldRecursive }, 195 | { 'M', FoldActions.CloseAllFolds }, 196 | { 'O', FoldActions.OpenFoldRecursive }, 197 | { 'R', FoldActions.OpenAllFolds }, 198 | { 'a', FoldActions.ToggleFold }, 199 | { 'c', FoldActions.CloseFold }, 200 | { 'o', FoldActions.OpenFold }, 201 | }}, 202 | { 'g', new ViCommandMap () { 203 | { 'g', GotoLine, true }, 204 | }}, 205 | { 'r', ViBuilders.ReplaceChar }, 206 | { '~', ViActions.ToggleCase }, 207 | { 'm', ViBuilders.Mark }, 208 | { 'M', ViEditorActions.CaretToScreenCenter }, 209 | { 'H', ViEditorActions.CaretToScreenTop }, 210 | { 'L', ViEditorActions.CaretToScreenBottom }, 211 | { 'u', MiscActions.Undo }, 212 | { 'i', Insert, true }, 213 | { 'R', Replace, true }, 214 | { 'o', Open, true }, 215 | { 'O', OpenAbove, true }, 216 | { new ViKey (ModifierType.ControlMask, Key.Up), ScrollActions.Up }, 217 | { new ViKey (ModifierType.ControlMask, Key.KP_Up), ScrollActions.Up }, 218 | { new ViKey (ModifierType.ControlMask, Key.Down), ScrollActions.Down }, 219 | { new ViKey (ModifierType.ControlMask, Key.KP_Down), ScrollActions.Down }, 220 | }; 221 | 222 | static ViCommandMap motions = new ViCommandMap () { 223 | { '`', ViBuilders.GoToMark }, 224 | { 'h', ViActions.Left }, 225 | { 'b', CaretMoveActions.PreviousSubword }, 226 | { 'B', CaretMoveActions.PreviousWord }, 227 | { 'l', ViActions.Right }, 228 | { 'w', CaretMoveActions.NextSubword }, 229 | { 'W', CaretMoveActions.NextWord }, 230 | { 'k', ViActions.Up }, 231 | { 'j', ViActions.Down }, 232 | { '%', MiscActions.GotoMatchingBracket }, 233 | { '0', CaretMoveActions.LineStart }, 234 | { '^', CaretMoveActions.LineFirstNonWhitespace }, 235 | { '_', CaretMoveActions.LineFirstNonWhitespace }, 236 | { '$', ViActions.LineEnd }, 237 | { 'G', CaretMoveActions.ToDocumentEnd }, 238 | { '{', ViActions.MoveToPreviousEmptyLine }, 239 | { '}', ViActions.MoveToNextEmptyLine }, 240 | }; 241 | 242 | static ViCommandMap nonCharMotions = new ViCommandMap () { 243 | { Key.Left, ViActions.Left }, 244 | { Key.KP_Left, ViActions.Left }, 245 | { Key.Right, ViActions.Right }, 246 | { Key.KP_Right, ViActions.Right }, 247 | { Key.Up, ViActions.Up }, 248 | { Key.KP_Up, ViActions.Up }, 249 | { Key.Down, ViActions.Down }, 250 | { Key.KP_Down, ViActions.Down }, 251 | { Key.KP_Home, CaretMoveActions.LineHome }, 252 | { Key.Home, CaretMoveActions.LineHome }, 253 | { Key.KP_End, ViActions.LineEnd }, 254 | { Key.End, ViActions.LineEnd }, 255 | { Key.Page_Up, CaretMoveActions.PageUp }, 256 | { Key.KP_Page_Up, CaretMoveActions.PageUp }, 257 | { Key.Page_Down, CaretMoveActions.PageDown }, 258 | { Key.KP_Page_Down, CaretMoveActions.PageDown }, 259 | { new ViKey (ModifierType.ControlMask, Key.Left), CaretMoveActions.PreviousWord }, 260 | { new ViKey (ModifierType.ControlMask, Key.KP_Left), CaretMoveActions.PreviousWord }, 261 | { new ViKey (ModifierType.ControlMask, Key.Right), CaretMoveActions.NextWord }, 262 | { new ViKey (ModifierType.ControlMask, Key.KP_Right), CaretMoveActions.NextWord }, 263 | { new ViKey (ModifierType.ControlMask, Key.Home), CaretMoveActions.ToDocumentStart }, 264 | { new ViKey (ModifierType.ControlMask, Key.KP_Home), CaretMoveActions.ToDocumentStart }, 265 | { new ViKey (ModifierType.ControlMask, Key.End), CaretMoveActions.ToDocumentEnd }, 266 | { new ViKey (ModifierType.ControlMask, Key.KP_End), CaretMoveActions.ToDocumentEnd }, 267 | { new ViKey (ModifierType.ControlMask, 'u'), CaretMoveActions.PageUp }, 268 | { new ViKey (ModifierType.ControlMask, 'd'), CaretMoveActions.PageDown }, 269 | }; 270 | 271 | static ViCommandMap insertEditActions = new ViCommandMap () { 272 | { Key.Tab, MiscActions.InsertTab }, 273 | { Key.Return, MiscActions.InsertNewLine }, 274 | { Key.KP_Enter, MiscActions.InsertNewLine }, 275 | { Key.BackSpace, DeleteActions.Backspace }, 276 | { Key.Delete, DeleteActions.Delete }, 277 | { Key.KP_Delete, DeleteActions.Delete }, 278 | { Key.Insert, MiscActions.SwitchCaretMode }, 279 | { new ViKey (ModifierType.ControlMask, Key.BackSpace), DeleteActions.PreviousWord }, 280 | { new ViKey (ModifierType.ControlMask, Key.Delete), DeleteActions.NextWord }, 281 | { new ViKey (ModifierType.ControlMask, Key.KP_Delete), DeleteActions.NextWord }, 282 | { new ViKey (ModifierType.ShiftMask, Key.Tab), MiscActions.RemoveTab }, 283 | { new ViKey (ModifierType.ShiftMask, Key.BackSpace), DeleteActions.Backspace }, 284 | }; 285 | 286 | static bool Insert (ViBuilderContext ctx) 287 | { 288 | ctx.RunAction ((ViEditor e) => e.SetMode (ViEditorMode.Insert)); 289 | ctx.SuppressCompleted (); 290 | 291 | ctx.Builder = ViBuilders.InsertBuilder (insertActions); 292 | return true; 293 | } 294 | 295 | static bool Replace (ViBuilderContext ctx) 296 | { 297 | ctx.RunAction ((ViEditor e) => e.SetMode (ViEditorMode.Replace)); 298 | ctx.SuppressCompleted (); 299 | 300 | ctx.Builder = ViBuilders.InsertBuilder (insertActions); 301 | return true; 302 | } 303 | 304 | static bool Open (ViBuilderContext ctx) 305 | { 306 | ctx.RunAction ((ViEditor e) => MiscActions.InsertNewLineAtEnd (e.Data)); 307 | return Insert (ctx); 308 | } 309 | 310 | static bool OpenAbove (ViBuilderContext ctx) 311 | { 312 | // FIXME: this doesn't work correctly on the first line 313 | ctx.RunAction ((ViEditor e) => ViActions.Up (e.Data)); 314 | return Open (ctx); 315 | } 316 | 317 | static bool GotoLine (ViBuilderContext ctx) 318 | { 319 | ctx.RunAction ((ViEditor e) => ViEditorActions.CaretToLineNumber (ctx.Multiplier, e)); 320 | ctx.RunAction ((ViEditor e) => CaretMoveActions.LineFirstNonWhitespace (e.Data)); 321 | return true; 322 | } 323 | } 324 | } 325 | 326 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViKeyNotation.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViKeyNotation.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (c) 2010 Novell, Inc. (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | using System; 28 | using Gdk; 29 | using System.Collections.Generic; 30 | using System.Text; 31 | 32 | //namespace Mono.TextEditor.Vi 33 | namespace VimAddin 34 | { 35 | public struct ViKey : IEquatable 36 | { 37 | Gdk.ModifierType modifiers; 38 | char ch; 39 | Gdk.Key key; 40 | 41 | public ViKey (char ch) : this (ModifierType.None, ch, (Gdk.Key) 0) 42 | { 43 | } 44 | 45 | public ViKey (Gdk.Key key) : this (ModifierType.None, '\0', key) 46 | { 47 | } 48 | 49 | public ViKey (ModifierType modifiers, Gdk.Key key) : this (modifiers, '\0', key) 50 | { 51 | } 52 | 53 | public ViKey (ModifierType modifiers, char ch) : this (modifiers, ch, (Gdk.Key)0) 54 | { 55 | } 56 | 57 | ViKey (ModifierType modifiers, char ch, Gdk.Key key): this () 58 | { 59 | this.modifiers = modifiers & KnownModifiers; 60 | this.ch = ch; 61 | this.key = key; 62 | } 63 | 64 | public ModifierType Modifiers { get { return this.modifiers; } } 65 | public char Char { get { return this.ch; } } 66 | public Key Key { get { return this.key; } } 67 | 68 | static ModifierType KnownModifiers = ModifierType.ShiftMask | ModifierType.MetaMask 69 | | ModifierType.ControlMask | ModifierType.Mod1Mask; 70 | 71 | public static implicit operator ViKey (char ch) 72 | { 73 | return new ViKey (ch); 74 | } 75 | 76 | public static implicit operator ViKey (Gdk.Key key) 77 | { 78 | return new ViKey (key); 79 | } 80 | 81 | public bool Equals (ViKey other) 82 | { 83 | return modifiers == other.modifiers && ch == other.ch && key == other.key; 84 | } 85 | 86 | public override bool Equals (object obj) 87 | { 88 | if (obj == null) 89 | return false; 90 | if (object.Equals (this, obj)) 91 | return true; 92 | if (!(obj is ViKey)) 93 | return false; 94 | return Equals ((ViKey)obj); 95 | } 96 | 97 | public override int GetHashCode () 98 | { 99 | unchecked { 100 | return modifiers.GetHashCode () ^ ch.GetHashCode () ^ key.GetHashCode (); 101 | } 102 | } 103 | 104 | public override string ToString() 105 | { 106 | return ViKeyNotation.IsValid(this)? ViKeyNotation.ToString(this) : ""; 107 | } 108 | } 109 | 110 | public static class ViKeyNotation 111 | { 112 | public static string ToString(ViKey key) 113 | { 114 | var sb = new StringBuilder(); 115 | ViKeyNotation.AppendToString(key, sb); 116 | return sb.ToString(); 117 | } 118 | 119 | public static string ToString (IList keys) 120 | { 121 | var sb = new StringBuilder (); 122 | foreach (var k in keys) 123 | AppendToString (k, sb); 124 | return sb.ToString (); 125 | } 126 | 127 | static void AppendToString (ViKey key, StringBuilder sb) 128 | { 129 | var c = GetString (key.Char); 130 | if (c != null && key.Char != '\0') { 131 | if (c.Length == 1 && key.Modifiers == ModifierType.None) { 132 | sb.Append (c); 133 | return; 134 | } 135 | } else { 136 | c = GetString (key.Key); 137 | } 138 | 139 | if (c == null) { 140 | var msg = string.Format ("Invalid key char=0x{0:x} key={1}", (int)key.Char, key.Key); 141 | throw new InvalidOperationException (msg); 142 | } 143 | 144 | sb.Append ("<"); 145 | 146 | if ((key.Modifiers & ModifierType.ShiftMask) != 0) 147 | sb.Append ("S-"); 148 | if ((key.Modifiers & ModifierType.ControlMask) != 0) 149 | sb.Append ("C-"); 150 | if ((key.Modifiers & ModifierType.Mod1Mask) != 0) 151 | sb.Append ("M-"); 152 | if ((key.Modifiers & ModifierType.MetaMask) != 0) //HACK: Mac command key 153 | sb.Append ("D-"); 154 | 155 | sb.Append (c); 156 | sb.Append (">"); 157 | } 158 | 159 | public static bool IsValid (ViKey key) 160 | { 161 | return GetString (key.Char) != null || keyStringMaps.ContainsKey (key.Key); 162 | } 163 | 164 | static string GetString (char ch) 165 | { 166 | string s; 167 | if (charStringMaps.TryGetValue (ch, out s)) 168 | return s; 169 | if (char.IsControl (ch)) 170 | return null; 171 | return ch.ToString (); 172 | } 173 | 174 | static Dictionary charStringMaps = new Dictionary () { 175 | { '\0', "Nul" }, 176 | { ' ', "Space" }, 177 | { '\r', "CR" }, 178 | { '\n', "NL" }, 179 | { '\f', "FF" }, 180 | { '\t', "Tab" }, 181 | { '<', "lt" }, 182 | { '\\', "Bslash" }, 183 | { '|', "Bar" }, 184 | }; 185 | 186 | static string GetString (Key key) 187 | { 188 | string str; 189 | if (keyStringMaps.TryGetValue (key, out str)) 190 | return str; 191 | return null; 192 | } 193 | 194 | static Dictionary keyStringMaps = new Dictionary () { 195 | { Gdk.Key.BackSpace, "BS" }, 196 | { Gdk.Key.Tab, "Tab" }, 197 | { Gdk.Key.Return, "Enter" }, //"CR" "Return" 198 | { Gdk.Key.Escape, "Esc" }, 199 | { Gdk.Key.space, "Space" }, 200 | { Gdk.Key.KP_Up, "Up" }, 201 | { Gdk.Key.Up, "Up" }, 202 | { Gdk.Key.KP_Down, "Down" }, 203 | { Gdk.Key.Down, "Down" }, 204 | { Gdk.Key.KP_Left, "Left" }, 205 | { Gdk.Key.Left, "Left" }, 206 | { Gdk.Key.KP_Right, "Right" }, 207 | { Gdk.Key.Right, "Right" }, 208 | { Gdk.Key.F1, "F1" }, 209 | { Gdk.Key.F2, "F2" }, 210 | { Gdk.Key.F3, "F3" }, 211 | { Gdk.Key.F4, "F4" }, 212 | { Gdk.Key.F5, "F5" }, 213 | { Gdk.Key.F6, "F6" }, 214 | { Gdk.Key.F7, "F7" }, 215 | { Gdk.Key.F8, "F8" }, 216 | { Gdk.Key.F9, "F9" }, 217 | { Gdk.Key.F10, "F10" }, 218 | { Gdk.Key.F11, "F11" }, 219 | { Gdk.Key.F12, "F12" }, 220 | { Gdk.Key.Insert, "Insert" }, 221 | { Gdk.Key.Delete, "Del" }, 222 | { Gdk.Key.KP_Delete, "kDel" }, 223 | { Gdk.Key.Home, "Home" }, 224 | { Gdk.Key.End, "End" }, 225 | { Gdk.Key.Page_Up, "PageUp" }, 226 | { Gdk.Key.Page_Down, "PageDown" }, 227 | { Gdk.Key.KP_Home, "kHome" }, 228 | { Gdk.Key.KP_End, "kEnd" }, 229 | { Gdk.Key.KP_Page_Up, "kPageUp" }, 230 | { Gdk.Key.KP_Page_Down, "kPageDown" }, 231 | { Gdk.Key.KP_Add, "kPlus" }, 232 | { Gdk.Key.KP_Subtract, "kMinus" }, 233 | { Gdk.Key.KP_Multiply, "kMultiply" }, 234 | { Gdk.Key.KP_Divide, "kDivide" }, 235 | { Gdk.Key.KP_Enter, "kEnter" }, 236 | { Gdk.Key.KP_Decimal, "kPoint" }, 237 | { Gdk.Key.KP_0, "k0" }, 238 | { Gdk.Key.KP_1, "k1" }, 239 | { Gdk.Key.KP_2, "k2" }, 240 | { Gdk.Key.KP_3, "k3" }, 241 | { Gdk.Key.KP_4, "k4" }, 242 | { Gdk.Key.KP_5, "k5" }, 243 | { Gdk.Key.KP_6, "k6" }, 244 | { Gdk.Key.KP_7, "k7" }, 245 | { Gdk.Key.KP_8, "k8" }, 246 | { Gdk.Key.KP_9, "k9" }, 247 | { Gdk.Key.Help, "Help" }, 248 | { Gdk.Key.Undo, "Undo" }, 249 | }; 250 | 251 | static char GetChar (string charName) 252 | { 253 | if (charName.Length == 1) 254 | return charName[0]; 255 | 256 | char c; 257 | if (stringCharMaps.TryGetValue (charName, out c)) 258 | return c; 259 | 260 | //FIXME this should be environment/editor-dependent 261 | if (charName == "EOL") 262 | return '\n'; 263 | 264 | throw new FormatException ("Unknown char '" + charName +"'"); 265 | } 266 | 267 | static Dictionary stringCharMaps = new Dictionary () { 268 | { "Nul", '\0' }, 269 | { "Space", ' ' }, 270 | { "CR", '\r' }, 271 | { "NL", '\n' }, 272 | { "FF", '\f' }, 273 | { "Tab", '\t' }, 274 | { "lt", '<' }, 275 | { "Bslash", '\\' }, 276 | { "Bar", '|' }, 277 | }; 278 | 279 | static Gdk.Key GetKey (string code) 280 | { 281 | Gdk.Key k; 282 | if (stringKeyMaps.TryGetValue (code, out k)) 283 | return k; 284 | return (Gdk.Key)0; 285 | } 286 | 287 | static Dictionary stringKeyMaps = new Dictionary () { 288 | { "BS", Gdk.Key.BackSpace }, 289 | { "Tab", Gdk.Key.Tab }, 290 | { "CR", Gdk.Key.Return }, 291 | { "Return", Gdk.Key.Return }, 292 | { "Enter", Gdk.Key.Return }, 293 | { "Esc", Gdk.Key.Escape }, 294 | { "Space", Gdk.Key.space }, 295 | { "Up", Gdk.Key.Up }, 296 | { "Down", Gdk.Key.Down }, 297 | { "Left", Gdk.Key.Left }, 298 | { "Right", Gdk.Key.Right }, 299 | { "#1", Gdk.Key.F1 }, 300 | { "F1", Gdk.Key.F1 }, 301 | { "#2", Gdk.Key.F2 }, 302 | { "F2", Gdk.Key.F2 }, 303 | { "#3", Gdk.Key.F3 }, 304 | { "F3", Gdk.Key.F3 }, 305 | { "#4", Gdk.Key.F4 }, 306 | { "F4", Gdk.Key.F4 }, 307 | { "#5", Gdk.Key.F5 }, 308 | { "F5", Gdk.Key.F5 }, 309 | { "#6", Gdk.Key.F6 }, 310 | { "F6", Gdk.Key.F6 }, 311 | { "#7", Gdk.Key.F7 }, 312 | { "F7", Gdk.Key.F7 }, 313 | { "#8", Gdk.Key.F8 }, 314 | { "F8", Gdk.Key.F8 }, 315 | { "#9", Gdk.Key.F9 }, 316 | { "F9", Gdk.Key.F9 }, 317 | { "#0", Gdk.Key.F10 }, 318 | { "F10", Gdk.Key.F10 }, 319 | { "F11", Gdk.Key.F11 }, 320 | { "F12", Gdk.Key.F12 }, 321 | { "Insert", Gdk.Key.Insert }, 322 | { "Del", Gdk.Key.Delete }, 323 | { "Home", Gdk.Key.Home }, 324 | { "End", Gdk.Key.End }, 325 | { "PageUp", Gdk.Key.Page_Up }, 326 | { "PageDown", Gdk.Key.Page_Down }, 327 | { "kHome", Gdk.Key.KP_Home }, 328 | { "kEnd", Gdk.Key.KP_End }, 329 | { "kPageUp", Gdk.Key.KP_Page_Up }, 330 | { "kPageDown", Gdk.Key.KP_Page_Down }, 331 | { "kPlus", Gdk.Key.KP_Add }, 332 | { "kMinus", Gdk.Key.KP_Subtract }, 333 | { "kMultiply", Gdk.Key.KP_Multiply }, 334 | { "kDivide", Gdk.Key.KP_Divide }, 335 | { "kEnter", Gdk.Key.KP_Enter }, 336 | { "kPoint", Gdk.Key.KP_Decimal }, 337 | { "k0", Gdk.Key.KP_0 }, 338 | { "k1", Gdk.Key.KP_1 }, 339 | { "k2", Gdk.Key.KP_2 }, 340 | { "k3", Gdk.Key.KP_3 }, 341 | { "k4", Gdk.Key.KP_4 }, 342 | { "k5", Gdk.Key.KP_5 }, 343 | { "k6", Gdk.Key.KP_6 }, 344 | { "k7", Gdk.Key.KP_7 }, 345 | { "k8", Gdk.Key.KP_8 }, 346 | { "k9", Gdk.Key.KP_9 }, 347 | { "Help", Gdk.Key.Help }, 348 | { "Undo", Gdk.Key.Undo }, 349 | }; 350 | 351 | static ViKey FlattenControlMappings (ViKey k) 352 | { 353 | ViKey ret; 354 | if ((k.Modifiers & ModifierType.ControlMask) == k.Modifiers && 355 | controlMappings.TryGetValue (k.Char, out ret)) 356 | return ret; 357 | return k; 358 | } 359 | 360 | static Dictionary controlMappings = new Dictionary () { 361 | { '@', '\0' }, 362 | { 'h', Key.BackSpace }, 363 | { 'i', '\t' }, 364 | { 'j', '\n' }, 365 | { 'l', '\f' }, 366 | { 'm', '\r' }, 367 | { '[', Key.Escape }, 368 | { 'p', Key.Up }, 369 | }; 370 | 371 | public static IList Parse (string command) 372 | { 373 | var list = new List (); 374 | for (int i = 0; i < command.Length; i++) { 375 | if (command[i] == '<') { 376 | int j = command.IndexOf ('>', i); 377 | if (j < i + 2) 378 | throw new FormatException ("Could not find matching > at index " + i.ToString ()); 379 | string seq = command.Substring (i + 1, j - i - 1); 380 | list.Add (ParseKeySequence (seq)); 381 | i = j; 382 | } else { 383 | list.Add (command[i]); 384 | } 385 | } 386 | return list; 387 | } 388 | 389 | //TODO: 390 | static ViKey ParseKeySequence (string seq) 391 | { 392 | var modifiers = ModifierType.None; 393 | while (seq.Length > 2 && seq[1] == '-') { 394 | switch (seq[0]) { 395 | case 'S': 396 | modifiers |= ModifierType.ShiftMask; 397 | break; 398 | case 'C': 399 | modifiers |= ModifierType.ControlMask; 400 | break; 401 | case 'M': 402 | case 'A': 403 | modifiers |= ModifierType.Mod1Mask; 404 | break; 405 | case 'D': 406 | modifiers |= ModifierType.MetaMask; //HACK: Mac command key 407 | break; 408 | default: 409 | throw new FormatException ("Unknown modifier " + seq[0].ToString ()); 410 | } 411 | seq = seq.Substring (2); 412 | } 413 | var k = GetKey (seq); 414 | var c = '\0'; 415 | if (k == (Gdk.Key)0) 416 | c = GetChar (seq); 417 | 418 | var key = c == '\0'? new ViKey (modifiers, k) : new ViKey (modifiers, c); 419 | if (!IsValid (key)) 420 | throw new FormatException ("Invalid key sequence '" + seq + "'"); 421 | return key; 422 | } 423 | } 424 | } 425 | 426 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViActions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViActions.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (C) 2008 Novell, Inc (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | using System; 30 | using System.Collections.Generic; 31 | using System.Text; 32 | using Mono.TextEditor; 33 | 34 | //namespace Mono.TextEditor.Vi 35 | namespace VimAddin 36 | { 37 | public static class ViActions 38 | { 39 | public static void NextSubwordEnd (TextEditorData data) 40 | { 41 | data.Caret.Offset = ViWordFindStrategy.FindNextSubwordEndOffset (data.Document, data.Caret.Offset); 42 | } 43 | 44 | public static void NextSubwordEndPlus1 (TextEditorData data) 45 | { 46 | data.Caret.Offset = ViWordFindStrategy.FindNextSubwordEndOffset (data.Document, data.Caret.Offset) + 1; 47 | } 48 | 49 | public static void NextWordEnd (TextEditorData data) 50 | { 51 | data.Caret.Offset = ViWordFindStrategy.FindNextWordEndOffset (data.Document, data.Caret.Offset); 52 | } 53 | 54 | public static void NextWordEndPlus1 (TextEditorData data) 55 | { 56 | data.Caret.Offset = ViWordFindStrategy.FindNextWordEndOffset (data.Document, data.Caret.Offset) + 1; 57 | } 58 | 59 | public static void MoveToNextEmptyLine (TextEditorData data) 60 | { 61 | if (data.Caret.Line == data.Document.LineCount) { 62 | data.Caret.Offset = data.Document.TextLength; 63 | return; 64 | } 65 | 66 | int line = data.Caret.Line + 1; 67 | DocumentLine currentLine = data.Document.GetLine (line); 68 | while (line <= data.Document.LineCount) { 69 | line++; 70 | DocumentLine nextLine = data.Document.GetLine (line); 71 | if (currentLine.Length != 0 && nextLine.Length == 0) { 72 | data.Caret.Offset = nextLine.Offset; 73 | return; 74 | } 75 | currentLine = nextLine; 76 | } 77 | 78 | data.Caret.Offset = currentLine.Offset; 79 | } 80 | 81 | public static void MoveToPreviousEmptyLine (TextEditorData data) 82 | { 83 | if (data.Caret.Line == DocumentLocation.MinLine) { 84 | data.Caret.Offset = 0; 85 | return; 86 | } 87 | 88 | int line = data.Caret.Line - 1; 89 | DocumentLine currentLine = data.Document.GetLine (line); 90 | while (line > DocumentLocation.MinLine) { 91 | line--; 92 | DocumentLine previousLine = data.Document.GetLine (line); 93 | if (currentLine.Length != 0 && previousLine.Length == 0) { 94 | data.Caret.Offset = previousLine.Offset; 95 | return; 96 | } 97 | currentLine = previousLine; 98 | } 99 | 100 | data.Caret.Offset = currentLine.Offset; 101 | } 102 | 103 | public static void NewLineBelow (TextEditorData data) 104 | { 105 | DocumentLine currentLine = data.Document.GetLine (data.Caret.Line); 106 | data.Caret.Offset = currentLine.Offset + currentLine.Length; 107 | MiscActions.InsertNewLine (data); 108 | } 109 | 110 | public static void NewLineAbove (TextEditorData data) 111 | { 112 | if (data.Caret.Line == DocumentLocation.MinLine ) { 113 | data.Caret.Offset = 0; 114 | MiscActions.InsertNewLine (data); 115 | data.Caret.Offset = 0; 116 | return; 117 | } 118 | 119 | DocumentLine currentLine = data.Document.GetLine (data.Caret.Line - 1); 120 | data.Caret.Offset = currentLine.Offset + currentLine.Length; 121 | MiscActions.InsertNewLine (data); 122 | } 123 | 124 | public static void Join (TextEditorData data) 125 | { 126 | int startLine, endLine, startOffset, length; 127 | 128 | if (data.IsSomethingSelected) { 129 | startLine = data.Document.OffsetToLineNumber (data.SelectionRange.Offset); 130 | endLine = data.Document.OffsetToLineNumber (data.SelectionRange.EndOffset - 1); 131 | } else { 132 | startLine = endLine = data.Caret.Line; 133 | } 134 | 135 | //single-line joins 136 | if (endLine == startLine) 137 | endLine++; 138 | 139 | if (endLine > data.Document.LineCount) 140 | return; 141 | 142 | DocumentLine seg = data.Document.GetLine (startLine); 143 | startOffset = seg.Offset; 144 | StringBuilder sb = new StringBuilder (data.Document.GetTextAt (seg).TrimEnd ()); 145 | //lastSpaceOffset = startOffset + sb.Length; 146 | 147 | for (int i = startLine + 1; i <= endLine; i++) { 148 | seg = data.Document.GetLine (i); 149 | //lastSpaceOffset = startOffset + sb.Length; 150 | sb.Append (" "); 151 | sb.Append (data.Document.GetTextAt (seg).Trim ()); 152 | } 153 | length = (seg.Offset - startOffset) + seg.Length; 154 | // TODO: handle conversion issues ? 155 | data.Replace (startOffset, length, sb.ToString ()); 156 | } 157 | 158 | public static void ToggleCase (TextEditorData data) 159 | { 160 | if (data.IsSomethingSelected) { 161 | if (!data.CanEditSelection) 162 | return; 163 | 164 | StringBuilder sb = new StringBuilder (data.SelectedText); 165 | for (int i = 0; i < sb.Length; i++) { 166 | char ch = sb [i]; 167 | if (Char.IsLower (ch)) 168 | sb [i] = Char.ToUpper (ch); 169 | else if (Char.IsUpper (ch)) 170 | sb [i] = Char.ToLower (ch); 171 | } 172 | data.Replace (data.SelectionRange.Offset, data.SelectionRange.Length, sb.ToString ()); 173 | } else if (data.CanEdit (data.Caret.Line)) { 174 | char ch = data.Document.GetCharAt (data.Caret.Offset); 175 | if (Char.IsLower (ch)) 176 | ch = Char.ToUpper (ch); 177 | else if (Char.IsUpper (ch)) 178 | ch = Char.ToLower (ch); 179 | var caretOffset = data.Caret.Offset; 180 | int length = data.Replace (caretOffset, 1, new string (ch, 1)); 181 | DocumentLine seg = data.Document.GetLine (data.Caret.Line); 182 | if (data.Caret.Column < seg.Length) 183 | data.Caret.Offset = caretOffset + length; 184 | } 185 | } 186 | 187 | public static void Right (TextEditorData data) 188 | { 189 | DocumentLine segment = data.Document.GetLine (data.Caret.Line); 190 | if (segment.EndOffsetIncludingDelimiter-1 > data.Caret.Offset) { 191 | CaretMoveActions.Right (data); 192 | RetreatFromLineEnd (data); 193 | } 194 | } 195 | 196 | public static void Left (TextEditorData data) 197 | { 198 | if (DocumentLocation.MinColumn < data.Caret.Column) { 199 | CaretMoveActions.Left (data); 200 | } 201 | } 202 | 203 | public static void Down (TextEditorData data) 204 | { 205 | int desiredColumn = System.Math.Max (data.Caret.Column, data.Caret.DesiredColumn); 206 | 207 | CaretMoveActions.Down (data); 208 | RetreatFromLineEnd (data); 209 | 210 | data.Caret.DesiredColumn = desiredColumn; 211 | } 212 | 213 | public static void Up (TextEditorData data) 214 | { 215 | int desiredColumn = System.Math.Max (data.Caret.Column, data.Caret.DesiredColumn); 216 | 217 | CaretMoveActions.Up (data); 218 | RetreatFromLineEnd (data); 219 | 220 | data.Caret.DesiredColumn = desiredColumn; 221 | } 222 | 223 | public static void WordEnd (TextEditorData data) 224 | { 225 | data.Caret.Offset = data.FindCurrentWordEnd (data.Caret.Offset); 226 | } 227 | 228 | public static void WordStart (TextEditorData data) 229 | { 230 | data.Caret.Offset = data.FindCurrentWordStart (data.Caret.Offset); 231 | } 232 | 233 | public static void InnerWord (TextEditorData data) 234 | { 235 | var start = data.FindCurrentWordStart (data.Caret.Offset); 236 | var end = data.FindCurrentWordEnd (data.Caret.Offset); 237 | data.SelectionRange = new TextSegment(start, end - start); 238 | } 239 | 240 | private static readonly Dictionary EndToBeginCharMap = new Dictionary 241 | { 242 | {')', '('}, 243 | {'}', '{'}, 244 | {']', '['}, 245 | {'>', '<'}, 246 | }; 247 | private static readonly Dictionary BeginToEndCharMap = new Dictionary 248 | { 249 | {'(', ')'}, 250 | {'{', '}'}, 251 | {'[', ']'}, 252 | {'<', '>'}, 253 | }; 254 | 255 | public static Action OuterSymbol (char command) 256 | { 257 | return data => 258 | { 259 | SymbolBlock result; 260 | if (!TryFindSymbolBlock (data, command, out result)) return; 261 | 262 | data.SelectionRange = result.GetOuterTextSegment (); 263 | }; 264 | } 265 | 266 | public static Action InnerSymbol (char command) 267 | { 268 | return data => 269 | { 270 | SymbolBlock result; 271 | if (!TryFindSymbolBlock (data, command, out result)) return; 272 | 273 | data.SelectionRange = result.GetInnerTextSegment (); 274 | }; 275 | } 276 | 277 | struct SymbolBlock 278 | { 279 | public int StartOffset, EndOffset; 280 | public DocumentLine StartLine, EndLine; 281 | bool IsSameLine { get { return StartLine == EndLine; } } 282 | 283 | public TextSegment GetInnerTextSegment() 284 | { 285 | var length = IsSameLine ? EndOffset - StartOffset : EndLine.PreviousLine.EndOffset - StartOffset; 286 | return new TextSegment (StartOffset + 1, length - 1); 287 | } 288 | 289 | public TextSegment GetOuterTextSegment () 290 | { 291 | return new TextSegment (StartOffset, (EndOffset - StartOffset) + 1); 292 | } 293 | } 294 | 295 | static bool TryFindSymbolBlock (TextEditorData data, char command, out SymbolBlock result) 296 | { 297 | char end, begin; 298 | if (!BeginToEndCharMap.TryGetValue (command, out end)) end = command; 299 | if (!EndToBeginCharMap.TryGetValue (command, out begin)) begin = command; 300 | 301 | var offset = data.Caret.Offset; 302 | 303 | var startTokenOffset = ParseForChar(data, offset, 0, end, begin, false); 304 | var endTokenOffset = ParseForChar(data, offset, data.Length, begin, end, true); 305 | 306 | // Use the editor's FindMatchingBrace built-in functionality. It's better at handling erroneous braces 307 | // inside quotes. We still need to do the above paragraph because we needed to find the braces. 308 | var matchingStartBrace = endTokenOffset.HasValue ? data.Document.GetMatchingBracketOffset( 309 | endTokenOffset.GetValueOrDefault ()) : -1; 310 | if (matchingStartBrace >= 0 && (!startTokenOffset.HasValue 311 | || matchingStartBrace != startTokenOffset.GetValueOrDefault ())) 312 | startTokenOffset = matchingStartBrace; 313 | 314 | var matchingEndBrace = startTokenOffset.HasValue && data.GetCharAt (offset) != end ? 315 | data.Document.GetMatchingBracketOffset(startTokenOffset.GetValueOrDefault ()) : -1; 316 | if (matchingEndBrace >= 0 && (!endTokenOffset.HasValue 317 | || matchingEndBrace != endTokenOffset.GetValueOrDefault ())) 318 | endTokenOffset = matchingEndBrace; 319 | 320 | if (!startTokenOffset.HasValue || !endTokenOffset.HasValue) throw new ViModeAbortException(); 321 | 322 | result = new SymbolBlock 323 | { 324 | StartOffset = startTokenOffset.GetValueOrDefault (), 325 | EndOffset = endTokenOffset.GetValueOrDefault (), 326 | StartLine = data.GetLineByOffset (startTokenOffset.GetValueOrDefault()), 327 | EndLine = data.GetLineByOffset (endTokenOffset.GetValueOrDefault()), 328 | }; 329 | return true; 330 | } 331 | 332 | static int? ParseForChar(TextEditorData data, int fromOffset, int toOffset, char oppositeToken, char findToken, bool forward) 333 | { 334 | int increment = forward ? 1 : -1; 335 | var symbolCount = 0; 336 | for (int i = fromOffset; forward && i < toOffset || !forward && i >= toOffset; i += increment) 337 | { 338 | var c = data.GetCharAt(i); 339 | if (c == oppositeToken) 340 | symbolCount++; 341 | else if (c == findToken) 342 | { 343 | if (symbolCount == 0) return i; 344 | symbolCount--; 345 | } 346 | } 347 | return null; 348 | } 349 | 350 | public static Action InnerQuote (char c) 351 | { 352 | return data => 353 | { 354 | int beginOffset, length; 355 | if (TryFindInnerQuote (data, c, out beginOffset, out length)) 356 | data.SelectionRange = new TextSegment (beginOffset, length); 357 | }; 358 | } 359 | 360 | static bool TryFindInnerQuote (TextEditorData data, char c, out int begin, out int length) 361 | { 362 | begin = 0; 363 | length = 0; 364 | var currentOffset = data.Caret.Offset; 365 | var lineText = data.Document.GetLineText (data.Caret.Line); 366 | var line = data.Document.GetLine (data.Caret.Line); 367 | var lineOffset = currentOffset - line.Offset; 368 | 369 | var beginOffset = ParseForQuote (lineText, lineOffset - 1, c, false); 370 | if (!beginOffset.HasValue && lineText[lineOffset] == c) 371 | beginOffset = lineOffset; 372 | if (!beginOffset.HasValue) return false; 373 | var startEndSearchAt = beginOffset.GetValueOrDefault () == lineOffset ? lineOffset + 1 : lineOffset; 374 | var endOffset = ParseForQuote (lineText, startEndSearchAt, c, true); 375 | if (!endOffset.HasValue) return false; 376 | 377 | begin = beginOffset.GetValueOrDefault () + line.Offset + 1; 378 | length = endOffset.GetValueOrDefault () - beginOffset.GetValueOrDefault () - 1; 379 | return true; 380 | } 381 | 382 | public static Action OuterQuote (char c) 383 | { 384 | return data => 385 | { 386 | int beginOffset, length; 387 | if (TryFindInnerQuote (data, c, out beginOffset, out length)) 388 | { 389 | beginOffset--; 390 | length += 2; 391 | data.SelectionRange = new TextSegment (beginOffset, length); 392 | } 393 | }; 394 | } 395 | 396 | static int? ParseForQuote (string text, int start, char charToFind, bool forward) 397 | { 398 | int increment = forward ? 1 : -1; 399 | for (int i = start; forward && i < text.Length || !forward && i >= 0; i += increment) 400 | { 401 | if (text[i] == charToFind && 402 | (i < 1 || text[i-1] != '\\') && 403 | (i < 2 || text[i-2] != '\\')) 404 | return i; 405 | } 406 | return null; 407 | } 408 | 409 | public static void LineEnd (TextEditorData data) 410 | { 411 | int desiredColumn = System.Math.Max (data.Caret.Column, data.Caret.DesiredColumn); 412 | 413 | CaretMoveActions.LineEnd (data); 414 | RetreatFromLineEnd (data); 415 | 416 | data.Caret.DesiredColumn = desiredColumn; 417 | } 418 | 419 | internal static bool IsEol (char c) 420 | { 421 | return (c == '\r' || c == '\n'); 422 | } 423 | 424 | internal static void RetreatFromLineEnd (TextEditorData data) 425 | { 426 | if (data.Caret.Mode == CaretMode.Block && !data.IsSomethingSelected && !data.Caret.PreserveSelection) { 427 | while (DocumentLocation.MinColumn < data.Caret.Column && (data.Caret.Offset >= data.Document.TextLength 428 | || IsEol (data.Document.GetCharAt (data.Caret.Offset)))) { 429 | Left (data); 430 | } 431 | } 432 | } 433 | 434 | public static Action VisualSelectionFromMoveAction (Action moveAction) 435 | { 436 | return delegate (TextEditorData data) { 437 | //get info about the old selection state 438 | DocumentLocation oldCaret = data.Caret.Location, oldAnchor = oldCaret, oldLead = oldCaret; 439 | if (data.IsSomethingSelected) { 440 | oldLead = data.MainSelection.Lead; 441 | oldAnchor = data.MainSelection.Anchor; 442 | } 443 | 444 | //do the action, preserving selection 445 | SelectionActions.StartSelection (data); 446 | moveAction (data); 447 | SelectionActions.EndSelection (data); 448 | 449 | DocumentLocation newCaret = data.Caret.Location, newAnchor = newCaret, newLead = newCaret; 450 | if (data.IsSomethingSelected) { 451 | newLead = data.MainSelection.Lead; 452 | newAnchor = data.MainSelection.Anchor; 453 | } 454 | 455 | //Console.WriteLine ("oc{0}:{1} oa{2}:{3} ol{4}:{5}", oldCaret.Line, oldCaret.Column, oldAnchor.Line, oldAnchor.Column, oldLead.Line, oldLead.Column); 456 | //Console.WriteLine ("nc{0}:{1} na{2}:{3} nl{4}:{5}", newCaret.Line, newCaret.Line, newAnchor.Line, newAnchor.Column, newLead.Line, newLead.Column); 457 | 458 | //pivot the anchor around the anchor character 459 | if (oldAnchor < oldLead && newAnchor >= newLead) { 460 | data.SetSelection (new DocumentLocation (newAnchor.Line, newAnchor.Column + 1), newLead); 461 | } else if (oldAnchor > oldLead && newAnchor <= newLead) { 462 | data.SetSelection (new DocumentLocation (newAnchor.Line, newAnchor.Column - 1), newLead); 463 | } 464 | 465 | //pivot the lead about the anchor character 466 | if (newAnchor == newLead) { 467 | if (oldAnchor < oldLead) 468 | SelectionActions.FromMoveAction (Left) (data); 469 | else 470 | SelectionActions.FromMoveAction (Right) (data); 471 | } 472 | //pivot around the anchor line 473 | else { 474 | if (oldAnchor < oldLead && newAnchor > newLead && ( 475 | (newLead.Line == newAnchor.Line && oldLead.Line == oldAnchor.Line + 1) || 476 | (newLead.Line == newAnchor.Line - 1 && oldLead.Line == oldAnchor.Line))) 477 | SelectionActions.FromMoveAction (Left) (data); 478 | else if (oldAnchor > oldLead && newAnchor < newLead && ( 479 | (newLead.Line == newAnchor.Line && oldLead.Line == oldAnchor.Line - 1) || 480 | (newLead.Line == newAnchor.Line + 1 && oldLead.Line == oldAnchor.Line))) 481 | SelectionActions.FromMoveAction (Right) (data); 482 | } 483 | }; 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /VimAddin/Mono.TextEditor.Vi/ViMode.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ViMode.cs 3 | // 4 | // Author: 5 | // Michael Hutchinson 6 | // 7 | // Copyright (C) 2008 Novell, Inc (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | using System; 30 | using System.Text; 31 | using System.Text.RegularExpressions; 32 | using System.Collections.Generic; 33 | using System.Linq; 34 | using Mono.TextEditor; 35 | using MonoDevelop.Ide.CodeFormatting; 36 | namespace VimAddin 37 | { 38 | /** 39 | Represent a keystroke that produces input in the text document e.g. characters, newlines, tabs. 40 | */ 41 | public struct Keystroke 42 | { 43 | private Gdk.Key m_key; 44 | public Gdk.Key Key { get { return m_key; } } 45 | private uint m_unicodeKey; 46 | public uint UnicodeKey { get { return m_unicodeKey; } } 47 | private Gdk.ModifierType m_modifier; 48 | public Gdk.ModifierType Modifier { get { return m_modifier; } } 49 | public Keystroke(Gdk.Key key, uint unicodeKey, Gdk.ModifierType modifier) 50 | { 51 | m_key = key; 52 | m_unicodeKey = unicodeKey; 53 | m_modifier = modifier; 54 | } 55 | } 56 | 57 | public class ViEditMode : Mono.TextEditor.EditMode 58 | { 59 | // State variable indicates that we have started recording actions into LastAction 60 | bool lastActionStarted; 61 | bool searchBackward; 62 | static string lastPattern; 63 | static string lastReplacement; 64 | State curState; 65 | protected State CurState { 66 | get { 67 | return curState; 68 | } 69 | set { 70 | curState = value; 71 | if (statusArea != null) { 72 | statusArea.ShowCaret = curState == State.Command; 73 | } 74 | } 75 | } 76 | protected State PrevState { get; set; } 77 | 78 | Motion motion; 79 | const string substMatch = @"^:s(?.)(?.+?)\k(?.*?)(\k(?[gi]+?))?$"; 80 | const string SpecialMarks = "`"; 81 | StringBuilder commandBuffer = new StringBuilder (); 82 | protected Dictionary marks = new Dictionary(); 83 | Dictionary macros = new Dictionary(); 84 | char macros_lastplayed = '@'; // start with the illegal macro character 85 | string statusText = ""; 86 | List> LastAction = new List>(); 87 | // Lines, RightOffsetOnLastLine 88 | Tuple LastSelectionRange = new Tuple(0,0); 89 | 90 | /** 91 | Cache keystroke sequence from last insert operation. 92 | */ 93 | List LastInsert = new List(); 94 | 95 | /** 96 | Cache keystrokes during insert operation that produce insert keys. 97 | */ 98 | List InsertBuffer = new List(); 99 | 100 | /// 101 | /// Number of times to perform the next action 102 | /// For example 3 is the numeric prefix when "3w" is entered 103 | /// 104 | string numericPrefix = ""; 105 | 106 | /// 107 | /// Number of times to perform the next action 108 | /// 109 | int repeatCount { 110 | get { 111 | int n; 112 | int.TryParse (numericPrefix, out n); 113 | return n < 1 ? 1 : n; 114 | } 115 | set { 116 | numericPrefix = value.ToString (); 117 | } 118 | } 119 | 120 | /// 121 | /// Whether ViEditMode is in a state where it should accept a numeric prefix 122 | /// 123 | bool AcceptNumericPrefix { 124 | get { 125 | return CurState == State.Normal || CurState == State.Delete || CurState == State.Change 126 | || CurState == State.Yank || CurState == State.Indent || CurState == State.Unindent; 127 | } 128 | } 129 | /// The macro currently being implemented. Will be set to null and checked as a flag when required. 130 | /// 131 | ViMacro currentMacro; 132 | 133 | public virtual string Status { 134 | 135 | get { 136 | return statusArea.Message; 137 | } 138 | 139 | protected set { 140 | if (currentMacro != null) { 141 | value = value + " recording"; 142 | } 143 | if (viTextEditor != null) { 144 | statusArea.Message = value; 145 | } 146 | } 147 | } 148 | 149 | protected MonoDevelop.Ide.Gui.Document saveableDocument; 150 | 151 | public ViEditMode( MonoDevelop.Ide.Gui.Document doc ) 152 | { 153 | saveableDocument = doc; 154 | } 155 | 156 | public bool HasData( ) 157 | { 158 | return Data != null; 159 | } 160 | 161 | public bool HasTextEditorData( ) 162 | { 163 | return textEditorData != null; 164 | } 165 | 166 | protected void FormatSelection( ) 167 | { 168 | var doc = saveableDocument; 169 | var formatter = CodeFormatterService.GetFormatter (Document.MimeType); 170 | if (formatter == null) 171 | return; 172 | 173 | TextSegment selection; 174 | var editor = Data; 175 | if (editor.IsSomethingSelected) { 176 | selection = editor.SelectionRange; 177 | } else { 178 | selection = editor.GetLine (editor.Caret.Line).Segment; 179 | } 180 | 181 | using (var undo = editor.OpenUndoGroup ()) { 182 | var version = editor.Version; 183 | 184 | if (formatter.SupportsOnTheFlyFormatting) { 185 | formatter.OnTheFlyFormat (doc, selection.Offset, selection.EndOffset); 186 | } else { 187 | var pol = doc.Project != null ? doc.Project.Policies : null; 188 | try { 189 | string text = formatter.FormatText (pol, editor.Text, selection.Offset, selection.EndOffset); 190 | if (text != null) { 191 | editor.Replace (selection.Offset, selection.Length, text); 192 | } 193 | } catch (Exception e) { 194 | Console.WriteLine ("Error during format."); 195 | } 196 | } 197 | 198 | if (editor.IsSomethingSelected) { 199 | int newOffset = version.MoveOffsetTo (editor.Version, selection.Offset); 200 | int newEndOffset = version.MoveOffsetTo (editor.Version, selection.EndOffset); 201 | editor.SetSelection (newOffset, newEndOffset); 202 | } 203 | } 204 | } 205 | 206 | protected virtual string RunExCommand (string command) 207 | { 208 | // by default, return to normal state after executing a command 209 | CurState = State.Normal; 210 | 211 | switch (command [0]) { 212 | case ':': 213 | if (2 > command.Length) 214 | break; 215 | 216 | int line; 217 | if (int.TryParse (command.Substring (1), out line)) { 218 | if (line < DocumentLocation.MinLine || line > Data.Document.LineCount) { 219 | return "Invalid line number."; 220 | } else if (line == 0) { 221 | RunAction (CaretMoveActions.ToDocumentStart); 222 | return "Jumped to beginning of document."; 223 | } 224 | 225 | Data.Caret.Line = line; 226 | Editor.ScrollToCaret (); 227 | return string.Format ("Jumped to line {0}.", line); 228 | } 229 | 230 | switch (command [1]) { 231 | case 's': 232 | if (2 == command.Length) { 233 | if (null == lastPattern || null == lastReplacement) 234 | return "No stored pattern."; 235 | 236 | // Perform replacement with stored stuff 237 | command = string.Format (":s/{0}/{1}/", lastPattern, lastReplacement); 238 | } 239 | 240 | var match = Regex.Match (command, substMatch, RegexOptions.Compiled); 241 | if (!(match.Success && match.Groups ["pattern"].Success && match.Groups ["replacement"].Success)) 242 | break; 243 | 244 | return RegexReplace (match); 245 | 246 | case '$': 247 | if (command.Length == 2) { 248 | //RunAction (CaretMoveActions.ToDocumentEnd); 249 | RunAction (ToDocumentEnd); 250 | return "Jumped to end of document."; 251 | } 252 | break; 253 | } 254 | if (command.Substring (1) == "test") { 255 | CurState = State.Confirm; 256 | var selection = Data.SelectionRange; 257 | int lineStart, lineEnd; 258 | int lookaheadOffset = Math.Min (Caret.Offset + 1000, Data.Document.TextLength); 259 | MiscActions.GetSelectedLines (Data, out lineStart, out lineEnd); 260 | int roffset = Data.SelectionRange.Offset; 261 | int linesAhead = Math.Min(LastSelectionRange.Item1, Document.LineCount - Caret.Line); 262 | // CaretMoveActions.LineStart(Data); 263 | SelectionActions.StartSelection (Data); 264 | if (linesAhead > 1) { 265 | for (int i = 0; i < linesAhead; ++i) { 266 | ViActions.Down (Data); 267 | } 268 | CaretMoveActions.LineStart (Data); 269 | } 270 | 271 | DocumentLine lastLine = Document.GetLineByOffset (Caret.Offset); 272 | int lastLineOffset = Math.Min (LastSelectionRange.Item2, lastLine.EndOffset); 273 | for (int i = 0; i < lastLineOffset; ++i) { 274 | ViActions.Right (Data); 275 | } 276 | SelectionActions.EndSelection (Data); 277 | // SelectionActions.StartSelection (Data); 278 | // CaretMoveActions.LineFirstNonWhitespace (Data); 279 | // ViActions.Down (Data); 280 | // ViActions.Right (Data); 281 | // ViActions.Right (Data); 282 | // ViActions.Right (Data); 283 | // ViActions.Right (Data); 284 | // ViActions.Right (Data); 285 | // SelectionActions.EndSelection (Data); 286 | 287 | //return string.Format("line range: {0} {1}", lineStart, lineEnd); 288 | return string.Format("s:{0} {1}; ({2},{3}) ({4}) ({5})", 289 | selection.Offset, 290 | selection.Length, 291 | LastSelectionRange.Item1, 292 | LastSelectionRange.Item2, 293 | linesAhead, 294 | Data.Document.TextLength); 295 | } 296 | break; 297 | // case ':' 298 | 299 | case '?': 300 | case '/': 301 | searchBackward = ('?' == command[0]); 302 | if (1 < command.Length) { 303 | Editor.HighlightSearchPattern = true; 304 | Editor.SearchEngine = new RegexSearchEngine (); 305 | var pattern = command.Substring (1); 306 | Editor.SearchPattern = pattern; 307 | var caseSensitive = pattern.ToCharArray ().Any (c => char.IsUpper (c)); 308 | Editor.SearchEngine.SearchRequest.CaseSensitive = caseSensitive; 309 | } 310 | return Search (); 311 | } 312 | 313 | return "Command not recognised"; 314 | } 315 | 316 | void SearchWordAtCaret (bool backward) 317 | { 318 | Editor.SearchEngine = new RegexSearchEngine (); 319 | var s = Data.FindCurrentWordStart (Data.Caret.Offset); 320 | var e = Data.FindCurrentWordEnd (Data.Caret.Offset); 321 | if (s < 0 || e <= s) 322 | return; 323 | 324 | var word = Document.GetTextBetween (s, e); 325 | //use negative lookahead and lookbehind for word characters to make sure we only get fully matching words 326 | word = "(? action in LastAction) { 336 | RunAction (action); 337 | } 338 | } 339 | 340 | /** 341 | Indent specified number of lines from the current caret position. 342 | */ 343 | protected Action MakeIndentAction(int lines) 344 | { 345 | var linesToIndent = lines; 346 | Action indentAction = delegate(TextEditorData data) { 347 | // programmatically generate a text selection 348 | List> actions = new List> (); 349 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 350 | int roffset = Data.SelectionRange.Offset; 351 | actions.Add (SelectionActions.FromMoveAction (CaretMoveActions.LineEnd)); 352 | for (int i = 1; i < linesToIndent; i++) { 353 | actions.Add (SelectionActions.FromMoveAction (ViActions.Down)); 354 | } 355 | // then indent it 356 | actions.Add (MiscActions.IndentSelection); 357 | RunActions (actions.ToArray ()); 358 | //set cursor to start of first line indented 359 | Caret.Offset = roffset; 360 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 361 | 362 | Reset (""); 363 | }; 364 | return indentAction; 365 | } 366 | 367 | /** 368 | Apply an action to a selection of the same size we selected previously. 369 | */ 370 | protected Action MakeActionToApplyToLastSelection(Action actionToApply) 371 | { 372 | // LastSelectionRange is set by SaveSelectionRange 373 | var lastLinesAhead = LastSelectionRange.Item1; 374 | var lastLastLineOffset = LastSelectionRange.Item2; 375 | Action action = delegate(TextEditorData data) { 376 | // generate a selection from last selection range 377 | SelectionActions.StartSelection(data); 378 | int linesAhead = Math.Min(lastLinesAhead, data.Document.LineCount - data.Caret.Line); 379 | SelectionActions.StartSelection (data); 380 | if (linesAhead > 1) { 381 | for (int i = 0; i < linesAhead; ++i) { 382 | ViActions.Down (data); 383 | } 384 | CaretMoveActions.LineStart (data); 385 | } 386 | DocumentLine lastLine = Document.GetLineByOffset (data.Caret.Offset); 387 | int lastLineOffset = Math.Min (lastLastLineOffset, lastLine.EndOffset); 388 | for (int i = 0; i < lastLineOffset; ++i) { 389 | ViActions.Right (data); 390 | } 391 | SelectionActions.EndSelection (data); 392 | 393 | // apply the action 394 | RunAction(actionToApply); 395 | }; 396 | return action; 397 | } 398 | 399 | /** 400 | Make an action that does action1 and then action2. 401 | */ 402 | protected Action MakeActionPair(Action action1, 403 | Action action2) 404 | { 405 | Action action = delegate(TextEditorData data) { 406 | RunAction(action1); 407 | RunAction(action2); 408 | }; 409 | return action; 410 | } 411 | 412 | /** 413 | An action that saves the current caret position in the special mark `. 414 | */ 415 | protected void SaveContextMark(TextEditorData data) 416 | { 417 | RunAction (marks ['`'].SaveMark); 418 | } 419 | 420 | protected void SaveSelectionRange(TextEditorData data) 421 | { 422 | int lineStart, lineEnd; 423 | MiscActions.GetSelectedLines (data, out lineStart, out lineEnd); 424 | int lines = lineEnd - lineStart + 1; 425 | int offset = 0; 426 | if (lines > 1) { 427 | var lastLine = data.Document.GetLineByOffset (data.SelectionRange.EndOffset); 428 | offset = data.SelectionRange.EndOffset - lastLine.Offset; 429 | } else { 430 | offset = data.SelectionRange.Length; 431 | } 432 | LastSelectionRange = new Tuple (lines, offset); 433 | } 434 | 435 | /** 436 | Wrapper for ViActionMaps.GetNavCharAction. We catch and modify actions as necessary. 437 | */ 438 | protected Action GetNavCharAction(char unicodeKey, bool isCommand) 439 | { 440 | Action action = ViActionMaps.GetNavCharAction (unicodeKey, isCommand); 441 | if (action == MiscActions.GotoMatchingBracket) { 442 | action = MakeActionPair (SaveContextMark, action); 443 | } 444 | return action; 445 | } 446 | 447 | /** 448 | Unindent specified number of lines from the current caret position. 449 | */ 450 | public Action MakeUnindentAction(int lines) 451 | { 452 | var linesToUnindent = lines; 453 | Action unindentAction = delegate(TextEditorData data) { 454 | List> actions = new List> (); 455 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 456 | int roffset = Data.SelectionRange.Offset; //save caret position 457 | actions.Add (SelectionActions.FromMoveAction (CaretMoveActions.LineEnd)); 458 | for (int i = 1; i < linesToUnindent; i++) { 459 | actions.Add (SelectionActions.FromMoveAction (ViActions.Down)); 460 | } 461 | actions.Add (MiscActions.RemoveIndentSelection); 462 | RunActions (actions.ToArray ()); 463 | //set cursor to start of first line indented 464 | Caret.Offset = roffset; 465 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 466 | 467 | Reset (""); 468 | }; 469 | return unindentAction; 470 | } 471 | 472 | /** 473 | Action to go to the document end. This replaces CaretMoveActions.DocumentEnd, which puts us in a dicey spot. 474 | */ 475 | protected void ToDocumentEnd(TextEditorData data) 476 | { 477 | 478 | if (data.Document.LineCount > 1) { 479 | data.Caret.Line = data.Document.LineCount - 1; 480 | } 481 | } 482 | 483 | public override bool WantsToPreemptIM { 484 | get { 485 | return CurState != State.Insert && CurState != State.Replace; 486 | } 487 | } 488 | 489 | protected override void SelectionChanged () 490 | { 491 | if (Data.IsSomethingSelected) { 492 | CurState = ViEditMode.State.Visual; 493 | Status = "-- VISUAL --"; 494 | } else if (CurState == State.Visual && !Data.IsSomethingSelected) { 495 | Reset (""); 496 | } 497 | } 498 | 499 | protected override void CaretPositionChanged () 500 | { 501 | if (CurState == State.Replace || CurState == State.Insert || CurState == State.Visual) 502 | return; 503 | else if (CurState == ViEditMode.State.Normal || CurState == ViEditMode.State.Unknown) 504 | ViActions.RetreatFromLineEnd (Data); 505 | else 506 | Reset (""); 507 | } 508 | 509 | ViStatusArea statusArea; 510 | TextEditor viTextEditor; 511 | 512 | void CheckVisualMode () 513 | { 514 | if (Data == null) { 515 | return; 516 | } 517 | if (CurState == ViEditMode.State.Visual || CurState == ViEditMode.State.Visual) { 518 | if (!Data.IsSomethingSelected) 519 | CurState = ViEditMode.State.Normal; 520 | } else { 521 | if (Data.IsSomethingSelected) { 522 | CurState = ViEditMode.State.Visual; 523 | Status = "-- VISUAL --"; 524 | } 525 | } 526 | } 527 | 528 | void ResetEditorState (TextEditorData data) 529 | { 530 | if (data == null) 531 | return; 532 | data.ClearSelection (); 533 | 534 | //Editor can be null during GUI-less tests 535 | // Commenting this fixes bug: Bug 622618 - Inline search fails in vi mode 536 | // if (Editor != null) 537 | // Editor.HighlightSearchPattern = false; 538 | 539 | if (CaretMode.Block != data.Caret.Mode) { 540 | data.Caret.Mode = CaretMode.Block; 541 | if (data.Caret.Column > DocumentLocation.MinColumn) 542 | data.Caret.Column--; 543 | } 544 | ViActions.RetreatFromLineEnd (data); 545 | } 546 | 547 | protected override void OnAddedToEditor (TextEditorData data) 548 | { 549 | data.Caret.Mode = CaretMode.Block; 550 | ViActions.RetreatFromLineEnd (data); 551 | 552 | viTextEditor = data.Parent; 553 | if (viTextEditor != null) { 554 | statusArea = new ViStatusArea (viTextEditor); 555 | } 556 | } 557 | 558 | protected override void OnRemovedFromEditor (TextEditorData data) 559 | { 560 | data.Caret.Mode = CaretMode.Insert; 561 | 562 | if (viTextEditor != null) { 563 | statusArea.RemoveFromParentAndDestroy (); 564 | statusArea = null; 565 | viTextEditor = null; 566 | } 567 | } 568 | 569 | public override void AllocateTextArea (TextEditor textEditor, TextArea textArea, Gdk.Rectangle allocation) 570 | { 571 | statusArea.AllocateArea (textArea, allocation); 572 | } 573 | 574 | void Reset (string status) 575 | { 576 | lastActionStarted = false; 577 | PrevState = State.Normal; 578 | CurState = State.Normal; 579 | ResetEditorState (Data); 580 | 581 | commandBuffer.Length = 0; 582 | Status = status; 583 | 584 | numericPrefix = ""; 585 | } 586 | 587 | protected virtual Action GetInsertAction (Gdk.Key key, Gdk.ModifierType modifier) 588 | { 589 | return ViActionMaps.GetInsertKeyAction (key, modifier) ?? 590 | ViActionMaps.GetDirectionKeyAction (key, modifier); 591 | } 592 | 593 | private void StartNewLastAction(Action action) 594 | { 595 | lastActionStarted = true; 596 | LastAction.Clear (); 597 | LastAction.Add (action); 598 | } 599 | 600 | /// Run an action multiple times if it was preceded by a numeric key 601 | /// Resets numeric prefixs 602 | /// 603 | private void RunRepeatableAction (Action action) 604 | { 605 | // if (Data != null) { 606 | // Console.WriteLine ("RunRepeatableAction entering with Data not null"); 607 | // } 608 | int reps = repeatCount; //how many times to repeat command 609 | for (int i = 0; i < reps; i++) { 610 | RunAction (action); 611 | } 612 | // if (Data == null) { 613 | // Console.WriteLine ("RunRepeatableAction leaving Data null"); 614 | // } 615 | numericPrefix = ""; 616 | } 617 | 618 | /// 619 | /// Run the first action multiple times if it was preceded by a numeric key 620 | /// Run the following actions once each 621 | /// 622 | private void RunRepeatableActionChain (params Action[] actions) 623 | { 624 | List> actionList = new List> (); 625 | int reps = repeatCount; //how many times to repeat command 626 | for (int i = 0; i < reps; i++) { 627 | actionList.Add (actions [0]); 628 | } 629 | for (int i = 1; i < actions.Length; i++) { 630 | actionList.Add (actions [i]); 631 | } 632 | RunActions (actionList.ToArray ()); 633 | numericPrefix = ""; 634 | } 635 | 636 | /// 637 | /// Repeat entire set of actions based on preceding numeric key 638 | /// The first action indicates the movement that initiates the line action 639 | /// The second action indicates the action to be taken on the line 640 | /// The third action indicates the action to reset after completing the action on that line 641 | /// 642 | private List> GenerateRepeatedActionList ( 643 | params Action[] actions) 644 | { 645 | List> actionList = new List> (); 646 | 647 | int reps = repeatCount; //how many times to repeat command 648 | 649 | for (int i = 0; i < reps; i++) { 650 | actionList.AddRange (actions); 651 | } 652 | numericPrefix = ""; 653 | return actionList; 654 | } 655 | 656 | private void RepeatLastInsert(TextEditorData data) 657 | { 658 | CurState = State.Insert; 659 | Caret.Mode = CaretMode.Insert; 660 | foreach (Keystroke ks in LastInsert) { 661 | // TODO: Handle full keystroke 662 | Action action = ViActionMaps.GetInsertKeyAction (ks.Key, ks.Modifier); 663 | if (action != null) { 664 | RunAction (action); 665 | } else { 666 | InsertCharacter (ks.UnicodeKey); 667 | } 668 | } 669 | Caret.Mode = CaretMode.Block; 670 | Reset (""); 671 | } 672 | 673 | protected override void HandleKeypress (Gdk.Key key, uint unicodeKey, Gdk.ModifierType modifier) 674 | { 675 | // if (Data != null) { 676 | // Console.WriteLine ("Data is not null at start of ViMode.HandleKeypress"); 677 | // Console.WriteLine (CurState); 678 | // Console.WriteLine ("unicodeKey: {0}", (char)unicodeKey); 679 | // } 680 | 681 | // Reset on Esc, Ctrl-C, Ctrl-[ 682 | if (key == Gdk.Key.Escape) { 683 | if (currentMacro != null) { 684 | // Record Escapes into the macro since it actually does something 685 | ViMacro.KeySet toAdd = new ViMacro.KeySet(); 686 | toAdd.Key = key; 687 | toAdd.Modifiers = modifier; 688 | toAdd.UnicodeKey = unicodeKey; 689 | currentMacro.KeysPressed.Enqueue(toAdd); 690 | } 691 | 692 | if (PrevState == State.Change && CurState == State.Insert) { 693 | LastInsert.Clear (); 694 | LastInsert.AddRange(InsertBuffer); 695 | InsertBuffer.Clear (); 696 | LastAction.Add (RepeatLastInsert); 697 | } else if (CurState == State.Insert) { 698 | LastInsert.Clear (); 699 | LastInsert.AddRange(InsertBuffer); 700 | InsertBuffer.Clear (); 701 | if (!lastActionStarted) { 702 | lastActionStarted = true; 703 | LastAction.Clear (); 704 | } 705 | LastAction.Add (RepeatLastInsert); 706 | } 707 | Reset(string.Empty); 708 | return; 709 | } else if (((key == Gdk.Key.c || key == Gdk.Key.bracketleft) && (modifier & Gdk.ModifierType.ControlMask) != 0)) { 710 | Reset (string.Empty); 711 | if (currentMacro != null) { 712 | // Otherwise remove the macro from the pool 713 | macros.Remove(currentMacro.MacroCharacter); 714 | currentMacro = null; 715 | } 716 | return; 717 | } else if (currentMacro != null && !((char)unicodeKey == 'q' && modifier == Gdk.ModifierType.None)) { 718 | ViMacro.KeySet toAdd = new ViMacro.KeySet(); 719 | toAdd.Key = key; 720 | toAdd.Modifiers = modifier; 721 | toAdd.UnicodeKey = unicodeKey; 722 | currentMacro.KeysPressed.Enqueue(toAdd); 723 | } 724 | 725 | Action action = null; 726 | bool lineAction = false; 727 | 728 | //handle numeric keypress 729 | if (AcceptNumericPrefix && '0' <= (char)unicodeKey && (char)unicodeKey <= '9') { 730 | numericPrefix += (char)unicodeKey; 731 | return; 732 | } 733 | 734 | switch (CurState) { 735 | case State.Unknown: 736 | Reset (string.Empty); 737 | goto case State.Normal; 738 | case State.Normal: 739 | if (((modifier & (Gdk.ModifierType.ControlMask)) == 0)) { 740 | if (key == Gdk.Key.Delete) 741 | unicodeKey = 'x'; 742 | switch ((char)unicodeKey) { 743 | case '?': 744 | case '/': 745 | case ':': 746 | CurState = State.Command; 747 | commandBuffer.Append ((char)unicodeKey); 748 | Status = commandBuffer.ToString (); 749 | return; 750 | 751 | case 'A': 752 | StartNewLastAction (CaretMoveActions.LineEnd); 753 | RunAction (CaretMoveActions.LineEnd); 754 | goto case 'i'; 755 | 756 | case 'I': 757 | StartNewLastAction (CaretMoveActions.LineFirstNonWhitespace); 758 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 759 | goto case 'i'; 760 | 761 | case 'a': 762 | StartNewLastAction (CaretMoveActions.Right); 763 | //use CaretMoveActions so that we can move past last character on line end 764 | RunAction (CaretMoveActions.Right); 765 | goto case 'i'; 766 | 767 | case 'i': 768 | Caret.Mode = CaretMode.Insert; 769 | Status = "-- INSERT --"; 770 | CurState = State.Insert; 771 | return; 772 | 773 | case 'R': 774 | Caret.Mode = CaretMode.Underscore; 775 | Status = "-- REPLACE --"; 776 | CurState = State.Replace; 777 | return; 778 | 779 | case 'V': 780 | Status = "-- VISUAL LINE --"; 781 | Data.SetSelectLines (Caret.Line, Caret.Line); 782 | CurState = State.VisualLine; 783 | return; 784 | 785 | case 'v': 786 | Status = "-- VISUAL --"; 787 | CurState = State.Visual; 788 | RunAction (ViActions.VisualSelectionFromMoveAction (ViActions.Right)); 789 | return; 790 | 791 | case 'd': 792 | Status = "d"; 793 | CurState = State.Delete; 794 | return; 795 | 796 | case 'y': 797 | Status = "y"; 798 | CurState = State.Yank; 799 | return; 800 | 801 | case 'Y': 802 | CurState = State.Yank; 803 | HandleKeypress (Gdk.Key.y, (int)'y', Gdk.ModifierType.None); 804 | return; 805 | 806 | case 'O': 807 | StartNewLastAction (ViActions.NewLineAbove); 808 | RunAction (ViActions.NewLineAbove); 809 | goto case 'i'; 810 | 811 | case 'o': 812 | StartNewLastAction (ViActions.NewLineBelow); 813 | RunAction (ViActions.NewLineBelow); 814 | goto case 'i'; 815 | 816 | case 'r': 817 | Caret.Mode = CaretMode.Underscore; 818 | Status = "-- REPLACE --"; 819 | CurState = State.WriteChar; 820 | return; 821 | 822 | case 'c': 823 | Caret.Mode = CaretMode.Insert; 824 | Status = "c"; 825 | CurState = State.Change; 826 | return; 827 | 828 | case 'x': 829 | if (Data.Caret.Column == Data.Document.GetLine (Data.Caret.Line).Length + 1) 830 | return; 831 | Status = string.Empty; 832 | if (!Data.IsSomethingSelected) 833 | RunActions (SelectionActions.FromMoveAction (CaretMoveActions.Right), ClipboardActions.Cut); 834 | else 835 | RunAction (ClipboardActions.Cut); 836 | ViActions.RetreatFromLineEnd (Data); 837 | return; 838 | 839 | case 'X': 840 | if (Data.Caret.Column == DocumentLocation.MinColumn) 841 | return; 842 | Status = string.Empty; 843 | if (!Data.IsSomethingSelected && 0 < Caret.Offset) 844 | RunActions (SelectionActions.FromMoveAction (CaretMoveActions.Left), ClipboardActions.Cut); 845 | else 846 | RunAction (ClipboardActions.Cut); 847 | return; 848 | 849 | case 'D': 850 | RunActions (SelectionActions.FromMoveAction (CaretMoveActions.LineEnd), ClipboardActions.Cut); 851 | return; 852 | 853 | case 'C': 854 | RunActions (SelectionActions.FromMoveAction (CaretMoveActions.LineEnd), ClipboardActions.Cut); 855 | goto case 'i'; 856 | 857 | case '>': 858 | Status = ">"; 859 | CurState = State.Indent; 860 | return; 861 | 862 | case '<': 863 | Status = "<"; 864 | CurState = State.Unindent; 865 | return; 866 | case 'n': 867 | Search (); 868 | return; 869 | case 'N': 870 | searchBackward = !searchBackward; 871 | Search (); 872 | searchBackward = !searchBackward; 873 | return; 874 | case 'p': 875 | PasteAfter (false); 876 | return; 877 | case 'P': 878 | PasteBefore (false); 879 | return; 880 | case 's': 881 | if (!Data.IsSomethingSelected) 882 | RunAction (SelectionActions.FromMoveAction (CaretMoveActions.Right)); 883 | RunAction (ClipboardActions.Cut); 884 | goto case 'i'; 885 | case 'S': 886 | if (!Data.IsSomethingSelected) 887 | RunAction (SelectionActions.LineActionFromMoveAction (CaretMoveActions.LineEnd)); 888 | else 889 | Data.SetSelectLines (Data.MainSelection.Anchor.Line, Data.Caret.Line); 890 | RunAction (ClipboardActions.Cut); 891 | goto case 'i'; 892 | 893 | case 'g': 894 | Status = "g"; 895 | CurState = State.G; 896 | return; 897 | 898 | case 'G': 899 | RunAction (marks ['`'].SaveMark); 900 | if (repeatCount > 1) { 901 | Caret.Line = repeatCount; 902 | } else { 903 | //RunAction (CaretMoveActions.ToDocumentEnd); 904 | RunAction (ToDocumentEnd); 905 | } 906 | Reset (string.Empty); 907 | return; 908 | 909 | case 'H': 910 | Caret.Line = System.Math.Max (DocumentLocation.MinLine, Editor.PointToLocation (0, Editor.LineHeight - 1).Line); 911 | return; 912 | case 'J': 913 | RunAction (ViActions.Join); 914 | return; 915 | case 'L': 916 | int line = Editor.PointToLocation (0, Editor.Allocation.Height - Editor.LineHeight * 2 - 2).Line; 917 | if (line < DocumentLocation.MinLine) 918 | line = Document.LineCount; 919 | Caret.Line = line; 920 | return; 921 | case 'M': 922 | line = Editor.PointToLocation (0, Editor.Allocation.Height / 2).Line; 923 | if (line < DocumentLocation.MinLine) 924 | line = Document.LineCount; 925 | Caret.Line = line; 926 | return; 927 | 928 | case '~': 929 | RunAction (ViActions.ToggleCase); 930 | return; 931 | 932 | case 'z': 933 | Status = "z"; 934 | CurState = State.Fold; 935 | return; 936 | 937 | case 'm': 938 | Status = "m"; 939 | CurState = State.Mark; 940 | return; 941 | 942 | case '`': 943 | Status = "`"; 944 | CurState = State.GoToMark; 945 | return; 946 | 947 | case '@': 948 | Status = "@"; 949 | CurState = State.PlayMacro; 950 | return; 951 | 952 | case 'q': 953 | if (currentMacro == null) { 954 | Status = "q"; 955 | CurState = State.NameMacro; 956 | return; 957 | } 958 | currentMacro = null; 959 | Reset ("Macro Recorded"); 960 | return; 961 | case '*': 962 | SearchWordAtCaret (backward: false); 963 | return; 964 | case '#': 965 | SearchWordAtCaret (backward: true); 966 | return; 967 | case '.': 968 | RepeatLastAction (); 969 | return; 970 | } 971 | 972 | } 973 | 974 | // if (Data == null) 975 | // Console.WriteLine ("ViMode.HandleKeypress: after State.Normal block, Data is null"); 976 | 977 | action = GetNavCharAction ((char)unicodeKey, true); 978 | if (action == null) { 979 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 980 | } 981 | if (action == null) { 982 | action = ViActionMaps.GetCommandCharAction ((char)unicodeKey); 983 | } 984 | 985 | if (action != null) { 986 | RunRepeatableAction (action); 987 | } 988 | 989 | // if (Data == null) 990 | // Console.WriteLine ("ViMode.HandleKeypress: right before CheckVisualMode, Data is null"); 991 | 992 | //undo/redo may leave MD with a selection mode without activating visual mode 993 | CheckVisualMode (); 994 | return; 995 | 996 | case State.Delete: 997 | if (IsInnerOrOuterMotionKey (unicodeKey, ref motion)) 998 | return; 999 | int offsetz = Caret.Offset; 1000 | 1001 | if (motion != Motion.None) { 1002 | action = ViActionMaps.GetEditObjectCharAction ((char)unicodeKey, motion); 1003 | } else if (((modifier & (Gdk.ModifierType.ShiftMask | Gdk.ModifierType.ControlMask)) == 0 1004 | && (unicodeKey == 'd' || unicodeKey == 'j' || unicodeKey == 'k'))) { 1005 | if (unicodeKey == 'k') { 1006 | action = CaretMoveActions.Up; 1007 | } else { 1008 | action = CaretMoveActions.Down; 1009 | } 1010 | if (unicodeKey == 'j') { 1011 | repeatCount += 1; 1012 | } //get one extra line for yj 1013 | lineAction = true; 1014 | } else { 1015 | action = GetNavCharAction ((char)unicodeKey, false); 1016 | if (action == null) 1017 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1018 | if (action != null) 1019 | action = SelectionActions.FromMoveAction (action); 1020 | } 1021 | 1022 | if (action != null) { 1023 | if (lineAction) { 1024 | RunAction (CaretMoveActions.LineStart); 1025 | SelectionActions.StartSelection (Data); 1026 | for (int i = 0; i < repeatCount; i++) { 1027 | RunAction (action); 1028 | } 1029 | SelectionActions.EndSelection (Data); 1030 | numericPrefix = ""; 1031 | } else { 1032 | RunRepeatableAction (action); 1033 | } 1034 | if (Data.IsSomethingSelected && !lineAction) 1035 | offsetz = Data.SelectionRange.Offset; 1036 | RunAction (ClipboardActions.Cut); 1037 | Reset (string.Empty); 1038 | } else { 1039 | Reset ("Unrecognised motion"); 1040 | } 1041 | Caret.Offset = offsetz; 1042 | 1043 | return; 1044 | 1045 | case State.Yank: 1046 | if (IsInnerOrOuterMotionKey (unicodeKey, ref motion)) 1047 | return; 1048 | int offset = Caret.Offset; 1049 | 1050 | if (motion != Motion.None) { 1051 | action = ViActionMaps.GetEditObjectCharAction ((char)unicodeKey, motion); 1052 | } else if (((modifier & (Gdk.ModifierType.ShiftMask | Gdk.ModifierType.ControlMask)) == 0 1053 | && (unicodeKey == 'y' || unicodeKey == 'j' || unicodeKey == 'k'))) { 1054 | if (unicodeKey == 'k') { 1055 | action = CaretMoveActions.Up; 1056 | } else { 1057 | action = CaretMoveActions.Down; 1058 | } 1059 | if (unicodeKey == 'j') { 1060 | repeatCount += 1; 1061 | } //get one extra line for yj 1062 | lineAction = true; 1063 | } else { 1064 | action = GetNavCharAction ((char)unicodeKey, false); 1065 | if (action == null) 1066 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1067 | if (action != null) 1068 | action = SelectionActions.FromMoveAction (action); 1069 | } 1070 | 1071 | if (action != null) { 1072 | if (lineAction) { 1073 | RunAction (CaretMoveActions.LineStart); 1074 | SelectionActions.StartSelection (Data); 1075 | for (int i = 0; i < repeatCount; i++) { 1076 | RunAction (action); 1077 | } 1078 | SelectionActions.EndSelection (Data); 1079 | numericPrefix = ""; 1080 | } else { 1081 | RunRepeatableAction (action); 1082 | } 1083 | if (Data.IsSomethingSelected && !lineAction) 1084 | offset = Data.SelectionRange.Offset; 1085 | RunAction (ClipboardActions.Copy); 1086 | Reset (string.Empty); 1087 | } else { 1088 | Reset ("Unrecognised motion"); 1089 | } 1090 | Caret.Offset = offset; 1091 | 1092 | return; 1093 | 1094 | case State.Change: 1095 | if (IsInnerOrOuterMotionKey (unicodeKey, ref motion)) 1096 | return; 1097 | 1098 | lastActionStarted = true; 1099 | LastAction.Clear (); 1100 | 1101 | if (motion != Motion.None) { 1102 | action = ViActionMaps.GetEditObjectCharAction ((char)unicodeKey, motion); 1103 | } 1104 | //copied from delete action 1105 | else if (((modifier & (Gdk.ModifierType.ShiftMask | Gdk.ModifierType.ControlMask)) == 0 1106 | && unicodeKey == 'c')) { 1107 | action = SelectionActions.LineActionFromMoveAction (CaretMoveActions.LineEnd); 1108 | lineAction = true; 1109 | } else { 1110 | action = ViActionMaps.GetEditObjectCharAction ((char)unicodeKey); 1111 | if (action == null) 1112 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1113 | if (action != null) 1114 | action = SelectionActions.FromMoveAction (action); 1115 | } 1116 | 1117 | if (action != null) { 1118 | List> actions; 1119 | if (lineAction) { //cd or cj -- delete lines moving downward 1120 | actions = GenerateRepeatedActionList ( 1121 | action, ClipboardActions.Cut, CaretMoveActions.LineFirstNonWhitespace); 1122 | actions.Add (ViActions.NewLineAbove); 1123 | } else if (unicodeKey == 'j') { //cj -- delete current line and line below 1124 | repeatCount += 1; 1125 | action = SelectionActions.LineActionFromMoveAction (CaretMoveActions.LineEnd); 1126 | actions = GenerateRepeatedActionList ( 1127 | action, ClipboardActions.Cut, CaretMoveActions.LineFirstNonWhitespace); 1128 | actions.Add (ViActions.NewLineAbove); 1129 | } else if (unicodeKey == 'k') { //ck -- delete current line and line above 1130 | repeatCount += 1; 1131 | actions = GenerateRepeatedActionList ( 1132 | CaretMoveActions.LineFirstNonWhitespace, ClipboardActions.Cut, action); 1133 | actions.Add (ViActions.NewLineBelow); 1134 | } else { 1135 | actions = GenerateRepeatedActionList (action); 1136 | actions.Add (ClipboardActions.Cut); 1137 | } 1138 | RunActions (actions.ToArray ()); 1139 | LastAction.AddRange (actions); 1140 | Status = "-- INSERT --"; 1141 | PrevState = State.Change; 1142 | CurState = State.Insert; 1143 | Caret.Mode = CaretMode.Insert; 1144 | } else { 1145 | Reset ("Unrecognised motion"); 1146 | } 1147 | 1148 | return; 1149 | 1150 | case State.Insert: 1151 | case State.Replace: 1152 | action = ViActionMaps.GetInsertKeyAction (key, modifier); 1153 | 1154 | // Record InsertKeyActions 1155 | if (action != null) { 1156 | RunAction (action); 1157 | InsertBuffer.Add (new Keystroke (key, unicodeKey, modifier)); 1158 | return; 1159 | } 1160 | 1161 | // Clear InsertBuffer if DirectionKeyAction 1162 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1163 | if (action != null) { 1164 | RunAction (action); 1165 | InsertBuffer.Clear (); 1166 | return; 1167 | } 1168 | 1169 | if (unicodeKey != 0) { 1170 | InsertCharacter (unicodeKey); 1171 | InsertBuffer.Add (new Keystroke (key, unicodeKey, modifier)); 1172 | } 1173 | 1174 | return; 1175 | 1176 | case State.VisualLine: 1177 | if (key == Gdk.Key.Delete) 1178 | unicodeKey = 'x'; 1179 | switch ((char)unicodeKey) { 1180 | case 'p': 1181 | PasteAfter (true); 1182 | return; 1183 | case 'P': 1184 | PasteBefore (true); 1185 | return; 1186 | } 1187 | action = GetNavCharAction ((char)unicodeKey, false); 1188 | if (action == null) { 1189 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1190 | } 1191 | if (action == null) { 1192 | action = ViActionMaps.GetCommandCharAction ((char)unicodeKey); 1193 | } 1194 | if (action != null) { 1195 | RunAction (SelectionActions.LineActionFromMoveAction (action)); 1196 | return; 1197 | } 1198 | 1199 | ApplyActionToSelection (modifier, unicodeKey); 1200 | return; 1201 | 1202 | case State.Visual: 1203 | if (IsInnerOrOuterMotionKey (unicodeKey, ref motion)) return; 1204 | 1205 | if (motion != Motion.None) { 1206 | action = ViActionMaps.GetEditObjectCharAction((char) unicodeKey, motion); 1207 | if (action != null) { 1208 | RunAction (action); 1209 | return; 1210 | } 1211 | } 1212 | 1213 | if (key == Gdk.Key.Delete) 1214 | unicodeKey = 'x'; 1215 | switch ((char)unicodeKey) { 1216 | case 'p': 1217 | PasteAfter (false); 1218 | return; 1219 | case 'P': 1220 | PasteBefore (false); 1221 | return; 1222 | } 1223 | action = GetNavCharAction ((char)unicodeKey, false); 1224 | if (action == null) { 1225 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1226 | } 1227 | if (action == null) { 1228 | action = ViActionMaps.GetCommandCharAction ((char)unicodeKey); 1229 | } 1230 | if (action != null) { 1231 | RunAction (ViActions.VisualSelectionFromMoveAction (action)); 1232 | return; 1233 | } 1234 | 1235 | ApplyActionToSelection (modifier, unicodeKey); 1236 | return; 1237 | 1238 | case State.Command: 1239 | switch (key) { 1240 | case Gdk.Key.Return: 1241 | case Gdk.Key.KP_Enter: 1242 | Status = RunExCommand (commandBuffer.ToString ()); 1243 | commandBuffer.Length = 0; 1244 | //CurState = State.Normal; 1245 | break; 1246 | case Gdk.Key.BackSpace: 1247 | case Gdk.Key.Delete: 1248 | case Gdk.Key.KP_Delete: 1249 | if (0 < commandBuffer.Length) { 1250 | commandBuffer.Remove (commandBuffer.Length-1, 1); 1251 | Status = commandBuffer.ToString (); 1252 | if (0 == commandBuffer.Length) 1253 | Reset (Status); 1254 | } 1255 | break; 1256 | default: 1257 | if(unicodeKey != 0) { 1258 | commandBuffer.Append ((char)unicodeKey); 1259 | Status = commandBuffer.ToString (); 1260 | } 1261 | break; 1262 | } 1263 | return; 1264 | 1265 | case State.WriteChar: 1266 | if (unicodeKey != 0) { 1267 | RunAction (SelectionActions.StartSelection); 1268 | int roffset = Data.SelectionRange.Offset; 1269 | InsertCharacter ((char) unicodeKey); 1270 | Reset (string.Empty); 1271 | Caret.Offset = roffset; 1272 | } else { 1273 | Reset ("Keystroke was not a character"); 1274 | } 1275 | return; 1276 | 1277 | case State.Indent: 1278 | if (((modifier & (Gdk.ModifierType.ControlMask)) == 0 && unicodeKey == '>')) { //select current line to indent 1279 | Action indentAction = MakeIndentAction (repeatCount); 1280 | StartNewLastAction (indentAction); 1281 | RunAction (indentAction); 1282 | return; 1283 | } 1284 | 1285 | action = GetNavCharAction ((char)unicodeKey, false); 1286 | if (action == null) 1287 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1288 | 1289 | if (action != null) { 1290 | // TODO: Stuff this into a delegate and go 1291 | var linesToIndent = repeatCount; 1292 | Action indentAction = delegate(TextEditorData data) { 1293 | List> actions = new List> (); 1294 | //get away from LineBegin 1295 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 1296 | int roffset = Data.SelectionRange.Offset; 1297 | actions.Add (ViActions.Right); 1298 | for (int i = 0; i < linesToIndent; i++) { 1299 | actions.Add (SelectionActions.FromMoveAction (action)); 1300 | } 1301 | actions.Add (MiscActions.IndentSelection); 1302 | RunActions (actions.ToArray ()); 1303 | //set cursor to start of first line indented 1304 | Caret.Offset = roffset; 1305 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 1306 | Reset (""); 1307 | }; 1308 | StartNewLastAction (indentAction); 1309 | RunAction (indentAction); 1310 | } else { 1311 | Reset ("Unrecognised motion"); 1312 | } 1313 | return; 1314 | 1315 | case State.Unindent: 1316 | if (((modifier & (Gdk.ModifierType.ControlMask)) == 0 && ((char)unicodeKey) == '<')) { //select current line to indent 1317 | // TODO: Stuff this into a delegate and go 1318 | Action unindentAction = MakeUnindentAction (repeatCount); 1319 | StartNewLastAction (unindentAction); 1320 | RunAction (unindentAction); 1321 | return; 1322 | } 1323 | 1324 | action = GetNavCharAction ((char)unicodeKey, false); 1325 | if (action == null) 1326 | action = ViActionMaps.GetDirectionKeyAction (key, modifier); 1327 | 1328 | if (action != null) { 1329 | // TODO: Stuff this into a delegate and go 1330 | var linesToUnindent = repeatCount; 1331 | Action unindentAction = delegate(TextEditorData data) { 1332 | List> actions = new List> (); 1333 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 1334 | int roffset = Data.SelectionRange.Offset; 1335 | //get away from LineBegin 1336 | actions.Add (ViActions.Right); 1337 | for (int i = 0; i < linesToUnindent; i++) { 1338 | actions.Add (SelectionActions.FromMoveAction (action)); 1339 | } 1340 | actions.Add (MiscActions.RemoveIndentSelection); 1341 | RunActions (actions.ToArray ()); 1342 | //set cursor to start of first line indented 1343 | Caret.Offset = roffset; 1344 | RunAction (CaretMoveActions.LineFirstNonWhitespace); 1345 | Reset (""); 1346 | }; 1347 | StartNewLastAction (unindentAction); 1348 | RunAction (unindentAction); 1349 | } else { 1350 | Reset ("Unrecognised motion"); 1351 | } 1352 | return; 1353 | 1354 | case State.G: 1355 | 1356 | if (((modifier & (Gdk.ModifierType.ControlMask)) == 0)) { 1357 | switch ((char)unicodeKey) { 1358 | case 'g': 1359 | RunAction (marks ['`'].SaveMark); 1360 | Caret.Offset = 0; 1361 | Reset (""); 1362 | return; 1363 | } 1364 | } 1365 | Reset ("Unknown command"); 1366 | return; 1367 | 1368 | case State.Mark: { 1369 | char k = (char)unicodeKey; 1370 | ViMark mark = null; 1371 | if (!char.IsLetterOrDigit(k) && !SpecialMarks.Contains(k)) { 1372 | Reset ("Invalid Mark"); 1373 | return; 1374 | } 1375 | if (marks.ContainsKey(k)) { 1376 | mark = marks [k]; 1377 | } else { 1378 | mark = new ViMark(k); 1379 | marks [k] = mark; 1380 | } 1381 | RunAction(mark.SaveMark); 1382 | Reset(""); 1383 | return; 1384 | } 1385 | 1386 | case State.NameMacro: { 1387 | char k = (char) unicodeKey; 1388 | if(!char.IsLetterOrDigit(k)) { 1389 | Reset("Invalid Macro Name"); 1390 | return; 1391 | } 1392 | currentMacro = new ViMacro (k); 1393 | currentMacro.KeysPressed = new Queue (); 1394 | macros [k] = currentMacro; 1395 | Reset(""); 1396 | return; 1397 | } 1398 | 1399 | case State.PlayMacro: { 1400 | char k = (char) unicodeKey; 1401 | if (k == '@') 1402 | k = macros_lastplayed; 1403 | if (macros.ContainsKey(k)) { 1404 | int playCount = repeatCount; //store repeat count in case macro changes it 1405 | Reset (""); 1406 | macros_lastplayed = k; // FIXME play nice when playing macros from inside macros? 1407 | ViMacro macroToPlay = macros [k]; 1408 | for (int i = 0 ; i < playCount ; i++) 1409 | { 1410 | foreach (ViMacro.KeySet keySet in macroToPlay.KeysPressed) { 1411 | HandleKeypress(keySet.Key, keySet.UnicodeKey, keySet.Modifiers); // FIXME stop on errors? essential with multipliers and nowrapscan 1412 | } 1413 | } 1414 | /* Once all the keys have been played back, quickly exit. */ 1415 | return; 1416 | } else { 1417 | Reset ("Invalid Macro Name '" + k + "'"); 1418 | return; 1419 | } 1420 | } 1421 | 1422 | case State.GoToMark: { 1423 | char k = (char)unicodeKey; 1424 | if (marks.ContainsKey (k)) { 1425 | if (k != '`') { 1426 | RunAction (marks ['`'].SaveMark); 1427 | } 1428 | RunAction (marks [k].LoadMark); 1429 | Reset (""); 1430 | } else { 1431 | Reset ("Unknown Mark"); 1432 | } 1433 | return; 1434 | } 1435 | 1436 | case State.Fold: 1437 | { 1438 | if (((modifier & (Gdk.ModifierType.ControlMask)) == 0)) { 1439 | switch ((char)unicodeKey) { 1440 | case 'A': 1441 | // Recursive fold toggle 1442 | action = FoldActions.ToggleFoldRecursive; 1443 | break; 1444 | case 'C': 1445 | // Recursive fold close 1446 | action = FoldActions.CloseFoldRecursive; 1447 | break; 1448 | case 'M': 1449 | // Close all folds 1450 | action = FoldActions.CloseAllFolds; 1451 | break; 1452 | case 'O': 1453 | // Recursive fold open 1454 | action = FoldActions.OpenFoldRecursive; 1455 | break; 1456 | case 'R': 1457 | // Expand all folds 1458 | action = FoldActions.OpenAllFolds; 1459 | break; 1460 | case 'a': 1461 | // Fold toggle 1462 | action = FoldActions.ToggleFold; 1463 | break; 1464 | case 'c': 1465 | // Fold close 1466 | action = FoldActions.CloseFold; 1467 | break; 1468 | case 'o': 1469 | // Fold open 1470 | action = FoldActions.OpenFold; 1471 | break; 1472 | default: 1473 | Reset ("Unknown command"); 1474 | break; 1475 | } 1476 | 1477 | if (null != action) { 1478 | RunAction (action); 1479 | Reset (string.Empty); 1480 | } 1481 | } 1482 | return; 1483 | } 1484 | 1485 | case State.Confirm: 1486 | if (((modifier & (Gdk.ModifierType.ControlMask)) == 0)) { 1487 | switch ((char)unicodeKey) { 1488 | case 'y': 1489 | Reset ("Yes"); 1490 | break; 1491 | 1492 | case 'n': 1493 | Reset ("No"); 1494 | break; 1495 | } 1496 | } 1497 | 1498 | return; 1499 | } 1500 | } 1501 | 1502 | static bool IsInnerOrOuterMotionKey (uint unicodeKey, ref Motion motion) 1503 | { 1504 | if (unicodeKey == 'i') { 1505 | motion = Motion.Inner; 1506 | return true; 1507 | } 1508 | if (unicodeKey == 'a') { 1509 | motion = Motion.Outer; 1510 | return true; 1511 | } 1512 | return false; 1513 | } 1514 | 1515 | /// 1516 | /// Runs an in-place replacement on the selection or the current line 1517 | /// using the "pattern", "replacement", and "trailer" groups of match. 1518 | /// 1519 | public string RegexReplace (System.Text.RegularExpressions.Match match) 1520 | { 1521 | string line = null; 1522 | var segment = TextSegment.Invalid; 1523 | 1524 | if (Data.IsSomethingSelected) { 1525 | // Operate on selection 1526 | line = Data.SelectedText; 1527 | segment = Data.SelectionRange; 1528 | } else { 1529 | // Operate on current line 1530 | var lineSegment = Data.Document.GetLine (Caret.Line); 1531 | if (lineSegment != null) 1532 | segment = lineSegment; 1533 | line = Data.Document.GetTextBetween (segment.Offset, segment.EndOffset); 1534 | } 1535 | 1536 | // Set regex options 1537 | RegexOptions options = RegexOptions.Multiline; 1538 | if (match.Groups ["trailer"].Success) { 1539 | if (match.Groups ["trailer"].Value.Contains ("i")) { 1540 | options |= RegexOptions.IgnoreCase; 1541 | } 1542 | } 1543 | 1544 | // Mogrify group backreferences to .net-style references 1545 | string replacement = Regex.Replace (match.Groups["replacement"].Value, @"\\([0-9]+)", "$$$1", RegexOptions.Compiled); 1546 | replacement = Regex.Replace (replacement, "&", "$$0", RegexOptions.Compiled); 1547 | 1548 | try { 1549 | string newline = ""; 1550 | if (match.Groups ["trailer"].Success && match.Groups["trailer"].Value.Contains("g")) 1551 | { 1552 | newline = Regex.Replace (line, match.Groups["pattern"].Value, replacement, options); 1553 | } 1554 | else 1555 | { 1556 | Regex regex = new Regex(match.Groups["pattern"].Value, options); 1557 | newline = regex.Replace(line, replacement, 1); 1558 | } 1559 | Data.Replace (segment.Offset, line.Length, newline); 1560 | if (Data.IsSomethingSelected) 1561 | Data.ClearSelection (); 1562 | lastPattern = match.Groups["pattern"].Value; 1563 | lastReplacement = replacement; 1564 | } catch (ArgumentException ae) { 1565 | return string.Format("Replacement error: {0}", ae.Message); 1566 | } 1567 | 1568 | return "Performed replacement."; 1569 | } 1570 | 1571 | public void ApplyActionToSelection (Gdk.ModifierType modifier, uint unicodeKey) 1572 | { 1573 | RunAction (SaveSelectionRange); 1574 | if (Data.IsSomethingSelected && (modifier & (Gdk.ModifierType.ControlMask)) == 0) { 1575 | switch ((char)unicodeKey) { 1576 | case 'x': 1577 | case 'd': 1578 | StartNewLastAction (MakeActionToApplyToLastSelection (ClipboardActions.Cut)); 1579 | RunAction (ClipboardActions.Cut); 1580 | Reset ("Deleted selection"); 1581 | return; 1582 | case 'y': 1583 | int offset = Data.SelectionRange.Offset; 1584 | RunAction (ClipboardActions.Copy); 1585 | Reset ("Yanked selection"); 1586 | Caret.Offset = offset; 1587 | return; 1588 | case 's': 1589 | case 'c': 1590 | StartNewLastAction (MakeActionToApplyToLastSelection (ClipboardActions.Cut)); 1591 | RunAction (ClipboardActions.Cut); 1592 | Caret.Mode = CaretMode.Insert; 1593 | CurState = State.Insert; 1594 | Status = "-- INSERT --"; 1595 | return; 1596 | case 'S': 1597 | Data.SetSelectLines (Data.MainSelection.Anchor.Line, Data.Caret.Line); 1598 | goto case 'c'; 1599 | 1600 | case '>': 1601 | int roffset = Data.SelectionRange.Offset; 1602 | if (true) { 1603 | int lineStart, lineEnd; 1604 | MiscActions.GetSelectedLines (Data, out lineStart, out lineEnd); 1605 | int lines = lineEnd - lineStart + 1; 1606 | StartNewLastAction (MakeIndentAction (lines)); 1607 | } 1608 | RunAction (MiscActions.IndentSelection); 1609 | Caret.Offset = roffset; 1610 | Reset (""); 1611 | return; 1612 | 1613 | case '<': 1614 | roffset = Data.SelectionRange.Offset; 1615 | if (true) { 1616 | int lineStart, lineEnd; 1617 | MiscActions.GetSelectedLines (Data, out lineStart, out lineEnd); 1618 | int lines = lineEnd - lineStart + 1; 1619 | StartNewLastAction (MakeUnindentAction (lines)); 1620 | } 1621 | RunAction (MiscActions.RemoveIndentSelection); 1622 | Caret.Offset = roffset; 1623 | Reset (""); 1624 | return; 1625 | 1626 | case ':': 1627 | commandBuffer.Append (":"); 1628 | Status = commandBuffer.ToString (); 1629 | CurState = State.Command; 1630 | break; 1631 | case 'J': 1632 | StartNewLastAction (MakeActionToApplyToLastSelection (ViActions.Join)); 1633 | RunAction (ViActions.Join); 1634 | Reset (""); 1635 | return; 1636 | 1637 | case '~': 1638 | StartNewLastAction (MakeActionToApplyToLastSelection (ViActions.ToggleCase)); 1639 | RunAction (ViActions.ToggleCase); 1640 | Reset (""); 1641 | return; 1642 | 1643 | case '=': 1644 | FormatSelection (); 1645 | Reset (""); 1646 | return; 1647 | } 1648 | } 1649 | } 1650 | 1651 | private string Search() 1652 | { 1653 | int next = (Caret.Offset + 1 > Document.TextLength) ? 1654 | Caret.Offset : Caret.Offset + 1; 1655 | SearchResult result = searchBackward? 1656 | Editor.SearchBackward (Caret.Offset): 1657 | Editor.SearchForward (next); 1658 | Editor.HighlightSearchPattern = (null != result); 1659 | if (null == result) 1660 | return string.Format ("Pattern not found: '{0}'", Editor.SearchPattern); 1661 | else { 1662 | RunAction (marks ['`'].SaveMark); 1663 | Caret.Offset = result.Offset; 1664 | } 1665 | 1666 | return string.Empty; 1667 | } 1668 | 1669 | /// 1670 | /// Pastes the selection after the caret, 1671 | /// or replacing an existing selection. 1672 | /// 1673 | private void PasteAfter (bool linemode) 1674 | { 1675 | TextEditorData data = Data; 1676 | using (var undo = Document.OpenUndoGroup ()) { 1677 | 1678 | Gtk.Clipboard.Get (ClipboardActions.CopyOperation.CLIPBOARD_ATOM).RequestText 1679 | (delegate (Gtk.Clipboard cb, string contents) { 1680 | if (contents == null) 1681 | return; 1682 | if (contents.EndsWith ("\r") || contents.EndsWith ("\n")) { 1683 | // Line mode paste 1684 | if (data.IsSomethingSelected) { 1685 | // Replace selection 1686 | RunAction (ClipboardActions.Cut); 1687 | data.InsertAtCaret (data.EolMarker); 1688 | int offset = data.Caret.Offset; 1689 | data.InsertAtCaret (contents); 1690 | if (linemode) { 1691 | // Existing selection was also in line mode 1692 | data.Caret.Offset = offset; 1693 | RunAction (DeleteActions.FromMoveAction (CaretMoveActions.Left)); 1694 | } 1695 | RunAction (CaretMoveActions.LineStart); 1696 | } else { 1697 | // Paste on new line 1698 | RunAction (ViActions.NewLineBelow); 1699 | RunAction (DeleteActions.FromMoveAction (CaretMoveActions.LineStart)); 1700 | data.InsertAtCaret (contents); 1701 | RunAction (DeleteActions.FromMoveAction (CaretMoveActions.Left)); 1702 | RunAction (CaretMoveActions.LineStart); 1703 | } 1704 | } else { 1705 | // Inline paste 1706 | if (data.IsSomethingSelected) 1707 | RunAction (ClipboardActions.Cut); 1708 | else RunAction (CaretMoveActions.Right); 1709 | data.InsertAtCaret (contents); 1710 | RunAction (ViActions.Left); 1711 | } 1712 | Reset (string.Empty); 1713 | }); 1714 | } 1715 | } 1716 | 1717 | /// 1718 | /// Pastes the selection before the caret, 1719 | /// or replacing an existing selection. 1720 | /// 1721 | private void PasteBefore (bool linemode) 1722 | { 1723 | TextEditorData data = Data; 1724 | 1725 | using (var undo = Document.OpenUndoGroup ()) { 1726 | Gtk.Clipboard.Get (ClipboardActions.CopyOperation.CLIPBOARD_ATOM).RequestText 1727 | (delegate (Gtk.Clipboard cb, string contents) { 1728 | if (contents == null) 1729 | return; 1730 | if (contents.EndsWith ("\r") || contents.EndsWith ("\n")) { 1731 | // Line mode paste 1732 | if (data.IsSomethingSelected) { 1733 | // Replace selection 1734 | RunAction (ClipboardActions.Cut); 1735 | data.InsertAtCaret (data.EolMarker); 1736 | int offset = data.Caret.Offset; 1737 | data.InsertAtCaret (contents); 1738 | if (linemode) { 1739 | // Existing selection was also in line mode 1740 | data.Caret.Offset = offset; 1741 | RunAction (DeleteActions.FromMoveAction (CaretMoveActions.Left)); 1742 | } 1743 | RunAction (CaretMoveActions.LineStart); 1744 | } else { 1745 | // Paste on new line 1746 | RunAction (ViActions.NewLineAbove); 1747 | RunAction (DeleteActions.FromMoveAction (CaretMoveActions.LineStart)); 1748 | data.InsertAtCaret (contents); 1749 | RunAction (DeleteActions.FromMoveAction (CaretMoveActions.Left)); 1750 | RunAction (CaretMoveActions.LineStart); 1751 | } 1752 | } else { 1753 | // Inline paste 1754 | if (data.IsSomethingSelected) 1755 | RunAction (ClipboardActions.Cut); 1756 | data.InsertAtCaret (contents); 1757 | RunAction (ViActions.Left); 1758 | } 1759 | Reset (string.Empty); 1760 | }); 1761 | } 1762 | } 1763 | 1764 | protected enum State { 1765 | Unknown = 0, 1766 | Normal, 1767 | Command, 1768 | Delete, 1769 | Yank, 1770 | Visual, 1771 | VisualLine, 1772 | Insert, 1773 | Replace, 1774 | WriteChar, 1775 | Change, 1776 | Indent, 1777 | Unindent, 1778 | G, 1779 | Fold, 1780 | Mark, 1781 | GoToMark, 1782 | NameMacro, 1783 | PlayMacro, 1784 | Confirm 1785 | } 1786 | } 1787 | 1788 | public enum Motion { 1789 | None = 0, 1790 | Inner, 1791 | Outer 1792 | } 1793 | } 1794 | --------------------------------------------------------------------------------