├── .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 |
--------------------------------------------------------------------------------