Repository: nosami/XSVim Branch: 8.1 Commit: cec402b3509d Files: 47 Total size: 218.4 KB Directory structure: gitextract_1b8pllvk/ ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── XSVim/ │ ├── Addin.fs │ ├── Classes.fs │ ├── ExMode.fs │ ├── KeyBindingSchemeVim.xml │ ├── Mapping.fs │ ├── PadTreeViews.fs │ ├── Properties/ │ │ ├── AddinInfo.fs │ │ ├── AssemblyInfo.fs │ │ └── Manifest.addin.xml │ ├── Reflection.fs │ ├── SettingsPanel.fs │ ├── SubstituteCommand.fs │ ├── TreeViewPads.fs │ ├── Types.fs │ ├── WindowManagement.fs │ ├── XSVim.fs │ ├── XSVim.fsproj │ └── packages.config ├── XSVim.Tests/ │ ├── ChangeTests.fs │ ├── DeleteTests.fs │ ├── ExModeTests.fs │ ├── IndentationTests.fs │ ├── InsertionTests.fs │ ├── KeyParsing.fs │ ├── KeyboardMap.fs │ ├── MacrosTests.fs │ ├── MarkerTests.fs │ ├── MiscTests.fs │ ├── Movement.fs │ ├── Properties/ │ │ └── AssemblyInfo.fs │ ├── TestHelpers.fs │ ├── TextObjectSelectionTests.fs │ ├── VisualTests.fs │ ├── XSVim.Tests.fsproj │ ├── YankAndPut.fs │ └── packages.config ├── XSVim.sln ├── addin-project.xml ├── build.sh ├── copy-assemblies.sh ├── run-from-source.sh └── test.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.sh text eol=lf *.fs text eol=lf Makefile.orig text eol=lf configure.sh text eol=lf ================================================ FILE: .gitignore ================================================ # F# [Bb]in/ [Oo]bj/ .fake/ repository/* *.suo *.pidb *.userprefs *.GhostDoc.xml *.user *.dll *.pdb *.cache *.swp *.swo *.swn *.pyc *.orig ======= *~ pack/* .DS_Store /packages XSVim/any .paket/paket.exe ================================================ FILE: .travis.yml ================================================ language: csharp os: osx mono: latest solution: XSVim.sln install: - nuget restore XSVim.sln - wget https://download.visualstudio.microsoft.com/download/pr/29ca6bca-9226-4904-93f8-3678cb24c2ca/596a772e2d2a7d28cbfd570933817a70/visualstudioformac-8.4.8.2.dmg script: - msbuild /p:Configuration=Release #- mono64 "/Volumes/Visual Studio/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/vstool.exe" # run-md-tests XSVim.Tests/bin/Release/XSVim.Tests.dll -labels - mono64 "/Volumes/Visual Studio/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/vstool.exe" setup pack XSVim/bin/Release/XSVim.dll deploy: provider: releases api_key: secure: GMgK73gqQxvaMXXtgZTVypt2nTmmY/uNOFc3f6pHqe31nvLtDgdwRgaqGC4uFHK/YspWujMXkmdo0OplyWZarZcGTSPJYcJ9/k9bEY4uXDVbLAGve2n7qIyhngprd1Mk9g+3zPEoi+xu7Ugc1y0GEFLy9Z3CXgr9A1AcEM49LsqpV2rqtjP8AjstmpVUgLXv5+6w5MlcJyoi0PulVE3B4N0I9EJoD8zfaTCNyq0YaydekR173bu5iuZsVuYkyqLsrKbWDJb67MiDG5pwW5vvcPIziPXf5TMXHRbnWZHdXmx5jlbV8k0DoaeChWh1gpMtr6gg1IuBCsFV65ackHqeZyyn6iKl/SwIUGBYkTadY4Hob5ns5LC/vRB2Rb32U7MRyP8eN8PpmHpVZL28DQq3PlqvehG3vjYe8AOZrRWY7e/2iu0lrpWxbYwf9X5CjTRhTPOUvMV0u7SgLMYdcf39iZoMwQuJTG3gv79ScZBlimisybEDl88T++XsfTI7LZOE4w91RnzjvhoHAUu2+R8Tpcj2wm0df/eOcFzIZqCTGgT+WefBB4yAo2Art3Ckk8MH/uLIEzRjsVeEYUFTN5xZ6N7YRhQB6JgyEEaP8NT7wZFX6xms+AwgITQEiTf29veyPKPFL0htvAaIlu7XFse7f4JZBtXNGfOja+T0b7nvlEI= file_glob: true file: XSVim*.mpack skip_cleanup: true on: tags: true ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Jason Imison Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # XSVim [![Gitter](https://badges.gitter.im/XSVim/Lobby.svg)](https://gitter.im/XSVim/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/nosami/XSVim.svg?branch=7.4)](https://travis-ci.org/nosami/XSVim) # This addin is obsolete This extension only works for the older editor. For VSMac 8.4, you should install VsVim instead ![VsVim installation from the Gallery](https://user-images.githubusercontent.com/667194/71757513-71efdc00-2e8e-11ea-898d-167d5d40cb17.png) as VsVim works on the new editor. Some file types (e.g. F#) are still currently using the old editor in VSMac. This addin will still work for those file types while the transition is made to the new editor. # Installation Interact with Visual Studio for Mac as follows: ``` Visual Studio -> Extensions -> Gallery -> IDE Extensions -> "VIM" -> Install ``` Then close the current document that you are working on and open a new document to activate the plugin. ## 8.1 New Editor Unfortunately, this addin does not work with the new editor that was made default in 8.1. To use this addin, make sure that the old editor is in use. ![image](https://user-images.githubusercontent.com/667194/59626372-9de68280-9133-11e9-9fbe-035553d7042e.png) Alternatively, I have been working on making VsVim work for the new editor. If you want to try this out, please follow the instructions [here](https://github.com/VsVim/VsVim/pull/2733#issuecomment-538998555) # What works? Most Vim commands should work. If you see something that doesn't work, please file an issue. There's a good chance that I just don't know about it. # What doesn't work - Vim split windows. XSVim uses VS for Mac's side by side mode to emulate this, but it's only possible to have 2 vertical split windows. `s` and `v` both switch to side by side mode. - Visual block mode works for most tasks, but there are some differences in the way that VS handles virtual spacing at the end of lines. - Selecting text with the mouse or using cmd+arrow keys doesn't switch to Visual mode - No leader key support or configurable key bindings. # Why don't the control keys work? Some Vim keybindings (such as Ctrl-F, Ctrl-D etc) conflict with VS's own built in keybindings. However, there is a keybinding scheme included that you may apply if you want (Visual Studio + Vim) ![image](https://user-images.githubusercontent.com/667194/37340194-39775566-26b5-11e8-9119-58d171aa9a01.png) # Extras - `gd` - Goto declaration - `gu` - Find usages - `gb` - Go to base symbol - `gh` - Show tooltip at current caret location (`G`o `H`over) - `hjkl` support on the Solution Explorer pad and Test Explorer pad. Pressing `` on these will switch focus back to the last editor window. `jk` support on the Search Results pad. - Goto Pad shortcuts start with `gp` - `gps` - Go to solution explorer - `gpc` - Go to class pad - `gpe` - Go to error list pad - `gpt` - Go to Task List pad - `gpp` - Go to Property pad - `gpo` - Go to document outline pad - `gpb` - Go to breakpoint pad - `gpl` - Go to locals pad - `gpw` - Go to watch pad - `gpi` - Go to immediate pad - `gpn` - Go to F# Interactive pad - `gpf` - When there is only one search results pad, go to it - `gpf1` - When there is more than one search results pad, go to the 1st - `gpf2` - When there is more than one search results pad, go to the 2nd....etc. - `gpdt` - Go to debugger threads pad - `gpds` - Go to debugger stack trace pad - `gput` - Go to unit test pad - `gpur` - Go to unit test results pad - Insert mode escape binding. See example screenshot to see how to configure `jj` to escape when in insert mode. ![Insert mode escape screenshot](screenshots/InsertModeMapping.png) # Looking for the latest release? Check the [release page](https://github.com/nosami/XSVim/releases) as there is usually a more recent version of the addin here than on the Visual Studio for Mac feed. Grab the .mpack file and install it via Visual Studio -> Extensions -> Install from file # Support & Contributions Jump in our [Gitter channel](https://gitter.im/XSVim/Lobby) and introduce yourself. # With thanks to - @shirshov - @mdizzy - @tdfacer ================================================ FILE: XSVim/Addin.fs ================================================ namespace XSVim open System open System.Collections.Generic open MonoDevelop.Components.Commands open MonoDevelop.Core open MonoDevelop.Ide open MonoDevelop.Ide.Editor open MonoDevelop.Ide.Editor.Extension open MonoDevelop.Ide.FindInFiles open Reflection module Subscriptions = let textChanged (editor:TextEditor) (changes: Text.TextChangeEventArgs) = for change in changes.TextChanges do if change.RemovalLength > 0 then let state = Vim.editorStates.[editor.FileName] let actions = [ yield! state.lastAction for _i in 1..change.RemovalLength do yield (typeChar VimKey.Backspace) ] let newState = { state with lastAction = actions } Vim.editorStates.[editor.FileName] <- newState if change.Offset + change.InsertionLength = editor.CaretOffset then let state = Vim.editorStates.[editor.FileName] let typedChars = [ for c in change.InsertedText.Text do yield typeChar (Key c) ] let vimState = { state with lastAction = state.lastAction @ typedChars } Vim.editorStates.[editor.FileName] <- vimState type XSVim() as this = inherit TextEditorExtension() let mutable disposables : IDisposable list = [] let mutable processingKey = false let mutable config = Config.Default static let searchPads = HashSet() let initConfig() = let keyboardMapping = match SettingsPanel.KeyboardLayout() with | "Colemak" -> Colemak | "Dvorak" -> Dvorak | _ -> Qwerty let mapping = SettingsPanel.InsertModeEscapeMapping() if mapping.Length = 2 then config <- { insertModeEscapeKey = { insertModeEscapeKey1 = string mapping.[0] insertModeEscapeKey2 = string mapping.[1] insertModeEscapeTimeout = SettingsPanel.InsertModeEscapeMappingTimeout() } |> Some keyboardLayout = keyboardMapping } else config <- { Config.Default with keyboardLayout = keyboardMapping } let initializeSearchResultsPads() = IdeApp.Workbench |> Option.ofObj |> Option.iter(fun workbench -> workbench.Pads |> Seq.iter(fun pad -> try // fetching pad.Content can throw when there is an exception // when initializing the pad tryUnbox pad.Content |> Option.iter(fun pad -> let padId = pad.Window.Id if not (searchPads.Contains padId) then searchPads.Add padId |> ignore let tree = pad.Control?nativeWidget?treeviewSearchResults padTreeViews.initialize tree) with | _ -> ())) let ctrl c = KeyDescriptor.FromGtk(Enum.Parse(typeof, c) :?> Gdk.Key, char c, Gdk.ModifierType.ControlMask) |> this.KeyPress let mutable fileName = FilePath.Empty member x.State with get() = Vim.editorStates.[fileName] and set(value) = Vim.editorStates.[fileName] <- value override x.IsValidInContext documentContext = documentContext.Name <> "__FSI__.fs" && documentContext.Name <> "__FSI__.fsx" override x.Initialize() = treeViewPads.initialize() x.Editor.FocusLost.Add(fun _ -> initializeSearchResultsPads()) fileName <- x.Editor.FileName.FullPath LoggingService.LogDebug("XSVim initializing - " + string fileName) initConfig() let editor = x.Editor let state = match Vim.getCaretMode editor with | Insert -> { VimState.Default with mode = InsertMode } | Block -> VimState.Default let initialState = Vim.switchToNormalMode editor state if not (Vim.editorStates.ContainsKey fileName) then Vim.editorStates.Add(fileName, initialState) editor.GrabFocus() let caretChanged = editor.CaretPositionChanged.Subscribe (fun _e -> if not processingKey then // only interested in mouse clicks let line = editor.GetLine editor.CaretLine if line.Length > 0 && editor.CaretColumn >= line.LengthIncludingDelimiter then editor.CaretOffset <- editor.CaretOffset - 1) let documentClosed = IdeApp.Workbench |> Option.ofObj |> Option.map(fun workbench -> workbench.DocumentClosed.Subscribe (fun e -> let documentName = e.Document.FileName if Vim.editorStates.ContainsKey documentName then Vim.editorStates.Remove documentName |> ignore)) let textChanged = editor.TextChanged.Subscribe(fun changes -> Subscriptions.textChanged editor changes) let propertyChanged = PropertyService.PropertyChanged.Subscribe (fun _ -> initConfig()) let focusLost = editor.FocusLost.Subscribe (fun _ -> match x.State.mode with | ExMode _ -> x.State <- Vim.switchToNormalMode x.Editor x.State IdeApp.Workbench.StatusBar.ShowReady() | _ -> ()) disposables <- [ yield caretChanged if documentClosed.IsSome then yield documentClosed.Value yield propertyChanged yield textChanged yield focusLost ] override x.KeyPress descriptor = match descriptor.ModifierKeys with | ModifierKeys.Control | ModifierKeys.Command when descriptor.KeyChar = 'z' -> // cmd-z uses the vim undo group x.State.undoGroup |> Option.iter(fun d -> d.Dispose()) EditActions.Undo x.Editor x.Editor.ClearSelection() false | ModifierKeys.Command when descriptor.KeyChar <> 'z' && descriptor.KeyChar <> 'r' -> false | _ -> let oldState = x.State processingKey <- true let newState, handledKeyPress = Vim.handleKeyPress x.State descriptor x.Editor config processingKey <- false match newState.statusMessage, newState.macro with | Some m, None -> IdeApp.Workbench.StatusBar.ShowMessage m | Some m, Some _ -> IdeApp.Workbench.StatusBar.ShowMessage (m + "recording") | None, Some _ -> IdeApp.Workbench.StatusBar.ShowMessage "recording" | _ -> IdeApp.Workbench.StatusBar.ShowReady() x.State <- newState match oldState.mode, newState.mode, config.insertModeEscapeKey with | InsertMode, InsertMode, _ when descriptor.ModifierKeys = ModifierKeys.Control && descriptor.KeyChar = 'n' -> false // Hack: Ctrl-N seems to be hardwired inside VS somehow to Emacs' line down | InsertMode, InsertMode, None -> base.KeyPress descriptor | InsertMode, InsertMode, Some escapeCombo when descriptor.KeyChar.ToString() <> escapeCombo.insertModeEscapeKey1 -> base.KeyPress descriptor | VisualMode, _, _ -> false | _ -> not handledKeyPress [] // We handle cmd-z ourselves to use the vim undo stack member x.CanUndo(ci:CommandInfo) = ci.Enabled <- false [] member x.Rename(ci:CommandInfo) = ci.Enabled <- true // dirty hack - use the command update handler to switch to insert mode // before the inline rename kicks in if x.State.mode <> InsertMode then x.State <- Vim.switchToInsertMode x.Editor x.State false // Command handlers for all keys that possibly conflict with // out of the box key binding schemes. [] member x.HalfPageDown() = ctrl "d" [] member x.PageDown() = ctrl "f" [] member x.PageUp() = ctrl "b" [] member x.FindFile() = ctrl "p" [] member x.DynamicAbbrev() = ctrl "n" [] member x.NavigateBackwards() = ctrl "o" [] member x.NavigateForwards() = ctrl "i" [] member x.IncrementNumber() = ctrl "x" [] member x.DecrementNumber() = ctrl "a" [] member x.Escape() = ctrl "c" override x.Dispose() = Vim.editorStates.Remove(fileName) |> ignore; base.Dispose() disposables |> List.iter(fun d -> d.Dispose()) ================================================ FILE: XSVim/Classes.fs ================================================ namespace XSVim open MonoDevelop.Core open MonoDevelop.Core.Text open MonoDevelop.Ide open MonoDevelop.Ide.Editor open MonoDevelop.Ide.Editor.Extension type Marker(editor:TextEditor, name:string) = let segment = TextSegment(editor.CaretOffset, 1) // Create an invisible "find usages" marker. This is the only way to construct a TextSegmentMarker // without reflection hacks. VS checks that the marker is a TextSegmentMarker // https://github.com/mono/monodevelop/blob/61459958511d7ad4ea8debf4a59a77b1e98793fc/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs#L2744 let marker = TextMarkerFactory.CreateUsageMarker(editor, Usage(segment, FindInFiles.ReferenceUsageType.Unknown)) do marker.IsVisible <- false editor.AddMarker marker member x.Name = name member x.FileName = string editor.FileName.FullPath member x.Offset = marker.Offset member x.TextSegmentMarker = marker member x.Remove() = try editor.RemoveMarker marker |> ignore with | ex -> LoggingService.LogError (string ex) ================================================ FILE: XSVim/ExMode.fs ================================================ namespace XSVim open System open System.Text.RegularExpressions open Reflection open MonoDevelop.Ide open MonoDevelop.Ide.Commands open MonoDevelop.Ide.Editor.Extension module exMode = let getFirstCharAndRest (s:string) = s.[0], s.[1..] let processCommand command = let firstChar, rest = getFirstCharAndRest command match firstChar, rest with | '/', _ -> [ runOnce (IncrementalSearch rest) Nothing ] | '?', _ -> [ runOnce (IncrementalSearchBackwards rest) Nothing ] | _ -> wait let save() = IdeApp.Workbench.ActiveDocument.Save() |> Async.AwaitTask let (|DeleteBetweenMarks|_|) input = let matches = Regex.Matches(input, "'([a-z]),'([a-z])d", RegexOptions.Compiled) if matches.Count = 1 then let m = matches.[0] Some (m.Groups.[1].Value, m.Groups.[2].Value) else None let (|DeleteLines|_|) input = let matches = Regex.Matches(input, "([\d]+),([\d]+)d", RegexOptions.Compiled) if matches.Count = 1 then let m = matches.[0] Some (int m.Groups.[1].Value, int m.Groups.[2].Value) else None let (|Substitute|_|) input = let matches = Regex.Matches(input, "(.*)s/(.*)/(.*)", RegexOptions.Compiled) if matches.Count = 1 then let m = matches.[0] match m.Groups.[1].Value with | "%" -> Some { find = m.Groups.[2].Value; replace = m.Groups.[3].Value; scope = Document } | "'<,'>" -> Some { find = m.Groups.[2].Value; replace = m.Groups.[3].Value; scope = Selection } | _ -> None else None let processKey (state:VimState) (key:KeyDescriptor) = let setMessage message = { state with statusMessage = message } let normalMode = { state with statusMessage = None; mode = NormalMode } match key.SpecialKey with | SpecialKey.BackSpace -> let message = match state.statusMessage with | Some msg -> let len = msg.Length msg.[0..len-2] | None -> "" if message.Length > 0 then setMessage (Some message), processCommand message else normalMode, resetKeys | SpecialKey.Return -> match state.statusMessage with | Some message -> let firstChar, rest = getFirstCharAndRest message let getSearchAction() = match state.searchAction with | Some action -> action | _ -> Move let restIsNumeric, number = Int32.TryParse rest // really bad parser. TODO: try and use https://github.com/jaredpar/VsVim/blob/447d980da9aa6c761238e39df9d2b64424643de1/Src/VimCore/Interpreter_Parser.fs match firstChar with | '/' -> { state with statusMessage = None; mode = NormalMode; lastSearch = Some (Jump (ToSearch rest)) } , [ runOnce (getSearchAction()) (Jump (ToSearch rest))] | '?' -> { state with statusMessage = None; mode = NormalMode; lastSearch = Some (Jump (ToSearchBackwards rest)) } , [ runOnce (getSearchAction()) (Jump (ToSearchBackwards rest))] | ':' when restIsNumeric -> { state with statusMessage = None; mode = NormalMode; } , [ runOnce Move (Jump (StartOfLineNumber number)) ] | ':' -> match rest with | "q" -> Window.closeTab() normalMode, resetKeys | "q!" -> Window.forceCloseTab() normalMode, resetKeys | "w" | "w!" -> async { do! save() } |> Async.StartImmediate normalMode, resetKeys | "wa" | "wa!" -> async { dispatchCommand FileCommands.SaveAll } |> Async.StartImmediate normalMode, resetKeys | "qa" -> async { dispatchCommand FileCommands.CloseAllFiles } |> Async.StartImmediate normalMode, resetKeys | "qa!" -> IdeApp.Workbench.Documents |> Seq.iter(fun doc -> async { do! doc.Close true |> Async.AwaitTask |> Async.Ignore } |> Async.StartImmediate) normalMode, resetKeys | "wq" -> async { do! save() dispatchCommand FileCommands.CloseFile } |> Async.StartImmediate normalMode, resetKeys | "wq!" -> async { do! save() Window.forceCloseTab() } |> Async.StartImmediate normalMode, resetKeys | "vs" | "vsp" | "vsplit" | "sp" | "split" -> let notebooks = Window.getNotebooks() if notebooks.Length < 2 then dispatchCommand "MonoDevelop.Ide.Commands.ViewCommands.SideBySideMode" normalMode, resetKeys | DeleteBetweenMarks (startMarker, endMarker) -> let actions = [ runOnce Move (Jump (ToMark (startMarker, MarkerJumpType.StartOfLine))) runOnce DeleteWholeLines (Jump (ToMark (endMarker, MarkerJumpType.StartOfLine))) ] normalMode, actions | DeleteLines (startLine, endLine) -> let actions = [ runOnce Move (Jump (StartOfLineNumber startLine)) runOnce DeleteWholeLines (Jump (StartOfLineNumber endLine)) ] normalMode, actions | Substitute substition -> match Substitute.substitute substition with | true -> normalMode, resetKeys | false -> state, resetKeys | _ -> { state with statusMessage = sprintf "Could not parse :%s" rest |> Some; mode = NormalMode; } , resetKeys | _ -> normalMode, resetKeys | _ -> normalMode, resetKeys | _ -> let message = match state.statusMessage with | Some msg -> sprintf "%s%c" msg key.KeyChar | None -> string key.KeyChar setMessage (Some message), processCommand message ================================================ FILE: XSVim/KeyBindingSchemeVim.xml ================================================  ================================================ FILE: XSVim/Mapping.fs ================================================ namespace XSVim [] module mapping = let colemakToQwerty = function | "f" -> "e" | "p" -> "r" | "g" -> "t" | "j" -> "y" | "l" -> "u" | "u" -> "i" | "y" -> "o" | ";" -> "p" | "r" -> "s" | "s" -> "d" | "t" -> "f" | "d" -> "g" | "n" -> "j" | "e" -> "k" | "i" -> "l" | "o" -> ";" | "k" -> "n" | "F" -> "E" | "P" -> "R" | "G" -> "T" | "J" -> "Y" | "L" -> "U" | "U" -> "I" | "Y" -> "O" | ":" -> "P" | "R" -> "S" | "S" -> "D" | "T" -> "F" | "D" -> "G" | "N" -> "J" | "E" -> "K" | "I" -> "L" | "O" -> ":" | "K" -> "N" | c -> c let dvorakToQwerty = function | "[" -> "-" | "]" -> "=" | "'" -> "q" | "," -> "w" | "." -> "e" | "p" -> "r" | "y" -> "t" | "f" -> "y" | "g" -> "u" | "c" -> "i" | "r" -> "o" | "l" -> "p" | "/" -> "[" | "=" -> "]" | "o" -> "s" | "e" -> "d" | "u" -> "f" | "i" -> "g" | "d" -> "h" | "h" -> "j" | "t" -> "k" | "n" -> "l" | "s" -> ";" | "-" -> "'" | ";" -> "z" | "q" -> "x" | "j" -> "c" | "k" -> "v" | "x" -> "b" | "b" -> "n" | "w" -> "," | "v" -> "." | "z" -> "/" | "{" -> "_" | "}" -> "+" | "\"" -> "Q" | "<" -> "W" | ">" -> "E" | "P" -> "R" | "Y" -> "T" | "F" -> "Y" | "G" -> "U" | "C" -> "I" | "R" -> "O" | "L" -> "P" | "?" -> "{" | "+" -> "}" | "O" -> "S" | "E" -> "D" | "U" -> "F" | "I" -> "G" | "D" -> "H" | "H" -> "J" | "T" -> "K" | "N" -> "L" | "S" -> ":" | "_" -> "\"" | ":" -> "Z" | "Q" -> "X" | "J" -> "C" | "K" -> "V" | "X" -> "B" | "B" -> "N" | "M" -> "M" | "W" -> "<" | "V" -> ">" | "Z" -> "?" | c -> c let remap layout key = match layout with | Colemak -> colemakToQwerty key | Dvorak -> dvorakToQwerty key | _ -> key ================================================ FILE: XSVim/PadTreeViews.fs ================================================ namespace XSVim open Gtk open MonoDevelop.Ide.Gui.Components module padTreeViews = let select (tree:TreeView) path = let column = tree.Columns.[0] tree.Selection.SelectPath path tree.SetCursor(path, column, false) let getSelectedPath (tree:TreeView) = tree.Selection.GetSelectedRows().[0] let moveDown tree = let path = getSelectedPath tree path.Next() select tree path let moveUp tree = let path = getSelectedPath tree path.Prev() |> ignore select tree path let initialize (tree:PadTreeView) = let processKey (key:KeyPressEventArgs) = match key.Event.Key with | Gdk.Key.Escape -> dispatchCommand "MonoDevelop.Ide.Commands.ViewCommands.FocusCurrentDocument" key.RetVal <- false | Gdk.Key.j -> moveDown tree key.RetVal <- true | Gdk.Key.k -> moveUp tree key.RetVal <- true | _ -> () tree.KeyPressEvent.Add processKey ================================================ FILE: XSVim/Properties/AddinInfo.fs ================================================ namespace XSVim open Mono.Addins open MonoDevelop [] [] [] [] [] [] [] [] [] () ================================================ FILE: XSVim/Properties/AssemblyInfo.fs ================================================ namespace XSVim open System.Reflection open System.Runtime.CompilerServices [] module AddinVersion = [] let version = "0.65.12.81" [] [] //[] //[] () ================================================ FILE: XSVim/Properties/Manifest.addin.xml ================================================ 
================================================ FILE: XSVim/Reflection.fs ================================================ namespace XSVim open System open System.Reflection open Microsoft.FSharp.Reflection module Reflection = // Various flags that specify what members can be called // NOTE: Remove 'BindingFlags.NonPublic' if you want a version // that can call only public methods of classes let staticFlags = BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Static let instanceFlags = BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Instance let private ctorFlags = instanceFlags let inline asMethodBase(a:#MethodBase) = a :> MethodBase // The operator takes just instance and a name. Depending on how it is used // it either calls method (when 'R is function) or accesses a property let (?) (o:obj) name : 'R = // The return type is a function, which means that we want to invoke a method if FSharpType.IsFunction(typeof<'R>) then // Get arguments (from a tuple) and their types let argType, _resType = FSharpType.GetFunctionElements(typeof<'R>) // Construct an F# function as the result (and cast it to the // expected function type specified by 'R) FSharpValue.MakeFunction(typeof<'R>, fun args -> // We treat elements of a tuple passed as argument as a list of arguments // When the 'o' object is 'System.Type', we call static methods let methods, instance, args = let args = // If argument is unit, we treat it as no arguments, // if it is not a tuple, we create singleton array, // otherwise we get all elements of the tuple if argType = typeof then [| |] elif not(FSharpType.IsTuple(argType)) then [| args |] else FSharpValue.GetTupleFields(args) // Static member call (on value of type System.Type)? if (typeof).IsAssignableFrom(o.GetType()) then let methods = (unbox o).GetMethods(staticFlags) |> Array.map asMethodBase let ctors = (unbox o).GetConstructors(ctorFlags) |> Array.map asMethodBase Array.concat [ methods; ctors ], null, args else o.GetType().GetMethods(instanceFlags) |> Array.map asMethodBase, o, args // A simple overload resolution based on the name and the number of parameters only // TODO: This doesn't correctly handle multiple overloads with same parameter count let methods = [ for m in methods do if m.Name = name && m.GetParameters().Length = args.Length then yield m ] // If we find suitable method or constructor to call, do it! match methods with | [] -> failwithf "No method '%s' with %d arguments found" name args.Length | _::_::_ -> failwithf "Multiple methods '%s' with %d arguments found" name args.Length | [:? ConstructorInfo as c] -> c.Invoke(args) | [ m ] -> m.Invoke(instance, args) ) |> unbox<'R> else // The result type is not an F# function, so we're getting a property // When the 'o' object is 'System.Type', we access static properties let typ, flags, instance = if (typeof).IsAssignableFrom(o.GetType()) then unbox o, staticFlags, null else o.GetType(), instanceFlags, o // Find a property that we can call and get the value let prop = typ.GetProperty(name, flags) if prop = null && instance = null then // The syntax can be also used to access nested types of a type let nested = typ.Assembly.GetType(typ.FullName + "+" + name) // Return nested type if we found one if nested = null then failwithf "Property nested type '%s' not found in '%s'." name typ.Name elif not ((typeof<'R>).IsAssignableFrom(typeof)) then let rname = (typeof<'R>.Name) failwithf "Cannot return nested type '%s' as a type '%s'." nested.Name rname else nested |> box |> unbox<'R> else if prop = null then // if we didn't find a nested type then search for a field let field = typ.GetField(name, flags) if not (isNull field) then field.GetValue(o) |> unbox<'R> else failwithf "Property '%s' found, but doesn't have 'get' method." name else // Call property and return result if we found some let meth = prop.GetGetMethod(true) try meth.Invoke(instance, [| |]) |> unbox<'R> with _ -> failwithf "Failed to get value of '%s' property (of type '%s')" name typ.Name let (?<-) (this:obj) (property:string) (value:'Value) = this.GetType().GetProperty(property).SetValue(this, value, null) ================================================ FILE: XSVim/SettingsPanel.fs ================================================ namespace XSVim open Gtk open MonoDevelop.Components open MonoDevelop.Core open MonoDevelop.Ide.Gui.Dialogs type SettingsWidget() as this = inherit Gtk.Box() let labelMapping = new Label("Insert mode escape binding", TooltipText = "2 character combination to escape from insert mode (jj / hh / jk etc)") let escapeMappingEntry = new Entry(2) let hbox = new HBox(false, 6) let vbox = new VBox(true, 6) let labelMappingTimeout = new Label("Insert mode mapping timeout (ms)", TooltipText = "Timeout (in milliseconds) before key is registered as an insert mode key press") let escapeMappingEntryTimeout = new Entry("1000") let checkDisableAutoCompleteNormalMode = new CheckButton("Disable intellisense in Normal mode. Use only if you are having issues with the intellisense drop down.", TooltipText = "Enabling this switches the setting Intellisense on keystroke globally when in Normal mode.") let labelKeyboardLayout = new Label("Keyboard Layout", TooltipText = "Select an keyboard layout for when not in insert mode") let dropDownKeyboardLayout = new ComboBox([| "Qwerty"; "Colemak"; "Dvorak" |]); do hbox.PackStart labelMapping hbox.PackStart escapeMappingEntry let hboxTimeout = new HBox(false, 6) hboxTimeout.PackStart labelMappingTimeout hboxTimeout.PackStart escapeMappingEntryTimeout let hboxKeyboardLayout = new HBox(false, 6) hboxKeyboardLayout.PackStart labelKeyboardLayout hboxKeyboardLayout.PackStart dropDownKeyboardLayout vbox.PackStart hbox vbox.PackStart hboxTimeout vbox.PackStart hboxKeyboardLayout vbox.Add hbox vbox.Add checkDisableAutoCompleteNormalMode this.Add vbox this.ShowAll() member this.EscapeMappingEntry = escapeMappingEntry member this.EscapeMappingEntryTimeout = escapeMappingEntryTimeout member this.DisableAutoCompleteNormalMode = checkDisableAutoCompleteNormalMode member this.KeyboardLayout = dropDownKeyboardLayout type SettingsPanel() = inherit OptionsPanel() let widget = new SettingsWidget() static let escapeMappingKey = "VimEscapeMapping" static let escapeMappingKeyTimeout = "VimEscapeMappingTimeout" static let disableAutoComplete = "VimDisableAutoComplete" static let keyboardLayout = "VimAlternateMapping" static member InsertModeEscapeMapping() = PropertyService.Get(escapeMappingKey, "") static member InsertModeEscapeMappingTimeout() = PropertyService.Get(escapeMappingKeyTimeout, 1000) static member AutoCompleteInNormalModeIsDisabled() = PropertyService.Get(disableAutoComplete, false) static member KeyboardLayout() = PropertyService.Get(keyboardLayout, "Qwerty") override x.Dispose() = widget.Dispose() override x.CreatePanelWidget() = widget.EscapeMappingEntry.Text <- SettingsPanel.InsertModeEscapeMapping() widget.EscapeMappingEntryTimeout.Text <- SettingsPanel.InsertModeEscapeMappingTimeout() |> string widget.DisableAutoCompleteNormalMode.Active <- SettingsPanel.AutoCompleteInNormalModeIsDisabled() widget.KeyboardLayout.Active <- match SettingsPanel.KeyboardLayout() with | "Colemak" -> 1 | "Dvorak" -> 2 | _ -> 0 widget.Show() Control.op_Implicit widget override x.ApplyChanges() = match widget.EscapeMappingEntry.Text.Length with | 2 | 0 -> PropertyService.Set(escapeMappingKey, widget.EscapeMappingEntry.Text) | _ -> let md = new MessageDialog (null, DialogFlags.Modal ||| DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Ok, "Mapping must be empty (not used) or 2 characters.") md.Show() PropertyService.Set(escapeMappingKeyTimeout, int widget.EscapeMappingEntryTimeout.Text) PropertyService.Set(disableAutoComplete, widget.DisableAutoCompleteNormalMode.Active) PropertyService.Set(keyboardLayout, widget.KeyboardLayout.ActiveText) ================================================ FILE: XSVim/SubstituteCommand.fs ================================================ namespace XSVim open System open MonoDevelop.Core open MonoDevelop.Core.ProgressMonitoring open MonoDevelop.Ide open MonoDevelop.Ide.FindInFiles type SubstituteScope = Selection | Document type Substitution = { find: string; replace: string; scope: SubstituteScope } module Substitute = let substitute substitution = let find = FindInFiles.FindReplace() let options = FindInFiles.FilterOptions() options.RegexSearch <- true if not(find.ValidatePattern(options, substitution.find)) then MessageService.ShowError (GettextCatalog.GetString ("Search pattern is invalid")); false elif not(find.ValidatePattern(options, substitution.replace)) then MessageService.ShowError (GettextCatalog.GetString ("Replace pattern is invalid")); false else use monitor = match IdeApp.Workbench with | null -> new ConsoleProgressMonitor() :> ProgressMonitor | workbench -> let monitor = workbench.ProgressMonitors.GetSearchProgressMonitor (true) monitor.PathMode <- PathMode.Hidden monitor :> ProgressMonitor let scope = match substitution.scope with | Document -> DocumentScope() :> Scope | Selection -> SelectionScope() :> Scope find.FindAll(scope, monitor, substitution.find, substitution.replace, options, Threading.CancellationToken.None) |> Seq.iter(fun res -> match monitor with | :? SearchProgressMonitor as mon -> mon.ReportResult res | _ -> printfn "%A" res) true ================================================ FILE: XSVim/TreeViewPads.fs ================================================ namespace XSVim open System open Gtk open MonoDevelop.Ide open MonoDevelop.Ide.Gui.Components open MonoDevelop.Ide.Gui.Pads open Reflection /// Set up TreeViewPads to accept hjkl keys module treeViewPads = let getTreeViewPads() = match IdeApp.Workbench |> Option.ofObj with | Some workbench -> workbench.Pads |> List.ofSeq |> List.choose(fun pad -> try // fetching pad.Content can throw when there is an exception // when initializing the pad match pad.Content with | :? TreeViewPad as pad -> Some pad | _ -> None with | _ -> None) | None -> List.empty let select (tree:TreeView) path = if tree.Selection.GetSelectedRows().Length > 0 then tree.Selection.UnselectAll() let column = tree.Columns.[0] tree.Selection.SelectPath path tree.SetCursor(path, column, false) let getSelectedNode (pad:TreeViewPad) = let (node:ITreeNavigator) = pad.TreeView?GetSelectedNode() node let getPath (tree:TreeView) = tree.Selection.GetSelectedRows().[0] let pathExists (store:TreeStore) path = let iter : TreeIter ref = ref Unchecked.defaultof<_> store.GetIter (iter, path) let moveDown (tree:TreeView) (store:TreeStore) pad = let selected = tree.Selection.GetSelectedRows() let pathExists = pathExists store if selected.Length > 0 then let path = selected.[0] path.Down() let node = getSelectedNode pad let res = pathExists path if res && node.Expanded then select tree path else let path = getPath tree path.Next() let res = pathExists path if res then select tree path else // parent, then sibling let path = getPath tree let _res = path.Up() let res = pathExists path if res then path.Next() let res = pathExists path if res then select tree path let moveUp (tree:TreeView) (store:TreeStore) pad = let selected = tree.Selection.GetSelectedRows() let pathExists = pathExists store if selected.Length > 0 then let path = selected.[0] let prev = path.Prev() let res = pathExists path if prev && res then select tree path let node = getSelectedNode pad if node.Expanded then // move to last child let path = getPath tree path.Down() let rec moveNext lastPath = path.Next() let res = pathExists path if res then moveNext (path.Copy()) else select tree lastPath moveNext (path.Copy()) else let path = getPath tree if path.Depth > 1 then let up = path.Up() let res = pathExists path if res && up then select tree path let mutable initialized = false /// Set up TreeViewPads to accept hjkl keys let initialize() = if not initialized then initialized <- true let errorPad = IdeApp.Workbench.Pads.ErrorsPad.Content let errorPadTree = errorPad?view if errorPadTree <> null then padTreeViews.initialize errorPadTree for pad in getTreeViewPads() do let (tree:TreeView) = pad.TreeView?Tree let (store:TreeStore) = pad.TreeView?Store let processKey (key:KeyPressEventArgs) = match key.Event.Key with | Gdk.Key.Escape -> dispatchCommand "MonoDevelop.Ide.Commands.ViewCommands.FocusCurrentDocument" key.RetVal <- false | Gdk.Key.l -> pad.TreeView?ExpandCurrentItem() key.RetVal <- true | Gdk.Key.h -> pad.TreeView?CollapseCurrentItem() key.RetVal <- true | Gdk.Key.j -> moveDown tree store pad key.RetVal <- true | Gdk.Key.k -> moveUp tree store pad key.RetVal <- true | _ -> () tree.KeyPressEvent.Add processKey ================================================ FILE: XSVim/Types.fs ================================================ namespace XSVim open System open System.Threading open System.Threading.Tasks open MonoDevelop.Ide open MonoDevelop.Ide.Editor type BeforeOrAfter = Before | After | OverSelection type CaretMode = Insert | Block type Selection = { linewise : bool content: string } type InsertModeEscapeKeyCombo = { insertModeEscapeKey1: string insertModeEscapeKey2: string insertModeEscapeTimeout: int } type KeyboardLayout = | Qwerty | Colemak | Dvorak type Config = { insertModeEscapeKey: InsertModeEscapeKeyCombo option keyboardLayout: KeyboardLayout } with static member Default = { insertModeEscapeKey = None; keyboardLayout = Qwerty } type Register = | Register of char | EmptyRegister type VimMode = | NormalMode | VisualMode | VisualBlockMode | VisualLineMode | InsertMode | ReplaceMode | ExMode of string // initial char typed to get to command line type MoveRightBehaviour = StopAtEndOfLine | MoveToNextLineAtEnd | IncludeDelimiter type MarkerJumpType = Offset | StartOfLine type Jump = | StartOfLineNumber of int | StartOfDocument | ToMark of string * MarkerJumpType | ToSearch of string | ToSearchBackwards of string | SearchAgain | SearchAgainBackwards | HalfPageUp | HalfPageDown | PageUp | PageDown | LastLine | FirstVisibleLine | MiddleVisibleLine | LastVisibleLine | ParagraphForwards | ParagraphBackwards type VimKey = | Esc | Ret | Left | Down | Up | Right | Backspace | Delete | Control of char | Super of char | Key of char | EscapeKey of char // The key is part of an insert escape mapping override x.ToString() = match x with | Esc -> "" | Backspace -> "" | Ret -> "" | Delete -> "" | Control k -> sprintf "" k | Super k -> sprintf "" k | Down -> "" | Up -> "" | Left -> "" | Right -> "" | EscapeKey c -> string c | Key c -> string c type Repeat = int type Offset = int type TextObject = | Jump of Jump | Character of Repeat | AWord | InnerWord | AWORD | InnerWORD | ASentence | InnerSentence | AParagraph | InnerParagraph | ABlock of string * string | InnerBlock of string * string | AQuotedBlock of char | InnerQuotedBlock of char | WholeLine | WholeLineIncludingDelimiter | ATag | InnerTag // motions | Up | Down | Left | Right of MoveRightBehaviour | FirstNonWhitespace | StartOfLine | EndOfLine | EndOfLineIncludingDelimiter | ToCharInclusive of string | ToCharInclusiveBackwards of string | ToCharExclusive of string | ToCharExclusiveBackwards of string | WordForwards | WORDForwards | WordBackwards | WORDBackwards | ForwardToEndOfWord | ForwardToEndOfWORD | BackwardToEndOfWord | BackwardToEndOfWORD | Nothing | CurrentLocation | SelectedText | SelectionStart | MatchingBrace | PrevUnmatchedBrace | NextUnmatchedBrace | PrevUnmatchedParen | NextUnmatchedParen | Offset of Offset | Range of Offset * Offset type CommandType = | Move | Visual | Yank of Register | Put of BeforeOrAfter | Delete | Substitute | DeleteWholeLines | DeleteLeft | BlockInsert of BeforeOrAfter | Change | SwitchMode of VimMode | Undo | Redo | JoinLines | Dispatch of obj | InsertLine of BeforeOrAfter | ReplaceChar of string | ResetKeys | DoNothing | Star of BeforeOrAfter | ToggleCase | InsertChar of VimKey | IncrementNumber | DecrementNumber | SetMark of string | IncrementalSearch of string | IncrementalSearchBackwards of string | SetSearchAction of CommandType | MacroStart of char | MacroEnd | ReplayMacro of char | NextTab | PreviousTab | Func of (unit -> unit) | EditorFunc of (TextEditor -> unit) | GotoPad of string | DelayedFunc of (TextEditor -> unit) * int | CancelFunc | ChangeState of VimState | Indent | UnIndent | EqualIndent | SelectionOtherEnd and VimAction = { repeat: int option commandType: CommandType textObject: TextObject } and Macro = Macro of char and VimSelection = { start: int; finish: int; mode: VimMode } and VimState = { keys: VimKey list mode: VimMode visualStartOffset: int lastSelection: VimSelection option findCharCommand: VimAction option // f,F,t or T command to be repeated with ; lastAction: VimAction list // used by . command to repeat the last action desiredColumn: int option undoGroup: IDisposable option statusMessage: string option searchAction: CommandType option // Delete, Change, Visual, Yank or Move when / or ? is pressed lastSearch: TextObject option // Last term searched for with / or ? macro: Macro option insertModeCancellationTokenSource: CancellationTokenSource option } with static member Default = { keys=[] mode=NormalMode visualStartOffset=0 lastSelection=None findCharCommand=None lastAction=[] desiredColumn=None undoGroup=None statusMessage=None lastSearch=None searchAction=None macro=None insertModeCancellationTokenSource=None } // shim for the build server which runs Mono 4.6.1 module Option = let inline defaultValue value = function | Some v -> v | None -> value [] module commandHelpers = let getCommand repeat commandType textObject = { repeat=repeat; commandType=commandType; textObject=textObject } let runOnce = getCommand (Some 1) let typeChar c = runOnce (InsertChar c) Nothing let wait = [ getCommand None DoNothing Nothing ] let switchMode mode = runOnce (SwitchMode mode) Nothing let dispatch command = runOnce (Dispatch command) Nothing let resetKeys = [ runOnce ResetKeys Nothing ] let func f = runOnce (Func f) Nothing let delayedFunc f ms = runOnce (DelayedFunc (f, ms)) Nothing let dispatchCommand command = IdeApp.CommandService.DispatchCommand command |> ignore let gotoPad padId = runOnce (GotoPad padId) Nothing ================================================ FILE: XSVim/WindowManagement.fs ================================================ namespace XSVim open MonoDevelop.Core open MonoDevelop.Ide open MonoDevelop.Ide.Commands open Reflection module Window = type Notebook = { isActive: bool activeTab: int tabs: string list } let dispatch command = IdeApp.CommandService.DispatchCommand command |> ignore let openDocument fileName = let (project:MonoDevelop.Projects.Project) = Unchecked.defaultof<_> IdeApp.Workbench.OpenDocument(fileName |> FilePath, project).Wait(System.Threading.CancellationToken.None) let switchToNotebook notebook = openDocument notebook.tabs.[notebook.activeTab] let forceClose() = IdeApp.Workbench.ActiveDocument.Close true |> Async.AwaitTask |> Async.Ignore let getNotebooks() = let (dockNotebookContainer: obj seq) = IdeApp.Workbench?RootWindow?TabControl?Container?GetNotebooks() let getFiles notebook = let tabs = notebook?Tabs let tabs' = tabs :> seq tabs' |> Seq.map(fun tab -> tab?Tooltip) |> List.ofSeq dockNotebookContainer |> Seq.map(fun notebook -> let firstChild = notebook?Children |> Array.tryHead let isActive = firstChild |> Option.map(fun tabstrip -> tabstrip?IsActiveNotebook) |> Option.defaultValue false { isActive=isActive; activeTab=notebook?CurrentTabIndex; tabs=getFiles notebook } ) |> List.ofSeq let tryFindActiveNoteBook() = getNotebooks() |> List.tryFind(fun notebook -> notebook.isActive) let tryActiveInactiveNoteBooks() = let notebooks = getNotebooks() notebooks |> List.tryFind(fun notebook -> notebook.isActive), notebooks |> List.tryFind(fun notebook -> not notebook.isActive) let nextTab() = tryFindActiveNoteBook() |> Option.iter(fun notebook -> let tabCount = notebook.tabs.Length let currentTabIndex = notebook.activeTab let index = if currentTabIndex < (tabCount-1) then currentTabIndex + 1 else 0 openDocument notebook.tabs.[index]) let previousTab() = tryFindActiveNoteBook() |> Option.iter(fun notebook -> let tabCount = notebook.tabs.Length let currentTabIndex = notebook.activeTab let index = if currentTabIndex > 0 then currentTabIndex - 1 else tabCount - 1 openDocument notebook.tabs.[index]) let switchWindow() = let notebook = getNotebooks() |> List.tryFind(fun notebook -> not notebook.isActive) notebook |> Option.iter switchToNotebook let leftWindow() = let notebooks = getNotebooks() if notebooks.Length = 2 && notebooks.[1].isActive then switchToNotebook notebooks.[0] let rightWindow() = let notebooks = getNotebooks() if notebooks.Length = 2 && notebooks.[0].isActive then switchToNotebook notebooks.[1] let private closeTabWithForce force = let closeFunc() = match force with | true -> forceClose() |> Async.RunSynchronously | false -> dispatch FileCommands.CloseFile match tryActiveInactiveNoteBooks() with | Some active, Some inactive when active.tabs.Length = 1 -> closeFunc() switchToNotebook inactive | Some active, _ when active.activeTab > 0 -> closeFunc() openDocument active.tabs.[active.activeTab-1] | Some active, _ when active.activeTab = 0 && active.tabs.Length > 1 -> closeFunc() openDocument active.tabs.[1] | _ -> closeFunc() let closeTab() = closeTabWithForce false let forceCloseTab() = closeTabWithForce true let gotoPad padId = IdeApp.Workbench.Pads |> Seq.tryFind(fun p -> p.Id = padId) |> Option.iter(fun pad -> pad.BringToFront(true)) ================================================ FILE: XSVim/XSVim.fs ================================================ namespace XSVim open System open System.Collections.Generic open System.Text.RegularExpressions open System.Threading open MonoDevelop.Core open MonoDevelop.Core.Text open MonoDevelop.Ide open MonoDevelop.Ide.Commands open MonoDevelop.Ide.Editor open MonoDevelop.Ide.Editor.Extension open Reflection [] module VimHelpers = let commandManager = IdeApp.CommandService |> Option.ofObj let dispatchCommand command = commandManager |> Option.iter(fun c -> c.DispatchCommand command |> ignore) let closingBraces = [')'; '}'; ']'] |> set let openingbraces = ['('; '{'; '[' ] |> set let markDict = Dictionary() let findNextBraceForwardsOnLine (editor:TextEditor) (line:IDocumentLine) = if closingBraces.Contains(editor.[editor.CaretOffset]) then Some editor.CaretOffset else seq { editor.CaretOffset .. line.EndOffset } |> Seq.tryFind(fun index -> openingbraces.Contains(editor.[index])) let findCharForwardsOnLine (editor:TextEditor) (line:IDocumentLine) startOffset character = let ch = char character seq { startOffset+1 .. line.EndOffset } |> Seq.tryFind(fun index -> editor.[index] = ch) let findCharBackwardsOnLine startOffset (editor:TextEditor) (line:IDocumentLine) matcher = seq { startOffset .. -1 .. line.Offset } |> Seq.tryFind (fun i -> matcher editor.[i]) let findCharBackwardsOnLineExclusive (editor:TextEditor) startOffset = findCharBackwardsOnLine (startOffset-1) editor let findCharBackwardsOnLineInclusive (editor:TextEditor) = findCharBackwardsOnLine editor.CaretOffset editor let findStringCharBackwardsOnLine (editor:TextEditor) (line:IDocumentLine) startOffset character = let ch = char character let f = findCharBackwardsOnLineExclusive editor startOffset f line ((=) ch) let findCharForwards (editor:TextEditor) character = let ch = char character seq { editor.CaretOffset+1 .. editor.Length-1 } |> Seq.tryFind(fun index -> editor.[index] = ch) let findCharBackwards (editor:TextEditor) character = let ch = char character seq { editor.CaretOffset .. -1 .. 0 } |> Seq.tryFind(fun index -> editor.[index] = ch) let findUnmatchedBlockDelimiter(editor:TextEditor) pos blockStartDelimiter blockEndDelimiter op = let blockStartShift = (blockStartDelimiter |> String.length) - 1 let blockEndShift = (blockEndDelimiter |> String.length) - 1 let text = editor.Text let rec findRec startCount endCount at = if (text.Length <= at || at < 0) then None else let next = op at 1 let st = try text.[at..(at+blockStartShift)] with | _ -> "" let en = try text.[at..(at+blockEndShift)] with | _ -> "" match st, en with | _, e when e = blockEndDelimiter && startCount < (endCount+1) -> Some at | _, e when e = blockEndDelimiter -> findRec startCount (endCount+1) next | s, _ when s = blockStartDelimiter -> findRec (startCount+1) endCount next | _,_ -> findRec startCount endCount next findRec 0 0 pos let rec findUnmatchedBlockStartDelimiter(editor:TextEditor) pos blockStartDelimiter blockEndDelimiter = findUnmatchedBlockDelimiter editor pos blockEndDelimiter blockStartDelimiter (-) let findUnmatchedBlockEndDelimiter(editor:TextEditor) pos blockStartDelimiter blockEndDelimiter = findUnmatchedBlockDelimiter editor pos blockStartDelimiter blockEndDelimiter (+) let isWordChar c = Char.IsLetterOrDigit c || c = '-' || c = '_' let isWORDChar c = not (Char.IsWhiteSpace c) let isNonBlankButNotWordChar c = isWORDChar c && not (isWordChar c) let isEOLChar c = c = '\r' || c = '\n' let isSpaceOrTab c = c = ' ' || c = '\t' let (|WhiteSpace|_|) c = if Char.IsWhiteSpace c then Some WhiteSpace else None let (|IsWordChar|_|) c = if isWordChar c then Some IsWordChar else None let (|EOLChar|_|) c = if isEOLChar c then Some EOLChar else None let findWordForwards (editor:TextEditor) commandType fWordChar = let findFromNonLetterChar index = match editor.[index], commandType with | EOLChar, Delete -> Some index | WhiteSpace, Move | WhiteSpace, Delete -> seq { index+1 .. editor.Length-1 } |> Seq.tryFind(fun index -> not (isSpaceOrTab editor.[index])) |> Option.bind(fun newIndex -> let findFirstWordOnLine startOffset = match editor.[startOffset] with | WhiteSpace -> seq { startOffset .. editor.Length-1 } |> Seq.tryFind(fun index -> let c = editor.[index] fWordChar c || c = '\n') | _ -> Some newIndex match editor.[newIndex] with | '\r' -> findFirstWordOnLine (newIndex + 2) | '\n' -> findFirstWordOnLine (newIndex + 1) | _ -> Some newIndex) | _ -> Some index if not (fWordChar editor.[editor.CaretOffset]) && fWordChar editor.[editor.CaretOffset + 1] then editor.CaretOffset + 1 |> Some else seq { editor.CaretOffset+1 .. editor.Length-1 } |> Seq.tryFind(fun index -> index = editor.Length || not (fWordChar editor.[index])) |> Option.bind findFromNonLetterChar let findWordBackwards (editor:TextEditor) commandType fWordChar = let findFromNonLetterChar index = match editor.[index], commandType with | WhiteSpace, Move -> seq { index .. -1 .. 0 } |> Seq.tryFind(fun index -> not (Char.IsWhiteSpace editor.[index])) | _ -> Some index if not (fWordChar editor.[editor.CaretOffset]) && fWordChar editor.[editor.CaretOffset - 1] then editor.CaretOffset - 1 |> Some else seq { editor.CaretOffset .. -1 .. 0 } |> Seq.tryFind(fun index -> index = editor.Length+1 || not (fWordChar editor.[index])) |> Option.bind findFromNonLetterChar let findPrevWord (editor:TextEditor) fWordChar = let result = Math.Max(editor.CaretOffset - 1, 0) let previous = fWordChar editor.[result] let rec findStartBackwards index previous isInIdentifier = let ch = editor.[index] let current = fWordChar ch match previous with | _ when index = 0 -> 0 | false when isInIdentifier -> index + 2 | _ -> findStartBackwards (index - 1) current previous findStartBackwards result previous previous let findCurrentWordEnd (editor:TextEditor) fWordChar = seq { editor.CaretOffset .. editor.Length - 2 } |> Seq.tryFind(fun index -> not (fWordChar editor.[index+1])) |> Option.defaultValue (editor.Length-1) let findWordEnd (editor:TextEditor) fWordChar = let currentWordEnd = findCurrentWordEnd editor fWordChar if editor.CaretOffset = currentWordEnd then let nextWordOffset = findWordForwards editor Move fWordChar match nextWordOffset with | Some offset -> let f = if fWordChar editor.[offset] then fWordChar else isNonBlankButNotWordChar editor.CaretOffset <- offset findCurrentWordEnd editor f | None -> editor.Length else currentWordEnd let findCurrentWordStart (editor:TextEditor) fWordChar = seq { editor.CaretOffset .. -1 .. 1 } |> Seq.tryFind(fun index -> not (fWordChar editor.[index-1])) |> Option.defaultValue 0 let paragraphBackwards (editor:TextEditor) = seq { editor.CaretLine-1 .. -1 .. 1 } |> Seq.tryFind(fun lineNr -> let line = editor.GetLineText lineNr String.IsNullOrWhiteSpace line) |> Option.bind(fun lineNr -> Some (editor.GetLine lineNr).Offset) let paragraphForwards (editor:TextEditor) = seq { editor.CaretLine+1 .. editor.LineCount } |> Seq.tryFind(fun lineNr -> let line = editor.GetLineText lineNr String.IsNullOrWhiteSpace line) |> Option.bind(fun lineNr -> Some (editor.GetLine lineNr).Offset) let getVisibleLines editor = let (lines:IDocumentLine seq) = editor?VisibleLines lines let getSortedVisibleLines editor = getVisibleLines editor |> Seq.sortBy(fun l -> l.LineNumber) /// the lines come back in random order let getVisibleLineCount editor = getVisibleLines editor |> Seq.length let getComparisonType (search:string) = match search with | s when s.ToLower() = s -> StringComparison.CurrentCultureIgnoreCase | _ -> StringComparison.CurrentCulture let findNextSearchOffset (editor:TextEditor) (search:string) (startOffset:int) = let comparison = getComparisonType search let index = editor.Text.IndexOf(search, startOffset, comparison) if index > -1 then Some index else let index = editor.Text.IndexOf(search, comparison) if index > -1 then Some index else None let findNextSearchOffsetBackwards (editor:TextEditor) (search:string) (startOffset:int) = let comparison = getComparisonType search let index = editor.Text.LastIndexOf(search, startOffset, comparison) if index > -1 then Some index else let index = editor.Text.LastIndexOf(search, editor.Length, comparison) if index > -1 then Some index else None let wordAtCaret (editor:TextEditor) = if isWordChar (editor.[editor.CaretOffset]) then let start = findCurrentWordStart editor isWordChar let finish = (findCurrentWordEnd editor isWordChar) let word = editor.GetTextAt(start, finish - start + 1) Some word else None let eofOnLine (line: IDocumentLine) = line.DelimiterLength = 0 let findQuoteTriplet (editor:TextEditor) line quoteChar = let firstBackwards = findCharBackwardsOnLine editor.CaretOffset editor line ((=) quoteChar) let firstForwards = findCharForwardsOnLine editor line editor.CaretOffset (string quoteChar) let secondForwards = match firstForwards with | Some offset when offset + 1 < editor.Length -> findCharForwardsOnLine editor line offset (string quoteChar) | _ -> None firstBackwards, firstForwards, secondForwards let inferDelimiter (editor:TextEditor) = editor.GetLines() |> Seq.tryFind(fun line -> line.DelimiterLength > 0) |> Option.map(fun line -> match line.UnicodeNewline with | UnicodeNewline.LF -> "\n" | UnicodeNewline.CRLF -> "\r\n" | UnicodeNewline.CR -> "\r" | _ -> editor.Options.DefaultEolMarker) |> Option.defaultValue editor.Options.DefaultEolMarker /// Get the range of the trailing or leading whitespace /// around a word when aw or aW is used let getAroundWordRange (editor:TextEditor) wordStart wordEnd = let hasLeadingWhiteSpace = wordStart > 1 && Char.IsWhiteSpace editor.[wordStart-1] let hasTrailingWhiteSpace = wordEnd < editor.Length && Char.IsWhiteSpace editor.[wordEnd] let line = editor.GetLine editor.CaretLine match hasTrailingWhiteSpace, hasLeadingWhiteSpace with | true, _ -> let finish = seq { wordEnd .. line.EndOffset - 2 } |> Seq.tryFind(fun index -> not (Char.IsWhiteSpace editor.[index+1])) |> Option.defaultValue (line.EndOffset-1) wordStart, finish + 1 | false, true -> let start = seq { wordStart .. -1 .. line.Offset } |> Seq.tryFind(fun index -> not (Char.IsWhiteSpace editor.[index-1])) |> Option.defaultValue line.Offset start, wordEnd | _ -> wordStart, wordEnd let getWordRange (editor:TextEditor) fWordChar = let wordStart = findCurrentWordStart editor fWordChar let wordEnd = findCurrentWordEnd editor fWordChar wordStart, wordEnd + 1 let getWhitespaceRange (editor:TextEditor) fWordChar = let prevWordEnd = findWordBackwards editor Move fWordChar |> Option.defaultValue editor.CaretOffset let nextWordEnd = findWordEnd editor fWordChar prevWordEnd + 1, nextWordEnd + 1 let rec findEnclosingTag(editor:TextEditor) pos = let search = seq { pos .. -1 .. 0 } |> Seq.tryFind(fun index -> editor.[index] = '<') match search with | Some startTagStart -> let m = Regex.Match(editor.GetTextBetween(startTagStart, editor.Length), "<([\w|:|\.]+).*?>", RegexOptions.Singleline) if m.Success then let tagName = m.Groups.[1].Value; let endTag = "" let startTagEnd = startTagStart + m.Length - 1 match findUnmatchedBlockEndDelimiter editor startTagEnd ("<" + tagName) endTag with | Some endTagStart -> Some (startTagStart, startTagEnd, endTagStart, endTagStart + endTag.Length) | None -> findEnclosingTag editor (startTagStart - 1) else None | None -> None let rec getRange (config:Config) (vimState:VimState) (editor:TextEditor) (command:VimAction) = let line = editor.GetLine editor.CaretLine let noOp = (editor.CaretOffset, editor.CaretOffset) match command.textObject with | Right behaviour -> let line = editor.GetLine editor.CaretLine let endOffset = match behaviour with | StopAtEndOfLine when editor.CaretColumn >= line.Length -> editor.CaretOffset | MoveToNextLineAtEnd when editor.[editor.CaretOffset+1] = '\r' -> editor.CaretOffset + 3 | MoveToNextLineAtEnd when editor.[editor.CaretOffset+1] = '\n' -> editor.CaretOffset + 2 | IncludeDelimiter -> let line = editor.GetLine editor.CaretLine if line.Length > 0 && editor.CaretColumn <= line.LengthIncludingDelimiter then editor.CaretOffset + 1 else editor.CaretOffset | _ -> editor.CaretOffset + 1 editor.CaretOffset, endOffset | Left -> editor.CaretOffset, if editor.CaretColumn > DocumentLocation.MinColumn && editor.[editor.CaretOffset-1] <> '\n' then editor.CaretOffset - 1 else editor.CaretOffset | Up -> editor.CaretOffset, if editor.CaretLine > DocumentLocation.MinLine then let column = let line = editor.GetLine (editor.CaretLine - 1) let desiredColumn = vimState.desiredColumn |> Option.defaultValue editor.CaretColumn if desiredColumn <= line.Length then desiredColumn else line.Length editor.LocationToOffset (new DocumentLocation(editor.CaretLine - 1, column)) else editor.CaretOffset | Down -> editor.CaretOffset, if editor.CaretLine < editor.LineCount then let column = let line = editor.GetLine (editor.CaretLine + 1) let desiredColumn = vimState.desiredColumn |> Option.defaultValue editor.CaretColumn if desiredColumn <= line.Length then desiredColumn else line.Length editor.LocationToOffset (new DocumentLocation(editor.CaretLine + 1, column)) else editor.CaretOffset | EndOfLine -> editor.CaretOffset, line.EndOffset-1 | Character repeat -> let line = editor.GetLine editor.CaretLine let endOffset = min line.EndOffset (editor.CaretOffset + repeat) editor.CaretOffset, endOffset | EndOfLineIncludingDelimiter -> editor.CaretOffset, if eofOnLine line then line.EndOffsetIncludingDelimiter else line.EndOffsetIncludingDelimiter-1 | StartOfLine -> editor.CaretOffset, line.Offset | Jump (StartOfLineNumber lineNumber) -> let line = editor.GetLine lineNumber editor.CaretOffset, line.Offset + editor.GetLineIndent(lineNumber).Length | Jump StartOfDocument -> editor.CaretOffset, 0 | FirstNonWhitespace -> editor.CaretOffset, line.Offset + editor.GetLineIndent(editor.CaretLine).Length | WholeLine -> line.Offset, line.EndOffset | WholeLineIncludingDelimiter -> if eofOnLine line && line.LineNumber <> 1 then let delimiter = inferDelimiter editor line.Offset-delimiter.Length, line.EndOffsetIncludingDelimiter else line.Offset, line.EndOffsetIncludingDelimiter | Jump LastLine -> let lastLine = editor.GetLine editor.LineCount editor.CaretOffset, lastLine.Offset | Jump FirstVisibleLine -> let firstLine = getSortedVisibleLines editor |> Seq.head editor.CaretOffset, firstLine.Offset | Jump MiddleVisibleLine -> let firstLine = getSortedVisibleLines editor |> Seq.head let lastLine = getSortedVisibleLines editor |> Seq.last let middleLineNumber = (lastLine.LineNumber - firstLine.LineNumber) / 2 + firstLine.LineNumber let middleLine = editor.GetLine middleLineNumber editor.CaretOffset, middleLine.Offset | Jump LastVisibleLine -> let lastLine = getSortedVisibleLines editor |> Seq.last editor.CaretOffset, lastLine.Offset | ToCharInclusiveBackwards c -> match findStringCharBackwardsOnLine editor line (editor.CaretOffset-1) c with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, editor.CaretOffset | ToCharExclusiveBackwards c -> let startOffset = match config.keyboardLayout, vimState.keys with | Qwerty, Key ';' :: _ when c = editor.[editor.CaretOffset-1].ToString() -> editor.CaretOffset-1 | Colemak, Key 'o' :: _ when c = editor.[editor.CaretOffset-1].ToString() -> editor.CaretOffset-1 | Dvorak, Key 's' :: _ when c = editor.[editor.CaretOffset-1].ToString() -> editor.CaretOffset-1 | _, _ -> editor.CaretOffset match findStringCharBackwardsOnLine editor line startOffset c with | Some index -> editor.CaretOffset, index+1 | None -> editor.CaretOffset, editor.CaretOffset | ToCharInclusive c -> match findCharForwardsOnLine editor line editor.CaretOffset c with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, editor.CaretOffset | ToCharExclusive c -> let startOffset = match config.keyboardLayout, vimState.keys with | Qwerty, Key ';' :: _ when c = editor.[editor.CaretOffset+1].ToString() -> editor.CaretOffset+1 | Colemak, Key 'o' :: _ when c = editor.[editor.CaretOffset+1].ToString() -> editor.CaretOffset+1 | Dvorak, Key 's' :: _ when c = editor.[editor.CaretOffset+1].ToString() -> editor.CaretOffset+1 | _, _ -> editor.CaretOffset match findCharForwardsOnLine editor line startOffset c with | Some index -> editor.CaretOffset, index-1 | None -> editor.CaretOffset, editor.CaretOffset | InnerBlock (startChar, endChar) -> let opening = findUnmatchedBlockStartDelimiter editor editor.CaretOffset startChar endChar let closing = findUnmatchedBlockEndDelimiter editor editor.CaretOffset startChar endChar match opening, closing with | Some start, Some finish -> start+1, finish | _, _ -> editor.CaretOffset, editor.CaretOffset | ABlock (startChar, endChar) -> let opening = findUnmatchedBlockStartDelimiter editor editor.CaretOffset startChar endChar let closing = findUnmatchedBlockEndDelimiter editor editor.CaretOffset startChar endChar match opening, closing with | Some start, Some finish when finish < editor.Length -> start, finish+1 | _, _ -> editor.CaretOffset, editor.CaretOffset | InnerQuotedBlock c -> match findQuoteTriplet editor line c with | Some start, Some finish, _ -> start + 1, finish // we're inside quotes | None, Some start, Some finish -> start + 1, finish // there's quoted text to the right | _, _,_ -> editor.CaretOffset, editor.CaretOffset | AQuotedBlock c -> match findQuoteTriplet editor line c with | Some start, Some finish, _ -> start, finish + 1 // we're inside quotes | None, Some start, Some finish -> start, finish + 1 // there's quoted text to the right | _, _,_ -> editor.CaretOffset, editor.CaretOffset | WordForwards -> match findWordForwards editor command.commandType isWordChar with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, editor.Length | WORDForwards -> match findWordForwards editor command.commandType isWORDChar with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, editor.CaretOffset | WordBackwards -> editor.CaretOffset, findPrevWord editor isWordChar | WORDBackwards -> editor.CaretOffset, findPrevWord editor isWORDChar | BackwardToEndOfWord -> match findWordBackwards editor command.commandType isWordChar with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, 0 | BackwardToEndOfWORD -> match findWordBackwards editor command.commandType isWORDChar with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, 0 | Jump ParagraphBackwards -> match paragraphBackwards editor with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, 0 | Jump ParagraphForwards -> match paragraphForwards editor with | Some index -> editor.CaretOffset, index | None -> editor.CaretOffset, editor.CaretOffset | InnerWord -> let getWordCharFunc = function | IsWordChar -> isWordChar | _ -> isNonBlankButNotWordChar match editor.[editor.CaretOffset] with | WhiteSpace -> let matchFunc c = Char.IsWhiteSpace c && (not (isEOLChar c)) getWordRange editor matchFunc | _ -> let fWordChar = getWordCharFunc editor.[editor.CaretOffset] getWordRange editor fWordChar | InnerWORD -> match editor.[editor.CaretOffset] with | WhiteSpace -> let matchFunc c = Char.IsWhiteSpace c && (not (isEOLChar c)) getWordRange editor matchFunc | _ -> getWordRange editor isWORDChar | AWord -> let getWordCharFunc = function | IsWordChar -> isWordChar | _ -> isNonBlankButNotWordChar match editor.[editor.CaretOffset] with | WhiteSpace -> getWhitespaceRange editor isWordChar | _ -> let fWordChar = getWordCharFunc editor.[editor.CaretOffset] let wordStart, wordEnd = getWordRange editor fWordChar getAroundWordRange editor wordStart wordEnd | AWORD -> match editor.[editor.CaretOffset] with | WhiteSpace -> getWhitespaceRange editor isWORDChar | _ -> let wordStart, wordEnd = getWordRange editor isWORDChar getAroundWordRange editor wordStart wordEnd | ForwardToEndOfWord -> let isWordCharAtOffset offset = isWordChar (editor.[offset]) match command.commandType, isWordCharAtOffset editor.CaretOffset, Char.IsWhiteSpace (editor.[editor.CaretOffset+1]) with | Change, true, true | Delete, true, true -> editor.CaretOffset, editor.CaretOffset | _ -> editor.CaretOffset, findWordEnd editor isWordChar | ForwardToEndOfWORD -> editor.CaretOffset, findWordEnd editor isWORDChar | ATag -> match findEnclosingTag editor editor.CaretOffset with | Some (startTagStart, _, _, endTagEnd) -> startTagStart, endTagEnd | None -> noOp | InnerTag -> match findEnclosingTag editor editor.CaretOffset with | Some (_, startTagEnd, endTagStart, _) -> startTagEnd + 1, endTagStart | None -> noOp | Jump HalfPageUp -> let visibleLineCount = getVisibleLineCount editor let halfwayUp = max 1 (editor.CaretLine - visibleLineCount / 2) editor.CaretOffset, editor.GetLine(halfwayUp).Offset | Jump HalfPageDown -> let visibleLineCount = getVisibleLineCount editor let halfwayDown = min editor.LineCount (editor.CaretLine + visibleLineCount / 2) editor.CaretOffset, editor.GetLine(halfwayDown).Offset | Jump PageUp -> let visibleLineCount = getVisibleLineCount editor let pageUp = max 1 (editor.CaretLine - visibleLineCount) editor.CaretOffset, editor.GetLine(pageUp).Offset | Jump PageDown -> let visibleLineCount = getVisibleLineCount editor let pageDown = min editor.LineCount (editor.CaretLine + visibleLineCount) editor.CaretOffset, editor.GetLine(pageDown).Offset | CurrentLocation -> editor.CaretOffset, editor.CaretOffset+1 | SelectedText -> let selection = editor.Selections |> Seq.head let lead = editor.LocationToOffset selection.Lead let anchor = editor.LocationToOffset selection.Anchor min lead anchor, max lead anchor | SelectionStart -> editor.CaretOffset, vimState.visualStartOffset | MatchingBrace -> match findNextBraceForwardsOnLine editor line with | Some offset -> let startOffset = editor.CaretOffset editor.CaretOffset <- offset EditActions.GotoMatchingBrace editor startOffset, editor.CaretOffset | _ -> editor.CaretOffset, editor.CaretOffset | PrevUnmatchedBrace -> match findUnmatchedBlockStartDelimiter editor editor.CaretOffset "{" "}" with | Some jumpPos -> editor.CaretOffset, jumpPos | None -> noOp | NextUnmatchedBrace -> match findUnmatchedBlockEndDelimiter editor editor.CaretOffset "{" "}" with | Some jumpPos -> editor.CaretOffset, jumpPos | None -> noOp | PrevUnmatchedParen -> match findUnmatchedBlockStartDelimiter editor editor.CaretOffset "(" ")" with | Some jumpPos -> editor.CaretOffset, jumpPos | None -> noOp | NextUnmatchedParen -> match findUnmatchedBlockEndDelimiter editor editor.CaretOffset "(" ")" with | Some jumpPos -> editor.CaretOffset, jumpPos | None -> noOp | Jump (ToMark (c, jumpType)) -> match markDict.TryGetValue c with | true, mark -> let offset = match jumpType with | MarkerJumpType.Offset -> mark.Offset | MarkerJumpType.StartOfLine -> let line = editor.GetLineByOffset mark.Offset line.Offset + editor.GetLineIndent(line).Length if editor.FileName.FullPath.ToString() = mark.FileName then editor.CaretOffset, offset else let document = IdeApp.Workbench.GetDocument(FilePath mark.FileName) let fileInfo = new MonoDevelop.Ide.Gui.FileOpenInformation (document.FileName) IdeApp.Workbench.OpenDocument(fileInfo) |> ignore editor.CaretOffset, offset | _ -> editor.CaretOffset, editor.CaretOffset | Offset offset -> editor.CaretOffset, offset | Range (startOffset, endOffset) -> startOffset, endOffset | Jump (ToSearch search) -> let startOffset = match config.keyboardLayout, vimState.keys with | Qwerty, [Key 'n'] | Qwerty, [Key 'N'] | Colemak, [Key 'k'] | Colemak, [Key 'K'] | Dvorak, [Key 'b'] | Dvorak, [Key 'B'] -> editor.CaretOffset + 1 | _ -> editor.CaretOffset let offset = findNextSearchOffset editor search startOffset |> Option.defaultValue editor.CaretOffset editor.CaretOffset, offset | Jump (ToSearchBackwards search) -> let offset = findNextSearchOffsetBackwards editor search editor.CaretOffset |> Option.defaultValue editor.CaretOffset editor.CaretOffset, offset | Jump SearchAgain -> match vimState.lastSearch with | Some search -> getRange config vimState editor { command with textObject = search } | None -> editor.CaretOffset, editor.CaretOffset | Jump SearchAgainBackwards -> match vimState.lastSearch with | Some search -> let reverseSearch = match search with | Jump (ToSearch s) -> Jump (ToSearchBackwards s) | Jump (ToSearchBackwards s) -> Jump (ToSearch s) | _ -> failwith "Invalid search" getRange config vimState editor { command with textObject = reverseSearch } | None -> editor.CaretOffset, editor.CaretOffset | _ -> editor.CaretOffset, editor.CaretOffset module Vim = LoggingService.LogInfo ("XSVim " + version) let registers = Dictionary() let editorStates = Dictionary() registers.[EmptyRegister] <- { linewise=false; content="" } let macros = Dictionary() let (|VisualModes|_|) = function | VisualMode | VisualLineMode | VisualBlockMode -> Some VisualModes | _ -> None let (|NotInsertMode|_|) = function | InsertMode | ReplaceMode | ExMode _ -> None | _ -> Some NotInsertMode let setSelection vimState (editor:TextEditor) (command:VimAction) (start:int) finish = match vimState.mode, command.commandType with | VisualMode, Move | VisualMode, SwitchMode _ -> let start, finish = if finish < vimState.visualStartOffset then finish, vimState.visualStartOffset + 1 else vimState.visualStartOffset, finish + if command.textObject = EndOfLine then 0 else 1 editor.SetSelection(start, min finish editor.Length) if editor.SelectionMode = SelectionMode.Block then EditActions.ToggleBlockSelectionMode editor | VisualBlockMode, Move | VisualBlockMode, SwitchMode _ -> let selectionStartLocation = editor.OffsetToLocation vimState.visualStartOffset let leftColumn, rightColumn = if editor.CaretColumn < selectionStartLocation.Column then editor.CaretColumn, selectionStartLocation.Column+1 else selectionStartLocation.Column, editor.CaretColumn+1 let topLine = min selectionStartLocation.Line editor.CaretLine let bottomLine = max selectionStartLocation.Line editor.CaretLine editor.SetSelection(DocumentLocation (topLine, leftColumn), DocumentLocation (bottomLine, rightColumn)) if editor.SelectionMode = SelectionMode.Normal then EditActions.ToggleBlockSelectionMode editor | VisualLineMode, Move | VisualLineMode, SwitchMode _ -> let startPos = min finish vimState.visualStartOffset let endPos = max finish vimState.visualStartOffset let startLine = editor.GetLineByOffset startPos let endLine = editor.GetLineByOffset endPos editor.SetSelection(startLine.Offset, endLine.EndOffsetIncludingDelimiter) if editor.SelectionMode = SelectionMode.Block then EditActions.ToggleBlockSelectionMode editor | _ -> editor.SetSelection(start, min finish editor.Length) let (|MoveUpOrDown|_|) = function | { commandType=Move; textObject=Up } | { commandType=Move; textObject=Down } -> Some MoveUpOrDown | _ -> None let (|LineWise|_|) = function | Up | Down | WholeLine | WholeLineIncludingDelimiter | Jump (ToMark (_, MarkerJumpType.StartOfLine)) | Jump LastLine -> Some LineWise | _ -> None let (|StartsWithDelimiter|_|) (s:string) = if s.StartsWith "\r\n" then Some "\r\n" elif s.StartsWith "\n" then Some "\n" else None let (|EndsWithDelimiter|_|) (s:string) = if s.EndsWith "\r\n" then Some "\r\n" elif s.EndsWith "\n" then Some "\n" else None let isLineWise vimState command = match vimState.mode with | VisualLineMode -> true | _ -> match command.textObject with | LineWise -> true | _ -> false let getSelectedText vimState (editor: TextEditor) command = let linewise = isLineWise vimState command { linewise=linewise; content=editor.SelectedText } let getCaretMode (editor:TextEditor) = let caret = editor.Carets.[0] let caretMode:bool = caret?IsInInsertMode match caretMode with | true -> Insert | false -> Block let setCaretMode (editor:TextEditor) caretMode = let currentMode = getCaretMode editor match currentMode, caretMode with | Block, Insert -> EditActions.SwitchCaretMode editor | Insert, Block -> EditActions.SwitchCaretMode editor | _ -> () let setAutoCompleteOnKeystroke value = if SettingsPanel.AutoCompleteInNormalModeIsDisabled() then IdeApp.Preferences.EnableAutoCodeCompletion.Set value |> ignore let switchToInsertMode (editor:TextEditor) state isInitial = let group = if isInitial then editor.OpenUndoGroup() |> Some else state.undoGroup setAutoCompleteOnKeystroke true setCaretMode editor Insert { state with mode = InsertMode; statusMessage = "-- INSERT --" |> Some; keys = []; undoGroup = group } let switchToNormalMode (editor:TextEditor) vimState = let lastSelection = match vimState.mode with | VisualModes -> Some { start = vimState.visualStartOffset; finish = editor.CaretOffset; mode = vimState.mode } | _ -> vimState.lastSelection editor.ClearSelection() setAutoCompleteOnKeystroke false setCaretMode editor Block // stupid hack to prevent intellisense in normal mode // https://github.com/mono/monodevelop/blob/fdbfbe89529bd9076e1906e7b70fdb51a9ae6b99/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/CompletionTextEditorExtension.cs#L153 if editor.SelectionMode = SelectionMode.Normal then EditActions.ToggleBlockSelectionMode editor vimState.undoGroup |> Option.iter(fun d -> d.Dispose()) if vimState.mode = InsertMode && editor.CaretColumn > 1 then EditActions.MoveCaretLeft editor { vimState with mode = NormalMode; lastSelection = lastSelection; undoGroup = None; statusMessage = None } let processVimKey (editor:TextEditor) = function | Key k -> editor.InsertAtCaret (string k) | VimKey.Delete -> EditActions.Delete editor | VimKey.Backspace -> EditActions.Backspace editor | VimKey.Left -> EditActions.MoveCaretLeft editor | VimKey.Right -> EditActions.MoveCaretRight editor | VimKey.Up -> EditActions.MoveCaretUp editor | VimKey.Down -> EditActions.MoveCaretDown editor | Ret -> EditActions.InsertNewLine editor | Super _ | Esc | EscapeKey _ | Control _ -> () let runCommand config vimState editor command = let delete state start finish = let finish = match command.textObject with | ForwardToEndOfWord | ForwardToEndOfWORD | EndOfLine | MatchingBrace | PrevUnmatchedBrace | NextUnmatchedBrace | PrevUnmatchedParen | NextUnmatchedParen | ToCharInclusive _ | ToCharExclusive _ -> finish + 1 | _ -> finish if start <> finish then if command.textObject <> SelectedText then setSelection state editor command start finish registers.[EmptyRegister] <- getSelectedText state editor command EditActions.ClipboardCut editor state let setMark c = if markDict.ContainsKey c then let marker = markDict.[c] markDict.Remove c |> ignore marker.Remove() let marker = Marker(editor, c) markDict.Add (c, marker) |> ignore let toggleCase state start finish = if command.textObject <> SelectedText then setSelection state editor command start finish let toggleChar = function | c when Char.IsUpper c -> Char.ToLower c | c when Char.IsLower c -> Char.ToUpper c | c -> c match state.mode with | VisualBlockMode -> let selectionStartLocation = editor.OffsetToLocation vimState.visualStartOffset let topLine = Math.Min(selectionStartLocation.Line, editor.CaretLine) let bottomLine = Math.Max(selectionStartLocation.Line, editor.CaretLine) editor.CaretColumn <- Math.Min(editor.CaretColumn, selectionStartLocation.Column) let offsets = [ topLine .. bottomLine ] |> List.map (fun c -> editor.LocationToOffset(c, editor.CaretColumn)) for i in offsets do let currentLetter = editor.[i] let isLetter = Char.IsLetter editor.[i] if isLetter then let c = toggleChar currentLetter editor.SetSelection(i, i+1) EditActions.Delete editor editor.InsertAtCaret (string c) EditActions.MoveCaretLeft editor | _ -> let swappedChars = editor.SelectedText |> Seq.map toggleChar |> Array.ofSeq EditActions.Delete editor editor.InsertAtCaret (swappedChars |> String) if command.textObject = SelectedText then editor.CaretOffset <- state.visualStartOffset state let modifyNumber f = let line = editor.GetLine editor.CaretLine let startOffset = let f = findCharBackwardsOnLineInclusive editor f line (fun c -> (c < '0' || c > '9') && c <> '-') let offset = match startOffset with | Some i -> i+1 | None -> editor.CaretOffset let lineToEnd = editor.Text.[offset .. line.EndOffset-1] let matches = Regex.Matches(lineToEnd, "-?[0-9]+", RegexOptions.Compiled) |> Seq.cast match matches |> Seq.tryHead with | Some m -> let i = Convert.ToInt32 (lineToEnd.[m.Index .. m.Index+m.Length-1]) let replacement = (f i) |> string editor.ReplaceText(line.Offset+m.Index+(line.Length-lineToEnd.Length), m.Length, replacement) let line = editor.GetLine editor.CaretLine editor.CaretOffset <- line.Offset + m.Index+(line.Length-lineToEnd.Length)+m.Length-1 vimState | None -> vimState let rec processCommands config count vimState command isInitial = let blockInsert fColumnSelect = let selectionStartLocation = editor.OffsetToLocation vimState.visualStartOffset let topLine = min selectionStartLocation.Line editor.CaretLine let bottomLine = max selectionStartLocation.Line editor.CaretLine editor.CaretColumn <- fColumnSelect editor.CaretColumn selectionStartLocation.Column editor.SetSelection(DocumentLocation (topLine, editor.CaretColumn), DocumentLocation (bottomLine, editor.CaretColumn)) if editor.SelectionMode = SelectionMode.Normal then EditActions.ToggleBlockSelectionMode editor switchToInsertMode editor vimState isInitial let start, finish = if editor.Length > 0 then VimHelpers.getRange config vimState editor command else // editor can have zero length when a tab containing it has just been closed 0, 0 let newState = match command.commandType with | Move -> match vimState.mode with | VisualModes -> editor.CaretOffset <- finish let finish = match command.textObject with | EndOfLine -> finish + 1 | _ -> finish setSelection vimState editor command start finish | _ -> () match command.textObject with | Jump _ -> setMark "\'" setMark "`" | _ -> () let newState = match command, vimState.desiredColumn with // don't change desired column if we already started moving up or down | MoveUpOrDown, Some _c -> editor.CaretOffset <- finish vimState | MoveUpOrDown, None -> let res = { vimState with desiredColumn = Some editor.CaretColumn } editor.CaretOffset <- finish res | _ -> editor.CaretOffset <- finish { vimState with desiredColumn = Some editor.CaretColumn } newState | Delete -> let linewise = isLineWise vimState command let start, finish = match linewise with | true -> let min = min start finish let maxOffset = max start finish let line = editor.GetLineByOffset min let finish = editor.GetLineByOffset(maxOffset).EndOffsetIncludingDelimiter if eofOnLine line && line.LineNumber <> 1 then let delimiter = inferDelimiter editor line.Offset-delimiter.Length, finish else line.Offset, finish | false -> start, finish let newState = delete vimState start finish let offsetBeforeDelimiter = match linewise with | true -> let line = editor.GetLineByOffset(editor.CaretOffset) let line = if editor.CaretOffset = editor.Length && editor.Length > 0 && editor.[editor.Length-1] = '\n' && line.PreviousLine <> null then line.PreviousLine else line line.Offset + editor.GetLineIndent(line.LineNumber).Length | false when editor.CaretOffset < editor.Length && editor.CaretOffset > 0 -> let charAtCaret = editor.[editor.CaretOffset] let previous = editor.[editor.CaretOffset - 1] if isEOLChar charAtCaret && not (isEOLChar previous) then editor.CaretOffset - 1 else editor.CaretOffset | _ -> editor.CaretOffset editor.CaretOffset <- max offsetBeforeDelimiter 0 newState | Indent -> let line = editor.GetLineByOffset(finish) setSelection vimState editor command start line.EndOffset EditActions.IndentSelection editor editor.ClearSelection() vimState | UnIndent -> let line = editor.GetLineByOffset(finish) setSelection vimState editor command start line.EndOffset EditActions.UnIndentSelection editor editor.ClearSelection() vimState | EqualIndent -> // Always work top to bottom setSelection vimState editor command start finish dispatchCommand CodeFormatting.CodeFormattingCommands.FormatBuffer editor.ClearSelection() processCommands config 1 vimState (runOnce Move FirstNonWhitespace) false | Substitute -> let newState = delete vimState start finish switchToInsertMode editor newState isInitial | ToggleCase -> toggleCase vimState start finish | DeleteWholeLines -> let min = min start finish let max = max start finish let start = editor.GetLineByOffset(min).Offset let finish = editor.GetLineByOffset(max).EndOffsetIncludingDelimiter delete vimState start finish | DeleteLeft -> if editor.CaretColumn > 1 then delete vimState (editor.CaretOffset - 1) editor.CaretOffset else vimState | Change -> let finish = if count <> 1 && editor.[finish] = ' ' then finish + 1 else finish let state = delete vimState start finish switchToInsertMode editor state isInitial | Yank register -> let finish = match command.textObject with | ForwardToEndOfWord | ForwardToEndOfWORD | EndOfLine | ToCharInclusive _ | ToCharExclusive _ -> finish + 1 | _ -> finish if command.textObject <> SelectedText then setSelection vimState editor command start finish registers.[register] <- getSelectedText vimState editor command if register = EmptyRegister then EditActions.ClipboardCopy editor editor.ClearSelection() match vimState.mode with | VisualModes -> editor.CaretOffset <- vimState.visualStartOffset | _ -> () processCommands config 1 vimState (runOnce (SwitchMode NormalMode) Nothing) false | Put Before -> if registers.[EmptyRegister].linewise then editor.CaretOffset <- editor.GetLine(editor.CaretLine).Offset EditActions.ClipboardPaste editor EditActions.MoveCaretUp editor EditActions.MoveCaretToLineStart editor else EditActions.ClipboardPaste editor vimState | Put After -> if registers.[EmptyRegister].linewise then if editor.CaretLine = editor.LineCount then let line = editor.GetLine(editor.CaretLine) let delimiter = inferDelimiter editor match registers.[EmptyRegister].content with | StartsWithDelimiter _delimiter -> () | _ -> editor.InsertText(editor.Length, delimiter) editor.CaretOffset <- editor.Length EditActions.ClipboardPaste editor if eofOnLine line then match registers.[EmptyRegister].content with | EndsWithDelimiter clipboardDelimiter -> editor.RemoveText(editor.Length-clipboardDelimiter.Length, clipboardDelimiter.Length) | _ -> () editor.CaretOffset <- line.Offset EditActions.MoveCaretDown editor EditActions.MoveCaretToLineStart editor else let line = editor.GetLine(editor.CaretLine) editor.CaretOffset <- line.EndOffsetIncludingDelimiter EditActions.ClipboardPaste editor editor.CaretOffset <- line.Offset EditActions.MoveCaretDown editor EditActions.MoveCaretToLineStart editor else EditActions.MoveCaretRight editor EditActions.ClipboardPaste editor EditActions.MoveCaretLeft editor vimState | Put OverSelection -> EditActions.ClipboardPaste editor { vimState with mode = NormalMode } | SelectionOtherEnd -> let offset = editor.CaretOffset editor.CaretOffset <- vimState.visualStartOffset let start, finish = if offset > vimState.visualStartOffset then vimState.visualStartOffset, offset + 1 else offset, vimState.visualStartOffset + 1 editor.SetSelection(start, finish) { vimState with visualStartOffset = offset } | Visual -> editor.SetSelection(start, finish); vimState | Undo -> EditActions.Undo editor; editor.ClearSelection(); vimState | Redo -> EditActions.Redo editor; vimState | JoinLines -> let lastColumn = editor.GetLine(editor.CaretLine).Length EditActions.JoinLines editor editor.CaretColumn <- lastColumn + 1 vimState | ReplaceChar c -> if editor.CaretOffset < editor.Length && not (isEOLChar editor.[editor.CaretOffset]) then editor.SetSelection(editor.CaretOffset, editor.CaretOffset+1) EditActions.Delete editor match c with | StartsWithDelimiter _ -> EditActions.InsertNewLine editor | _ -> editor.InsertAtCaret c EditActions.MoveCaretLeft editor vimState | SetMark c -> setMark c vimState | InsertLine Before -> EditActions.InsertNewLineAtEnd editor vimState | InsertLine After -> if editor.CaretLine = 1 then EditActions.MoveCaretToLineStart editor EditActions.InsertNewLine editor EditActions.MoveCaretUp editor vimState else EditActions.MoveCaretUp editor EditActions.InsertNewLineAtEnd editor vimState | Dispatch command -> dispatchCommand command ; vimState | ResetKeys -> { vimState with keys = [] } | BlockInsert Before -> blockInsert min | BlockInsert After -> blockInsert (fun s f -> (max s f) + 1) | SwitchMode mode -> match mode with | NormalMode -> let state = switchToNormalMode editor vimState if vimState.mode = InsertMode then MonoDevelop.Ide.CodeCompletion.CompletionWindowManager.HideWindow () processCommands config 1 state (runOnce (SetMark ".") Nothing) false else state | VisualMode | VisualLineMode | VisualBlockMode -> setCaretMode editor Block let start, finish = VimHelpers.getRange config vimState editor command let statusMessage = match mode with | VisualMode -> Some "-- VISUAL --" | VisualLineMode -> Some "-- VISUAL LINE --" | VisualBlockMode -> Some "-- VISUAL BLOCK --" | _ -> None let newState = { vimState with mode = mode; visualStartOffset = editor.CaretOffset; statusMessage = statusMessage } setAutoCompleteOnKeystroke false setSelection newState editor command start finish match mode, editor.SelectionMode with | VisualBlockMode, SelectionMode.Normal -> EditActions.ToggleBlockSelectionMode editor | _, SelectionMode.Block -> EditActions.ToggleBlockSelectionMode editor | _ -> () newState | InsertMode -> switchToInsertMode editor vimState isInitial | ReplaceMode -> let undoGroup = if isInitial then editor.OpenUndoGroup() |> Some else vimState.undoGroup { vimState with mode = ReplaceMode; statusMessage = "-- REPLACE --"|> Some; undoGroup = undoGroup } | ExMode c -> { vimState with mode = (ExMode c); statusMessage = string c |> Some } | Star After -> match wordAtCaret editor with | Some word -> let matches = Regex.Matches(editor.Text, sprintf @"\b%s\b" word) |> Seq.cast let m = matches |> Seq.tryFind(fun m -> m.Index > editor.CaretOffset) let offset = match m with | Some m -> m.Index | None -> let m = matches |> Seq.head m.Index editor.CaretOffset <- offset { vimState with lastSearch = Some (Jump (ToSearch word)) } | None -> processCommands config 1 vimState (runOnce Move WordForwards) isInitial | Star Before -> match wordAtCaret editor with | Some word -> let matches = Regex.Matches(editor.Text, sprintf @"\b%s\b" word) |> Seq.cast let start = findCurrentWordStart editor isWordChar let m = matches |> Seq.tryFindBack(fun m -> m.Index < start) let offset = match m with | Some m -> m.Index | None -> let m = matches |> Seq.last m.Index editor.CaretOffset <- offset { vimState with lastSearch = Some (Jump (ToSearchBackwards word)) } | None -> vimState | InsertChar c -> match vimState.mode with | InsertMode -> processVimKey editor c vimState | _ -> vimState | IncrementNumber -> modifyNumber (fun i -> i + 1) | DecrementNumber -> modifyNumber (fun i -> i - 1) | IncrementalSearch search -> findNextSearchOffset editor search editor.CaretOffset |> Option.iter(fun index -> editor.SetSelection(index, index + search.Length) editor.ScrollTo(index)) vimState | IncrementalSearchBackwards search -> findNextSearchOffsetBackwards editor search editor.CaretOffset |> Option.iter(fun index -> editor.SetSelection(index, index + search.Length) editor.ScrollTo(index)) vimState | SetSearchAction command -> { vimState with searchAction = Some command } | MacroStart c -> macros.[c] <- [] { vimState with macro = Macro (char c) |> Some } | MacroEnd -> { vimState with macro = None } | ReplayMacro c -> let getCount repeat = repeat |> Option.defaultValue 1 let rec runMacro state actions = match actions with | [ only ] -> processCommands config (getCount only.repeat) state only false | h :: t -> let newState = processCommands config (getCount h.repeat) state h false runMacro newState t | [] -> state runMacro vimState macros.[c] | NextTab -> Window.nextTab() vimState | PreviousTab -> Window.previousTab() vimState | Func f -> f() vimState | EditorFunc f -> f editor vimState | GotoPad padId -> Window.gotoPad padId vimState | ChangeState s -> s | DelayedFunc (f, ms) -> let token = new CancellationTokenSource() let work = async { do! Async.Sleep ms if (not token.IsCancellationRequested) then Runtime.RunInMainThread(fun _ -> f editor) |> ignore } Async.Start(work, token.Token) { vimState with insertModeCancellationTokenSource = Some token } | CancelFunc -> vimState.insertModeCancellationTokenSource |> Option.iter(fun token -> token.Cancel()) { vimState with insertModeCancellationTokenSource = None } | _ -> vimState match count with | 1 -> newState | _ -> processCommands config (count-1) newState command false let count = command.repeat |> Option.defaultValue 1 processCommands config count vimState command true let (|Digit|_|) character = if character >= "0" && character <= "9" then Some (Convert.ToInt32 character) else None let (|OneToNine|_|) character = if character >= "1" && character <= "9" then Some (Convert.ToInt32 character) else None let (|RegisterMatch|_|) = function | c -> Some (Register (Char.Parse c)) let (|BlockDelimiter|_|) layout character = let pairs = [ "[", ("[", "]") "]", ("[", "]") "(", ("(", ")") ")", ("(", ")") "b", ("(", ")") "{", ("{", "}") "}", ("{", "}") "B", ("{", "}") "<", ("<", ">") ">", ("<", ">") ] |> dict let mappedChar = remap layout character if pairs.ContainsKey mappedChar then Some pairs.[mappedChar] else None let (|QuoteDelimiter|_|) layout character = let mappedChar = remap layout character if Array.contains mappedChar [| "\""; "'"; "`"|] then Some mappedChar else None let (|Movement|_|) layout keys = let remappedKeys = keys |> List.map (fun k -> remap layout k) match remappedKeys with | [""] | ["h"] -> Some Left | [""] | ["j"] -> Some Down | [""] | ["k"] -> Some Up | [""] | ["l"] -> Some (Right StopAtEndOfLine) | [" "] -> Some (Right MoveToNextLineAtEnd) | ["$"] -> Some EndOfLine | ["^"] -> Some FirstNonWhitespace | ["0"] -> Some StartOfLine | ["_"] -> Some FirstNonWhitespace | ["w"] -> Some WordForwards | ["W"] -> Some WORDForwards | ["b"] -> Some WordBackwards | ["B"] -> Some WORDBackwards | ["e"] -> Some ForwardToEndOfWord | ["E"] -> Some ForwardToEndOfWORD | ["g"; "e"] -> Some BackwardToEndOfWord | ["g"; "E"] -> Some BackwardToEndOfWORD | ["{"] -> Some (Jump ParagraphBackwards) | ["}"] -> Some (Jump ParagraphForwards) | ["%"] -> Some MatchingBrace | ["["; "{"] -> Some PrevUnmatchedBrace | ["]"; "}"] -> Some NextUnmatchedBrace | ["["; "("] -> Some PrevUnmatchedParen | ["]"; ")"] -> Some NextUnmatchedParen | ["G"] -> Some (Jump LastLine) | ["H"] -> Some (Jump FirstVisibleLine) | ["M"] -> Some (Jump MiddleVisibleLine) | ["L"] -> Some (Jump LastVisibleLine) | [""] -> Some (Jump HalfPageDown) | [""] -> Some (Jump HalfPageUp) | [""] -> Some (Jump PageDown) | [""] -> Some (Jump PageUp) | ["n"] -> Some (Jump SearchAgain) | ["N"] -> Some (Jump SearchAgainBackwards) | ["'"; c] -> Some (Jump (ToMark (c, MarkerJumpType.StartOfLine))) | ["`"; c] -> Some (Jump (ToMark (c, MarkerJumpType.Offset))) | _ -> None let unfinishedMovements = [ "g"; "["; "]"; "@"; "m"; "`"; "'" ] |> set let (|UnfinishedMovement|_|) layout character = let remappedCharacter = remap layout character if unfinishedMovements.Contains remappedCharacter then Some UnfinishedMovement else None let (|IndentChar|_|) layout key = match remap layout key with | ">" -> Some Indent | "<" -> Some UnIndent | "=" -> Some EqualIndent | _ -> None let (|FindChar|_|) layout key = match remap layout key with | "f" -> Some ToCharInclusive | "F" -> Some ToCharInclusiveBackwards | "t" -> Some ToCharExclusive | "T" -> Some ToCharExclusiveBackwards | _ -> None let (|SearchChar|_|) layout key = match remap layout key with | "/" -> Some (SearchChar '/') | "?" -> Some (SearchChar '?') | _ -> None let (|Action|_|) layout key = match remap layout key with | "d" -> Some Delete | "c" -> Some Change | "v" -> Some Visual | "y" -> Some (Yank EmptyRegister) | ">" -> Some Indent | "<" -> Some UnIndent | _ -> None let (|ModeChange|_|) layout key = match remap layout key with | "i" -> Some InsertMode | "v" -> Some VisualMode | "" -> Some VisualBlockMode | "" -> Some VisualBlockMode | "V" -> Some VisualLineMode | "R" -> Some ReplaceMode | _ -> None let (|Escape|_|) = function | "" | "" | "" -> Some Escape | _ -> None let (|RemappedMatches|_|) layout matchList unmappedList = let mappedList = unmappedList |> List.map (fun x -> remap layout x) if mappedList = matchList then Some matchList else None let (|RemappedMatchesChar|_|) layout matchChar unmappedChar = // let mappedChar = remap layout unmappedChar if mappedChar = matchChar then Some matchChar else None let getInsertModeEscapeCombo config = match config.insertModeEscapeKey with | Some combo -> combo.insertModeEscapeKey1, combo.insertModeEscapeKey2, combo.insertModeEscapeTimeout | None -> "", "", 0 let parseKeys (state:VimState) (config: Config) = let layout = config.keyboardLayout; let keyList = state.keys |> List.map string let numericArgument, keyList = match keyList, state.mode with | "r" :: _, _ | [ _ ], ReplaceMode | FindChar layout _ :: _, _ -> None, keyList // 2dw -> 2, dw | OneToNine d1 :: Digit d2 :: Digit d3 :: Digit d4 :: t, _ -> Some (d1 * 1000 + d2 * 100 + d3 * 10 + d4), t | OneToNine d1 :: Digit d2 :: Digit d3 :: t, _ -> Some (d1 * 100 + d2 * 10 + d3), t | OneToNine d1 :: Digit d2 :: t, _ -> Some (d1 * 10 + d2), t | OneToNine d :: t, _ -> Some (d), t // d2w -> 2, dw | c :: OneToNine d1 :: Digit d2 :: Digit d3 :: Digit d4 :: t, _ -> Some (d1 * 1000 + d2 * 100 + d3 * 10 + d4), c::t | c :: OneToNine d1 :: Digit d2 :: Digit d3 :: t, _ -> Some (d1 * 100 + d2 * 10 + d3), c::t | c :: OneToNine d1 :: Digit d2 :: t, _ -> Some (d1 * 10 + d2), c::t | c :: OneToNine d :: t, _ -> Some d, c::t | _ -> None, keyList let run = getCommand numericArgument let runInVisualMode actions = [ yield switchMode VisualMode; yield! actions; yield switchMode NormalMode ] let runInVisualLineMode actions = [ yield switchMode VisualLineMode; yield! actions; yield switchMode NormalMode ] LoggingService.LogDebug (sprintf "%A %A" state.mode keyList) let newState = match keyList with | [ FindChar layout m; c ] -> { state with findCharCommand = run Move ( m c ) |> Some } | _ -> state let insertModeEscapeFirstChar, insertModeEscapeSecondChar, insertModeTimeout = getInsertModeEscapeCombo config let action = match state.mode, keyList with | VisualBlockMode, [ Escape ] -> [ switchMode NormalMode; run Move SelectionStart ] | NormalMode, [ Escape ] -> [ yield! resetKeys; yield dispatch "MonoDevelop.Ide.Commands.ViewCommands.FocusCurrentDocument" ] | _, [ Escape ] -> [ switchMode NormalMode ] | InsertMode, [ c ] when c = insertModeEscapeFirstChar -> delayedFunc (fun editor -> editor.InsertAtCaret insertModeEscapeFirstChar let oldState = editorStates.[editor.FileName] editorStates.[editor.FileName] <- { oldState with keys = [] } ) insertModeTimeout :: wait | InsertMode, [ c1; c2 ] when c1 = insertModeEscapeFirstChar && c2 = insertModeEscapeSecondChar -> [ run CancelFunc Nothing; switchMode NormalMode ] | InsertMode, [ c; _ ] when c = insertModeEscapeFirstChar -> [ run CancelFunc Nothing run (ChangeState { state with keys = [] }) Nothing typeChar (Key (char insertModeEscapeFirstChar)) ] | NotInsertMode, RemappedMatches layout [ "G" ] _ -> match numericArgument with | Some lineNumber -> [ runOnce Move (Jump (StartOfLineNumber lineNumber)) ] | None -> [ runOnce Move (Jump LastLine) ] | NormalMode, [ IndentChar layout _ ] -> wait | NormalMode, [ IndentChar layout _ ; RemappedMatchesChar layout "g" _ ] -> wait | NormalMode, [ IndentChar layout indent; RemappedMatchesChar layout "G" _ ] -> match numericArgument with | Some lineNumber -> [ runOnce Indent (Jump (StartOfLineNumber lineNumber)) ] | None -> [ runOnce indent (Jump LastLine) ] | NormalMode, [ IndentChar layout indent; RemappedMatchesChar layout "g" _; RemappedMatchesChar layout "g" _ ] -> let lineNumber = match numericArgument with Some n -> n | None -> 1 [ runOnce indent (Jump (StartOfLineNumber lineNumber)) ] | NormalMode, RemappedMatches layout [ ">"; ">" ] _ -> [ run Indent WholeLine ] | NormalMode, RemappedMatches layout [ "<"; "<" ] _ -> [ run UnIndent WholeLine ] | NormalMode, RemappedMatches layout [ "="; "=" ] _ -> [ run EqualIndent WholeLine ] | NormalMode, RemappedMatches layout [ "V" ] _ -> match numericArgument with | Some lines -> [ switchMode VisualLineMode; getCommand (lines-1 |> Some) Move Down ] | None -> [ switchMode VisualLineMode ] | NormalMode, RemappedMatches layout [ "v" ] _ -> match numericArgument with | Some chars -> [ switchMode VisualMode; getCommand (chars-1 |> Some) Move (Right StopAtEndOfLine) ] | None -> [ switchMode VisualMode ] | NormalMode, RemappedMatches layout [ "d"; "G" ] _ -> [ runOnce DeleteWholeLines (Jump LastLine)] | NormalMode, RemappedMatches layout [ "d"; "j" ] _ -> let numberOfLines = match numericArgument with | Some lines -> lines | None -> 1 runInVisualLineMode [ getCommand (numberOfLines |> Some) Move Down; runOnce Delete SelectedText ] | NormalMode, RemappedMatches layout [ "d"; "k" ] _ -> let numberOfLines = match numericArgument with | Some lines -> lines | None -> 1 runInVisualLineMode [ getCommand (numberOfLines |> Some) Move Up; runOnce Delete SelectedText; ] | NotInsertMode, [ (Action layout _) ; UnfinishedMovement layout _] -> wait | NotInsertMode, [ UnfinishedMovement layout _ ] -> wait | NormalMode, RemappedMatches layout [ "d"; "g"; "g" ] _ -> [ runOnce DeleteWholeLines (Jump StartOfDocument)] | ReplaceMode, [ c ] -> [ run (ReplaceChar c) Nothing; run Move (Right IncludeDelimiter) ] | NotInsertMode, Movement layout m -> [ run Move m ] | NotInsertMode, [ FindChar layout m; c ] -> [ run Move (m c) ] | NormalMode, IndentChar layout indent :: Movement layout m -> match numericArgument with | None -> [ run indent m ] | Some lines -> runInVisualMode [ getCommand (lines-1 |> Some) Move Down; runOnce indent SelectedText ] | NormalMode, Action layout action :: Movement layout m when numericArgument = None -> [ run action m ] | NormalMode, Action layout action :: Movement layout m -> match action, m with | Delete, _ | Yank _, _ -> match m with | WordForwards -> runInVisualMode [ run Move ForwardToEndOfWord; runOnce Move (Right StopAtEndOfLine); runOnce action SelectedText; ] | WORDForwards -> runInVisualMode [ run Move ForwardToEndOfWORD; runOnce Move (Right StopAtEndOfLine); runOnce action SelectedText; ] | WordBackwards -> runInVisualMode [ runOnce Move Left; run Move ForwardToEndOfWord; runOnce action SelectedText; ] | WORDBackwards -> runInVisualMode [ runOnce Move Left; run Move ForwardToEndOfWORD; runOnce action SelectedText; ] | _ -> runInVisualMode [ run Move m; runOnce action SelectedText; ] | _ -> [ run action m ] | NormalMode, RemappedMatches layout [ "u" ] _ -> [ run Undo Nothing ] | NormalMode, [ "" ] -> [ run Redo Nothing ] | NormalMode, RemappedMatches layout [ "d"; "d" ] _ -> match numericArgument with | None -> [ run Delete WholeLine ] | Some lines -> [ switchMode VisualLineMode getCommand (lines-1 |> Some) Move Down runOnce Delete SelectedText switchMode NormalMode runOnce Move FirstNonWhitespace ] | NormalMode, RemappedMatches layout [ "c"; "c" ] _ -> [ run Change WholeLine ] | NormalMode, RemappedMatches layout ["\""] _ -> wait | NormalMode, [RemappedMatchesChar layout "\"" _; _ ] -> wait | NormalMode, [RemappedMatchesChar layout "\"" _; _; RemappedMatchesChar layout "y" _ ] -> wait | NormalMode, RemappedMatchesChar layout "\"" _ :: (RegisterMatch r) :: RemappedMatchesChar layout "y" _ :: (Movement layout m) -> [ run (Yank r) m] | NormalMode, RemappedMatches layout [ "y"; "y" ] _ | NormalMode, RemappedMatches layout [ "Y" ] _ -> match numericArgument with | Some lines -> runInVisualLineMode [ getCommand (lines-1 |> Some) Move Down; runOnce (Yank EmptyRegister) SelectedText ] | None -> [ runOnce (Yank EmptyRegister) WholeLineIncludingDelimiter ] | NormalMode, RemappedMatches layout [ "C" ] _ -> [ run Change EndOfLine ] | NormalMode, RemappedMatches layout [ "D" ] _ -> [ run Delete EndOfLine ] | NormalMode, RemappedMatches layout [ "x" ] _ -> [ runOnce Delete (Character (numericArgument |> Option.defaultValue 1)) ] | NormalMode, RemappedMatches layout [ "X" ] _ -> [ run DeleteLeft Nothing ] | NormalMode, RemappedMatches layout [ "s" ] _ -> [ run Substitute CurrentLocation] | NormalMode, RemappedMatches layout [ "S" ] _ -> [ run Delete WholeLine; runOnce (InsertLine After) Nothing; switchMode InsertMode ] | NormalMode, RemappedMatches layout [ "p" ] _ -> [ run (Put After) Nothing ] | NormalMode, RemappedMatches layout [ "P" ] _ -> [ run (Put Before) Nothing ] | VisualModes, RemappedMatches layout [ "p" ] _ -> [ run (Put OverSelection) Nothing ] | VisualModes, RemappedMatches layout [ "P" ] _ -> [ run (Put OverSelection) Nothing ] | NormalMode, RemappedMatches layout [ "J" ] _ -> [ run JoinLines Nothing ] | NotInsertMode, RemappedMatches layout [ "*" ] _ -> [ run (Star After) Nothing ] | NotInsertMode, RemappedMatches layout [ "#" ] _ -> [ run (Star Before) Nothing ] | NotInsertMode, RemappedMatches layout [ "£" ] _ -> [ run (Star Before) Nothing ] | NotInsertMode, [ SearchChar layout c ] -> [ switchMode (ExMode (string c)); runOnce (SetSearchAction Move) Nothing ] | VisualModes, RemappedMatches layout [ ":" ] _ -> [ switchMode (ExMode ":'<,'>") ] | NotInsertMode, RemappedMatches layout [ ":" ] _ -> [ switchMode (ExMode ":") ] | NotInsertMode, [ Action layout action; SearchChar layout c ] -> [ switchMode (ExMode (string c)); runOnce (SetSearchAction action) Nothing ] | NormalMode, RemappedMatches layout [ "z"; "z" ] _ -> [ dispatch ViewCommands.CenterAndFocusCurrentDocument ] | NormalMode, RemappedMatches layout [ "z"; ] _ -> wait | NormalMode, [ "" ] -> [ dispatch TextEditorCommands.ScrollLineUp ] | NormalMode, [ "" ] -> [ dispatch TextEditorCommands.ScrollLineDown ] | NormalMode, [ "" ] -> [ dispatch NavigationCommands.NavigateBack ] | NormalMode, [ "" ] -> [ dispatch NavigationCommands.NavigateForward ] | NormalMode, RemappedMatches layout [ "r" ] _ -> wait | NormalMode, [ RemappedMatchesChar layout "r" _; "" ] -> [ run (ReplaceChar "\n" ) Nothing ] | NormalMode, [ RemappedMatchesChar layout "r" _; c ] -> [ run (ReplaceChar c) Nothing ] | NormalMode, [ RemappedMatchesChar layout "m" _; c ] -> [ run (SetMark c) Nothing ] | NotInsertMode, [ Action layout action; FindChar layout m; c ] -> [ run action (m c) ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "i" _; BlockDelimiter layout c ] -> [ run action (InnerBlock c) ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "a" _; BlockDelimiter layout c ] -> [ run action (ABlock c) ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "i" _; QuoteDelimiter layout c ] -> [ run action (InnerQuotedBlock (char c)) ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "a" _; QuoteDelimiter layout c ] -> [ run action (AQuotedBlock (char c)) ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "i" _; RemappedMatchesChar layout "w" _ ] -> [ run action InnerWord ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "a" _; RemappedMatchesChar layout "w" _ ] -> [ run action AWord ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "i" _; RemappedMatchesChar layout "W" _ ] -> [ run action InnerWORD ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "a" _; RemappedMatchesChar layout "W" _ ] -> [ run action AWORD ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "a" _; RemappedMatchesChar layout "t" _ ] -> [ run action ATag ] | NotInsertMode, [ Action layout action; RemappedMatchesChar layout "i" _; RemappedMatchesChar layout "t" _ ] -> [ run action InnerTag ] | VisualMode, RemappedMatches layout [ "i"; "w" ] _ -> [ run Visual InnerWord ] | VisualMode, RemappedMatches layout [ "a"; "w" ] _ -> [ run Visual AWord ] | VisualMode, RemappedMatches layout [ "i"; "W" ] _ -> [ run Visual InnerWORD ] | VisualMode, RemappedMatches layout [ "a"; "W" ] _ -> [ run Visual AWORD ] | VisualMode, RemappedMatches layout [ "a"; "t" ] _ -> [ run Visual ATag ] | VisualMode, RemappedMatches layout [ "i"; "t" ] _ -> [ run Visual InnerTag ] | VisualMode, RemappedMatches layout [ "u"] _ -> [ dispatch EditCommands.LowercaseSelection ] | VisualMode, RemappedMatches layout [ "U"] _ -> [ dispatch EditCommands.UppercaseSelection ] | NormalMode, [ ModeChange layout mode ] -> [ switchMode mode ] | NormalMode, RemappedMatches layout [ "a" ] _ -> [ run Move (Right IncludeDelimiter); switchMode InsertMode ] | NormalMode, RemappedMatches layout [ "A" ] _ -> [ run Move EndOfLineIncludingDelimiter; switchMode InsertMode ] | NormalMode, RemappedMatches layout [ "O" ] _ -> [ run (InsertLine After) Nothing; switchMode InsertMode ] | NormalMode, RemappedMatches layout [ "o" ] _ -> [ run (InsertLine Before) Nothing; switchMode InsertMode ] | NormalMode, RemappedMatches layout [ "I" ] _ -> [ run Move FirstNonWhitespace; switchMode InsertMode ] | NormalMode, [ Action layout _ ] -> wait | NotInsertMode, [ Action layout _; RemappedMatchesChar layout "i" _ ] -> wait | NotInsertMode, [ Action layout _; "a" ] -> wait | VisualMode, RemappedMatches layout [ "i" ] _ | VisualMode, [ "a" ] -> wait | NotInsertMode, [ FindChar layout _; ] -> wait | NotInsertMode, [ Action layout _; FindChar layout _; ] -> wait | NotInsertMode, [ "" ] -> [ run Move Down; run Move FirstNonWhitespace ] | NotInsertMode, RemappedMatches layout [ "q" ] _ when state.macro.IsNone -> wait | NotInsertMode, [ RemappedMatchesChar layout "q" _; c ] -> [ run (MacroStart (char c)) Nothing ] | NotInsertMode, RemappedMatches layout [ "q" ] _ -> [ run MacroEnd Nothing ] | NotInsertMode, [ "@"; c ] -> [ run (ReplayMacro (char c)) Nothing ] | NotInsertMode, RemappedMatches layout [ "g"; "g" ] _ -> let lineNumber = match numericArgument with Some n -> n | None -> 1 [ runOnce Move (Jump (StartOfLineNumber lineNumber)) ] | NotInsertMode, RemappedMatches layout [ "g"; "d" ] _ -> [ dispatch "MonoDevelop.Refactoring.RefactoryCommands.GotoDeclaration" ] | NotInsertMode, RemappedMatches layout [ "g"; "u" ] _ -> [ dispatch "MonoDevelop.Refactoring.RefactoryCommands.FindReferences" ] | NotInsertMode, RemappedMatches layout [ "g"; "b" ] _ -> [ dispatch "MonoDevelop.RefactoryCommands.NavigationCommands.FindBaseSymbols" ] | NotInsertMode, RemappedMatches layout [ "g"; "t" ] _ -> [ func Window.nextTab ] | NotInsertMode, RemappedMatches layout [ "g"; "T" ] _ -> [ func Window.previousTab ] | NotInsertMode, RemappedMatches layout [ "z"; "z" ] _ -> [ dispatch TextEditorCommands.RecenterEditor ] | NotInsertMode, RemappedMatches layout [ "z"; "a" ] _ -> [ dispatch EditCommands.ToggleAllFoldings ] | NotInsertMode, RemappedMatches layout [ "z"; "o" ] _ -> [ dispatch EditCommands.ToggleFolding ] | NotInsertMode, RemappedMatches layout [ "z"; "c" ] _ -> [ dispatch EditCommands.ToggleFolding ] | NotInsertMode, RemappedMatches layout [ "g"; "h" ] _ -> [ dispatch TextEditorCommands.ShowQuickInfo ] | NotInsertMode, RemappedMatches layout [ "g"; "v" ] _ -> match state.lastSelection with | Some selection -> [ run Move (Offset selection.start) switchMode selection.mode run Move (Offset selection.finish) ] | None -> resetKeys | NotInsertMode, RemappedMatches layout [ "." ] _ -> state.lastAction @ [ switchMode NormalMode ] | NotInsertMode, RemappedMatches layout [ ";" ] _ -> match state.findCharCommand with Some command -> [ command ] | None -> [] | NotInsertMode, RemappedMatches layout [ "," ] _ -> match state.findCharCommand with | Some command -> let findCommand = match command.textObject with | ToCharInclusive c -> ToCharInclusiveBackwards c | ToCharInclusiveBackwards c -> ToCharInclusive c | ToCharExclusive c -> ToCharExclusiveBackwards c | ToCharExclusiveBackwards c -> ToCharExclusive c | _ -> failwith "Invalid find command" [ { command with textObject=findCommand } ] | None -> [] | VisualModes, Movement layout m -> [ run Move m ] | VisualBlockMode, RemappedMatches layout [ "I" ] _ -> [ run (BlockInsert Before) Nothing ] | VisualBlockMode, [ "A" ] -> [ run (BlockInsert After) Nothing ] | VisualModes, [ RemappedMatchesChar layout "i" _; BlockDelimiter layout c ] -> [ run Visual (InnerBlock c) ] | VisualModes, [ "a"; BlockDelimiter layout c ] -> [ run Visual (ABlock c) ] | VisualModes, [ RemappedMatchesChar layout "i" _; QuoteDelimiter layout c ] -> [ run Visual (InnerQuotedBlock (char c)) ] | VisualModes, [ "a"; QuoteDelimiter layout c ] -> [ run Visual (AQuotedBlock (char c)) ] | VisualModes, RemappedMatches layout [ "x" ] _ -> [ run Delete SelectedText; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "d" ] _ -> [ run Delete SelectedText; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "D" ] _ -> [ run Delete EndOfLine; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "c" ] _ | VisualModes, RemappedMatches layout [ "s" ] _ -> [ run Change SelectedText ] | VisualModes, RemappedMatches layout [ "o" ] _ -> [ run SelectionOtherEnd Nothing ] | NormalMode, [ "~" ] -> [ run ToggleCase CurrentLocation ] | VisualModes, [ "~" ] -> [ run ToggleCase SelectedText; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "y" ] _ -> [ run (Yank EmptyRegister) SelectedText; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "Y" ] _ -> [ run (Yank EmptyRegister) WholeLine; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ ">" ] _ -> [ run (EditorFunc EditActions.IndentSelection) Nothing; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "<" ] _ -> [ run (EditorFunc EditActions.UnIndentSelection) Nothing; switchMode NormalMode ] | VisualModes, RemappedMatches layout [ "=" ] _ -> [ run EqualIndent SelectedText; switchMode NormalMode ] | NotInsertMode, RemappedMatches layout [ "Z" ] _ -> wait | NotInsertMode, RemappedMatches layout ["Z"; "Z" ] _ -> [ func Window.closeTab ] | NotInsertMode, [ "" ] -> [ dispatch SearchCommands.GotoFile ] | NotInsertMode, [ "" ] -> wait | NotInsertMode, [ ""; "w" ] | NotInsertMode, [ ""; "" ] -> [ func Window.switchWindow ] | NotInsertMode, [ ""; "h" ] -> [ func Window.leftWindow ] | NotInsertMode, [ ""; "l" ] -> [ func Window.rightWindow ] // These commands don't work the same way as vim yet, but better than nothing | NotInsertMode, [ ""; "o" ] -> [ dispatch FileTabCommands.CloseAllButThis ] | NotInsertMode, [ ""; "c" ] -> [ func Window.closeTab ] | NotInsertMode, [ ""; "v" ] | NotInsertMode, [ ""; "s" ] | NotInsertMode, [ ""; "" ] | NotInsertMode, [ ""; "" ] -> let notebooks = Window.getNotebooks() if notebooks.Length < 2 then [ dispatch "MonoDevelop.Ide.Commands.ViewCommands.SideBySideMode" ] else resetKeys | InsertMode, [ "" ] -> [ dispatch TextEditorCommands.DynamicAbbrev ] | NotInsertMode, [ "" ] -> [ run IncrementNumber Nothing; switchMode NormalMode ] | NotInsertMode, [ "" ] -> [ run DecrementNumber Nothing; switchMode NormalMode ] | NotInsertMode, RemappedMatches layout [ "g"; "p" ] _ -> wait | NotInsertMode, RemappedMatches layout [ "g"; "p"; "s" ] _ -> [ gotoPad "ProjectPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "c" ] _ -> [ gotoPad "ClassPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "e" ] _ -> [ gotoPad "MonoDevelop.Ide.Gui.Pads.ErrorListPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "t" ] _ -> [ gotoPad "MonoDevelop.Ide.Gui.Pads.TaskListPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "p" ] _ -> [ gotoPad "MonoDevelop.DesignerSupport.PropertyPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "o" ] _ -> [ gotoPad "MonoDevelop.DesignerSupport.DocumentOutlinePad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "b" ] _ -> [ gotoPad "MonoDevelop.Debugger.BreakpointPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "l" ] _ -> [ gotoPad "MonoDevelop.Debugger.LocalsPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "w" ] _ -> [ gotoPad "MonoDevelop.Debugger.WatchPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "i" ] _ -> [ gotoPad "MonoDevelop.Debugger.ImmediatePad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "n" ] _ -> [ gotoPad "MonoDevelop.FSharp.FSharpInteractivePad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "f" ] _ -> let searchResultPads = IdeApp.Workbench.Pads |> Seq.filter(fun p -> p.Content :? MonoDevelop.Ide.FindInFiles.SearchResultPad) match searchResultPads |> Seq.length with | 0 -> resetKeys | 1 -> [ gotoPad "SearchPad - Search Results - 0" ] | _ -> wait | NotInsertMode, [ RemappedMatchesChar layout "g" _; RemappedMatchesChar layout "p" _; RemappedMatchesChar layout "f" _; OneToNine d ] -> [ gotoPad (sprintf "SearchPad - Search Results - %d" (d-1)) ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "d" ] _ -> wait | NotInsertMode, RemappedMatches layout [ "g"; "p"; "d"; "t" ] _ -> [ gotoPad "MonoDevelop.Debugger.ThreadsPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "d"; "s" ] _ -> [ gotoPad "MonoDevelop.Debugger.StackTracePad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "d"; "c" ] _ -> [ gotoPad "MonoDevelop.Debugger.StackTracePad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "u" ] _ -> wait | NotInsertMode, RemappedMatches layout [ "g"; "p"; "u"; "t" ] _ -> [ gotoPad "MonoDevelop.UnitTesting.TestPad" ] | NotInsertMode, RemappedMatches layout [ "g"; "p"; "u"; "r" ] _-> [ gotoPad "MonoDevelop.UnitTesting.TestResultsPad" ] | _, [] when numericArgument.IsSome -> wait | _ -> resetKeys action, newState let keyPressToVimKey (keyPress:KeyDescriptor) = match keyPress.KeyChar with | c when keyPress.ModifierKeys = ModifierKeys.Control -> Control c | c when keyPress.ModifierKeys = ModifierKeys.Command -> Super c | 'z' when keyPress.ModifierKeys = ModifierKeys.Command -> Key 'u' | c when keyPress.KeyChar <> '\000' -> Key c | _ -> match keyPress.SpecialKey with | SpecialKey.Escape -> Esc | SpecialKey.Return -> Ret | SpecialKey.Left -> VimKey.Left | SpecialKey.Down -> VimKey.Down | SpecialKey.Up -> VimKey.Up | SpecialKey.Right -> VimKey.Right | SpecialKey.BackSpace -> Backspace | SpecialKey.Delete -> VimKey.Delete | _ -> Key keyPress.KeyChar let handleKeyPress state (keyPress:KeyDescriptor) (editor:TextEditor) config = let fileName = editor.FileName let vimKey = match state.mode, keyPress.KeyChar, config.insertModeEscapeKey with | InsertMode, c, Some combo when (string c) = combo.insertModeEscapeKey1 -> EscapeKey c | InsertMode, c, Some combo when state.keys |> List.tryHead = Some (Key (char combo.insertModeEscapeKey1)) -> EscapeKey c | _ -> keyPressToVimKey keyPress let newState = { state with keys = state.keys @ [ vimKey ] } let action, newState = parseKeys newState config let newState = match state.statusMessage, state.mode, newState.mode with | Some _, NormalMode, NormalMode -> { newState with statusMessage = None } | _ -> newState LoggingService.LogDebug (sprintf "%A" action) let rec performActions config actions' state handled = match actions' with | [] -> state, handled | [ only ] -> match only.commandType with | DoNothing -> state, true | _ -> let newState = runCommand config state editor only { newState with keys = [] }, true | h::t -> let newState = runCommand config state editor h performActions config t newState true let newState, handled = let processKey() = use group = editor.OpenUndoGroup() state.macro |> Option.iter(fun (Macro c) -> macros.[c] <- macros.[c] @ action) performActions config action newState false match state.mode, newState.keys |> List.map string with | ExMode _, [ Escape ] -> processKey() | ExMode _, _ -> let state, actions = exMode.processKey state keyPress performActions config actions state true | _ -> processKey() let firstAction = action |> List.head let newState = match config.keyboardLayout, state.mode, vimKey, firstAction.commandType with | _, InsertMode, _, _ -> newState.macro |> Option.iter(fun (Macro m) -> macros.[m] <- macros.[m] @ [ typeChar vimKey ]) //{ newState with lastAction = newState.lastAction @ [ typeChar vimKey ]} newState | _, NotInsertMode, _, SwitchMode VisualModes | _, NotInsertMode, _, Delete | _, NotInsertMode, _, Change | _, NotInsertMode, _, Indent | _, NotInsertMode, _, UnIndent | _, NotInsertMode, _, Put _ | _, NotInsertMode, _, ReplaceChar _ | Qwerty, NotInsertMode, Key 'a', _ | Qwerty, NotInsertMode, Key 'i', _ | Qwerty, NotInsertMode, Key 'I', _ | Qwerty, NotInsertMode, Key 'o', _ | Qwerty, NotInsertMode, Key 'O', _ | Qwerty, NotInsertMode, Key 'A', _ | Colemak, NotInsertMode, Key 'a', _ | Colemak, NotInsertMode, Key 'u', _ | Colemak, NotInsertMode, Key 'U', _ | Colemak, NotInsertMode, Key 'y', _ | Colemak, NotInsertMode, Key 'Y', _ | Colemak, NotInsertMode, Key 'A', _ | Dvorak, NotInsertMode, Key 'a', _ | Dvorak, NotInsertMode, Key 'c', _ | Dvorak, NotInsertMode, Key 'C', _ | Dvorak, NotInsertMode, Key 'r', _ | Dvorak, NotInsertMode, Key 'R', _ | Dvorak, NotInsertMode, Key 'A', _-> { newState with lastAction = action } | _ -> newState editorStates.[fileName] <- newState newState, handled ================================================ FILE: XSVim/XSVim.fsproj ================================================  Debug AnyCPU {9DB313D4-4CD1-455F-846F-42CD234DE626} Library XSVim XSVim v4.7.2 false true portable false bin\Debug DEBUG prompt Program --warnon:1182 Program /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/VisualStudio.exe /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin -no-redirect true portable true bin\Release prompt true $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets ..\lib\MonoDevelop.SourceEditor.dll False ..\lib\MonoDevelop.Core.dll False ..\lib\MonoDevelop.Ide.dll False ..\lib\Mono.Addins.dll False ..\lib\System.Collections.Immutable.dll ..\lib\Xamarin.Mac.dll ..\lib\Xwt.dll ..\packages\FSharp.Core.4.5.4\lib\net45\FSharp.Core.dll ================================================ FILE: XSVim/packages.config ================================================  ================================================ FILE: XSVim.Tests/ChangeTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Change tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``cc non empty line``() = assertText "abc\nd$ef\nghi" "cc" "abc\n|\nghi" [] let ``cw changes word``() = assertText "a$bc def" "cw" "| def" [] let ``C changes last character``() = assertText "abc$" "C" "ab|" [] let ``cw changes space``() = assertText "abc $def" "cw" "abc|def" [] let ``c2w changes two words``() = assertText "a$bc def ghi" "c2w" "| ghi" [] let ``undo works after cw``() = assertText "a$bc def ghi" "cwu" "a$bc def ghi" [] let ``undo works after c2w``() = assertText "a$bc def ghi" "c2wu" "a$bc def ghi" [] let ``ce changes word``() = assertText "a$bc def" "ce" "| def" [] let ``ce changes last character``() = assertText "a$ bcd" "ce" "| bcd" [] let ``c% changes to matching parens``() = assertText "abc($def)ghi" "c%" "abc|ghi" [] let ``Change to end of word does not include dot``() = assertText "open Mon$o.Addins" "ce" "open Mo|.Addins" [] let ``Change to end of word includes dot``() = assertText "open Mono$.Addins" "ce" "open Mon|Addins" [] let ``cc empty line``() = assertText "abc\n\n$def" "cc" "abc\n|\ndef" [] let ``ci backtick``() = assertText "``some t$ext``" "ci`" "``|``" [] let ``S changes entire line``() = assertText " line1 \n line2$ \n line3 " "S" " line1 \n |\n line3 " [] let ``2S changes two lines``() = assertText " line1 \n line2$ \n line3 \n line4 " "2S" " line1 \n |\n line4 " [] let ``s before the end of line``() = assertText "a$b" "s" "|b" [] let ``s at the end of line``() = assertText "ab$\n" "s" "a|\n" ================================================ FILE: XSVim.Tests/DeleteTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Delete tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``Vjd test``() = let source = @"aaaaaa bb$bbbb cccccc dddddd eeeeee"; let expected = @"aaaaaa d$ddddd eeeeee"; assertText source "Vjd" expected [] let ``Delete line and line below``() = let source = @"aaaaaa bb$bbbb cccccc dddddd eeeeee"; let expected = @"aaaaaa d$ddddd eeeeee"; assertText source "dj" expected [] let ``Delete line and next two lines``() = let source = @"aaaaaa bb$bbbb cccccc dddddd eeeeee"; let expected = @"aaaaaa e$eeeee"; assertText source "d2j" expected [] let ``dd first line``() = assertText "ab$c\n def" "dd" " d$ef" [] let ``dd next line is blank``() = assertText """ { fo$o bar } """ "dd" // $ is over \n here """ { $ bar } """ [] let ``2dd deletes 2 lines``() = assertText "ab$c\ndef\nghi" "2dd" "g$hi" [] let ``2ddp puts 2 lines back``() = assertText "abc\nde$f\nghi" "2ddp" "abc\nd$ef\nghi" [] let ``dd last line at EOF``() = assertText "abc\ndef\ngh$i" "dd" "abc\nd$ef" [] let ``dd only line``() = assertText "a$bc" "dd" "$" [] let ``Delete char under caret``() = assertText "abc$def" "x" "abd$ef" [] let ``Delete char at EOL``() = assertText "abcdef$\n" "xx" "abcd$\n" [] let ``x with multiplier stops at EOL (caret at EOL)``() = assertText "abcdef$\n" "4x" "abcde$\n" [] let ``x with multiplier stops at EOL``() = assertText "ab$cdef\n" "100x" "a$\n" [] let ``Delete char to left of caret``() = assertText "abc$def" "X" "ac$def" [] let ``Delete to end of line``() = assertText "abc$ def\nghi" "d$" "ab$\nghi" [] let ``Delete to end of document``() = assertText "abc\nde$f\nghi" "dG" "abc\n$" [] let ``Delete to start of document``() = assertText "abc\nde$f\nghi" "dgg" "g$hi" [] let ``Delete to end of line using D``() = assertText "abc$ def\nghi" "D" "ab$\nghi" [] let ``Delete to end of line from start keeps caret on current line``() = assertText "abc\nd$ef\nghi" "D" "abc\n\n$ghi" [] let ``Deletes word``() = assertText "a$bc def" "dw" "d$ef" [] let ``Delete to end of word``() = assertText "ab$c def" "de" "a $def" [] let ``Delete to end of word does not include dot``() = assertText "open Mon$o.Addins" "de" "open Mo.$Addins" [] let ``Delete to end of word includes dot``() = assertText "open Mono$.Addins" "de" "open MonA$ddins" [] let ``Delete word does not include dot``() = assertText "open Mo$no.Addins" "dw" "open M.$Addins" [] let ``Delete to end of WORD``() = assertText "ab$c.def ghi" "dE" "a $ghi" [] let ``Deleting last word doesn't delete delimiter'``() = assertText "abc d$ef \nghi" "dw" "abc $\nghi" [] let ``Deleting last word touching EOL doesn't delete delimiter'``() = assertText "abc d$ef\nghi" "dw" "abc $\nghi" [] let ``Delete char to left doesn't delete past start of line``() = assertText "abcdef\nab$cdef" "XXX" "abcdef\nb$cdef" [] let ``dw last word at EOF``() = assertText "a$bc" "dw" "$" [] let ``dw to brace #167``() = assertText "abc\n $ {" "dw" "abc\n{$" [] let ``d]) deletes to next unmatched )``() = assertText "if (a$ == (b)c)" "d])" "if ($" [] let ``dib deletes nested brackets``() = assertText "(fo$o (bar (foo) ) )" "dib" "()$" [] let ``dib deletes nested brackets backwards``() = assertText "(foo (bar (foo) ) $ )" "dib" "()$" [] let ``dib outside brackets does nothing``() = assertText "don't crash$ me" "dib" "don't crash$ me" ================================================ FILE: XSVim.Tests/ExModeTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Ex mode tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``/ searches for word``() = assertText "ab$c abc" "/abc" "abc a$bc" [] let ``/ is case insensitive``() = assertText "ab$c ABC" "/abc" "abc A$BC" [] let ``/ is case sensitive``() = assertText "ab$c ABC Abc" "/Abc" "abc ABC A$bc" [] let ``deletes to search term``() = assertText "ab$c ABC Abc 123" "d/123" "a1$23" [] let ``n searches for next word``() = assertText "ab$c abc abc" "/abcn" "abc abc a$bc" [] let ``n wraps to start``() = assertText "ab$c abc abc" "/abcnn" "a$bc abc abc" [] let ``N searches for previous word``() = assertText "ab$c abc abc" "/abcN" "a$bc abc abc" [] let ``n searches for previous word after ?``() = assertText "abc abc a$bc" "?abcn" "a$bc abc abc" [] let ``? searches for word backwards``() = assertText "abc abc a$bc" "?abc" "abc a$bc abc" [] let ``:2 jumps to line 2``() = assertText "l$ine1\nline2" ":2" "line1\nl$ine2" [] let ``Backspacing ex mode returns to normal mode``() = let _, state, _ = test "abc abc a$bc" "/a" state.mode |> should equal NormalMode [] let `` returns to normal mode``() = let _, state, _ = test "abc abc a$bc" "/" state.mode |> should equal NormalMode [] let `` returns to normal mode``() = let _, state, _ = test "abc abc a$bc" "/" state.mode |> should equal NormalMode [] let `` returns to normal mode``() = let _, state, _ = test "abc abc a$bc" "/" state.mode |> should equal NormalMode [] let ``Displays could not parse message``() = let _, state, _ = test "a$bc" ":garbage" state.statusMessage.Value |> should equal "Could not parse :garbage" [] let ``Could not parse message is reset``() = let _, state, _ = test "a$bc" ":garbagel" state.statusMessage |> should equal None [] let ``Deletes lines 2 to 4``() = assertText """11111 22222 33333 44444 55555$""" ":2,4d" """11111 5$5555""" [] let ``Switching to substitute command mode with a selection``() = let _, state, _ = test "a$bc" "v:" state.statusMessage |> should equal (Some ":'<,'>") ================================================ FILE: XSVim.Tests/IndentationTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Indentation tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``>> indents right in normal mode``() = assertText "a$bc" ">>" " a$bc" [] let ``indent is repeatable``() = assertText "a$bc" ">>." " a$bc" [] let ``V> indents line right``() = let text, state, _ = test "a$bc\ndef" "V>" text |> should equal " a$bc\ndef" state.mode |> should equal NormalMode [] let ``V2> indents line right twice``() = assertText "a$bc\ndef" "V2>" " a$bc\ndef" [] let ``>j indents current line and line below``() = assertText "a$bc\ndef" ">j" " a$bc\n def" [] let ``] let ``>2j indents current line and two lines below``() = assertText "a$bc\ndef\nghi" ">2j" " a$bc\n def\n ghi" [] let ``>gg indents to top of file``() = assertText "abc\ndef\ngh$i" ">gg" " abc\n def\n gh$i" [] let ``V> indents line``() = assertText "abc\ndef\ngh$i" ">gg" " abc\n def\n gh$i" [] let ``>2gg indents to line 2``() = assertText "abc\ndef\ngh$i" ">2gg" "abc\n def\n gh$i" [] let ``>2G indents to line 2``() = assertText "abc\ndef\ngh$i" ">2G" "abc\n def\n gh$i" [] let ``== autoindents line``() = assertText "abc\n def\ngh$i" "==" "abc\n def\n g$hi" [] let ``= autoindents selection``() = assertText "abc\n def\ngh$i" "V=" "abc\n def\n g$hi" [] let ``= autoindents multiple line selection``() = assertText "abc\n de$f\n ghi\n jkl" "Vj=" "abc\nd$ef\nghi\n jkl" [] let ``=gg indents to top of file``() = assertText "abc\n def\n gh$i" "=gg" "a$bc\ndef\nghi" ================================================ FILE: XSVim.Tests/InsertionTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Insertion tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``'O' should insert line above``() = //TODO: 'O' is broken on the top line assertText " \n a$bcdef" "O" " \n |\n abcdef" [] let ``'O' inserts line above``() = assertText "abc$def\n" "O" "|\nabcdef\n" ================================================ FILE: XSVim.Tests/KeyParsing.fs ================================================ namespace XSVim.Tests open XSVim open NUnit.Framework [] module ``Key parsing tests`` = let test keys = let keys = [for c in keys -> Key c] let state = { VimState.Default with keys=keys } let config = Config.Default let action, _state = Vim.parseKeys state config let first = action.Head first.repeat, first.commandType, first.textObject [] let ``10j``() = test "10j" |> should equal (Some 10, Move, Down) [] let ``11G``() = test "11G" |> should equal (Some 1, Move, Jump (StartOfLineNumber 11)) ================================================ FILE: XSVim.Tests/KeyboardMap.fs ================================================ namespace XSVim.Tests open XSVim module mapFromQwerty = let qwertyToColemak = function | 'e' -> 'f' | 'r' -> 'p' | 't' -> 'g' | 'y' -> 'j' | 'u' -> 'l' | 'i' -> 'u' | 'o' -> 'y' | 'p' -> ';' | 's' -> 'r' | 'd' -> 's' | 'f' -> 't' | 'g' -> 'd' | 'j' -> 'n' | 'k' -> 'e' | 'l' -> 'i' | ';' -> 'o' | 'n' -> 'k' | 'E' -> 'F' | 'R' -> 'P' | 'T' -> 'G' | 'Y' -> 'J' | 'U' -> 'L' | 'I' -> 'U' | 'O' -> 'Y' | 'P' -> ':' | 'S' -> 'R' | 'D' -> 'S' | 'F' -> 'T' | 'G' -> 'D' | 'J' -> 'N' | 'K' -> 'E' | 'L' -> 'I' | ':' -> 'O' | 'N' -> 'K' | c -> c let qwertyToDvorak = function | '-' -> '[' | '=' -> ']' | 'q' -> ''' | 'w' -> ',' | 'e' -> '.' | 'r' -> 'p' | 't' -> 'y' | 'y' -> 'f' | 'u' -> 'g' | 'i' -> 'c' | 'o' -> 'r' | 'p' -> 'l' | '[' -> '/' | ']' -> '=' | 's' -> 'o' | 'd' -> 'e' | 'f' -> 'u' | 'g' -> 'i' | 'h' -> 'd' | 'j' -> 'h' | 'k' -> 't' | 'l' -> 'n' | ';' -> 's' | ''' -> '-' | 'z' -> ';' | 'x' -> 'q' | 'c' -> 'j' | 'v' -> 'k' | 'b' -> 'x' | 'n' -> 'b' | ',' -> 'w' | '.' -> 'v' | '/' -> 'z' | '_' -> '{' | '+' -> '}' | 'Q' -> '\'' | 'W' -> '<' | 'E' -> '>' | 'R' -> 'P' | 'T' -> 'Y' | 'Y' -> 'F' | 'U' -> 'G' | 'I' -> 'C' | 'O' -> 'R' | 'P' -> 'L' | '{' -> '?' | '}' -> '+' | 'S' -> 'O' | 'D' -> 'E' | 'F' -> 'U' | 'G' -> 'I' | 'H' -> 'D' | 'J' -> 'H' | 'K' -> 'T' | 'L' -> 'N' | ':' -> 'S' | '"' -> '_' | 'Z' -> ':' | 'X' -> 'Q' | 'C' -> 'J' | 'V' -> 'K' | 'B' -> 'X' | 'N' -> 'B' | 'M' -> 'M' | '<' -> 'W' | '>' -> 'V' | '?' -> 'Z' | c -> c let remap layout key = match layout with | Colemak -> qwertyToColemak key | Dvorak -> qwertyToDvorak key | _ -> key ================================================ FILE: XSVim.Tests/MacrosTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Macro tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``Start recording macro q``() = let _, state, _ = test " $" "qq" state.macro |> should equal (Some (Macro 'q')) [] let ``Stop recording macro q``() = let _, state, _ = test " $" "qqq" state.macro |> should equal None [] let ``Replay macro q``() = assertText "a$bc abc" "qqfcadq@q" "abcd abcd$" [] let ``Macros are repeatable``() = assertText "a$bc abc abc abc" "qqfcadq3@q" "abcd abcd abcd abcd$" [] let ``Macros containing repeats are repeatable``() = assertText " $aa aa aa aa" "qq2faabq3@q" " aab aab aab aab$" ================================================ FILE: XSVim.Tests/MarkerTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Marker tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``ma adds marker a``() = assertText "a$bc" "mall`a" "a$bc" [] let ``'a moves to start of line of marker a``() = assertText "123 a$bc" "mall'a" "1$23 abc" [] let ``'. jumps to last edit line``() = assertText " 123 a$bc" "i'." " 1$23 abc" [] let ``'' jumps to last jump location line``() = assertText "ab$c\ndef" "G''" "a$bc\ndef" [] let ``Deletes from 4 up to marker a``() = assertText "1234XXXXXXXa$5678" "maF4d`a" "123a$5678" [] let ``Selects from 4 up to marker a``() = let _ = test "1234XXXXXXXa$5678" "maF4v`ay" Vim.registers.[EmptyRegister].content |> should equal "4XXXXXXXa" [] let ``Deletes linewise between marker a and marker b``() = assertText """ ......... aaaaa$aaaa ......... ......... bbbbbbbbb ......... """ "ma/bmb:'a,'bd" """ ......... .$........ """ ================================================ FILE: XSVim.Tests/MiscTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Miscellaneous tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``'A' should put caret at end of the line``() = assertText "abc$def\n" "A" "abcdef|\n" [] let ``'A' should put caret at EOF``() = assertText "abc$def" "A" "abcdef|" [] let ``'a' should append after``() = assertText "a$bcdef" "a" "a|bcdef" [] let ``'a' should append after last char``() = assertText "abcdef$\n" "a" "abcdef|\n" [] let ``'a' should append before EOF``() = assertText "abcdef$" "a" "abcdef|" [] let ``'a' on empty line should keep cursor on the current line``() = assertText "\n$abc" "a" "|\nabc" [] let ``'I' should insert at first non whitespace``() = assertText " abcdef$" "I" " |abcdef" [] let ``Undo repeat``() = assertText "a$bc def ghi" "3dwu" "a$bc def ghi" [] let ``Repeat typed chars``() = assertText "d$" "iabc ." "abcabc $ d" [] let ``Repeat intellisense``() = let _, state, editor = test """ System$ System """ "a.Coll" // simulate ctrl-space intellisense inserting "Collections" by removing // "Coll" and then inserting "Collections" let offset = editor.Text.IndexOf("Coll") editor.RemoveText(offset,4) // delete "Coll" editor.InsertText(editor.CaretOffset, "Collections") let config = Config.Default let _, newState, _ = sendKeysToEditor editor "j." config let text = getEditorText editor newState text |> should equal """ System.Collections System.Collections$ """ [] let ``Large text addition is not too slow!``() = let _, state, editor = test " $" "" editor.InsertText(0, new System.String('x', 200000)) [] let ``backspace is repeated``() = assertText "d$" "iabc ." "abab $ d" [] let ``delete key is repeated``() = assertText "d$" "iabc." "ababc$" [] let `` escapes``() = assertText " abc$" "i" " ab$c" [] let ``Return to normal mode doesn't move past start of line``() = assertText "abc\nd$ef" "i" "abc\nd$ef" [] let ``dot repeats at start of line``() = assertText """ def$ def """ "Iabcj." """ abcdef abc$def """ [] let ``dot repeats at end of line``() = assertText """ a$bc a$bc """ "Adefj." """ abcdef abcdef$ """ [] let ``Repeat delete word``() = assertText "a$bc de fgh" "dww." "de $" [] let ``Repeat change word``() = assertText "a$bc de fgz " "cwxxxww." "xxx de xxx$ " [] let ``r should be repeatable``() = assertText "a$aaa" "rb$." "baab$" [] let ``r inserts and indents``() = assertText " aaa$\nbbb" "r" " aa\n \n$bbb" [] let ``R switches to replace mode``() = let _, state, _ = test "a$bc" "R" state.mode |> should equal ReplaceMode [] let ``R replaces characters``() = assertText "a$bc" "RABCD" "ABCD$" [] let ``R replaces digits``() = assertText "a$bc" "R123" "123$" [] let ``Replace mode inserts at end of line``() = assertText "a$bc\ndef" "RABCD" "ABCD\n$def" [] let ``Replace mode is undoable``() = assertText "a$bc\ndef" "RABCDu" "a$bc\ndef" [] let ``Undo insert mode``() = assertText "abc$" "adef ghi jklu" "abc$" [] let ``J puts caret between joined lines``() = assertText "a$bc\ndef" "J" "abc $def" [] let ``* finds next word``() = assertText " $ abc" "*" " a$bc" [] let ``* does not match substring``() = assertText "a$bc abcde abc" "*" "abc abcde a$bc" [] let ``* finds next word at caret``() = assertText "a$bc abc" "*" "abc a$bc" [] let ``n finds next match``() = assertText "a$bc abc cba abc" "*n" "abc abc cba a$bc" [] let ``* finds next word when on last word char``() = assertText "abc$ abc" "*" "abc a$bc" [] let ``* wraps to start``() = assertText "abc a$bc" "*" "a$bc abc" [] let ``* finds next word at EOF``() = assertText "abc abc$" "*" "a$bc abc" [] let ``# finds previous word at caret``() = assertText "abc abc a$bc" "#" "abc a$bc abc" [] let ``# matches exact word``() = assertText "abc abcde a$bc" "#" "a$bc abcde abc" [] let ``£ wraps to end``() = assertText "a$bc abc" "£" "abc a$bc" [] let ``~ toggles case of char at caret``() = assertText "a$bc abc" "~" "Ab$c abc" [] let ``~ toggles case of selection``() = assertText "A$bC abc" "vll~" "a$Bc abc" [] let `` doesn't move caret left onto newline'``() = assertText "\nA$bC abc" "o" "\nAbC abc\n$" [] let `` increments next number``() = assertText "a$bc 9 " "" "abc 10$ " [] let `` increments second number``() = assertText "abc 0 $1 " "" "abc 0 2$ " [] let `` increments same width number``() = assertText "a$bc 0 " "" "abc 1$ " [] let `` increments number at caret``() = assertText "abc 9$ " "" "abc 10$ " [] let `` increments next negative number``() = assertText "a$bc -1 " "" "abc 0$ " [] let `` decrements next number``() = assertText "a$bc 10 " "" "abc 9$ " [] let `` decrements -1``() = assertText "abc -1$ " "" "abc -2$ " [] let ``dot repeats 2dd``() = assertText """ a$aaaa aaaaa bbbbb bbbbb ccccc """ "2dd." """ c$cccc """ [] let ``dot repeats 2dj``() = assertText """ a$aaaa aaaaa aaaaa bbbbb bbbbb bbbbb """ "2dj." """ $""" [] let ``dot repeats 3S``() = assertText """ a$aaaa aaaaa bbbbb bbbbb ccccc ccccc""" "3S." """ $ ccccc""" ================================================ FILE: XSVim.Tests/Movement.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Movement tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``Move to next word``() = assertText "aa$a bbb" "w" "aaa b$bb" [] let ``Move to next word on next line``() = assertText "aa$a\n bbb" "w" "aaa\n b$bb" [] let ``Move to empty line``() = assertText "aa$a\n\nbbb" "w" "aaa\n\n$bbb" [] let ``Moves to EOF when there is no next word``() = assertText "aa$aa" "w" "aaaa$" [] let ``w skips over tabs``() = assertText "\t$\t\taaaa" "w" "\t\t\ta$aaa" [] let ``Move to word end``() = assertText "aa$a bbb" "e" "aaa$ bbb" [] let ``Move to next word end``() = assertText "aaa$ bbb" "e" "aaa bbb$" [] let ``Move to second word end``() = assertText "aa$a bbb" "ee" "aaa bbb$" [] let ``e jumps over punctuation``() = assertText "int model$);\n " "e" "int model);$\n " [] let ``e jumps over chevrons``() = assertText "Task> nextWord" "e" "Task>$ nextWord" [] let ``e jumps from chevron to end of next word``() = assertText "Task>$ nextWord" "e" "Task> nextWord$" [] let ``e jumps over spaces``() = assertText " $ abcde" "e" " abcde$" [] let ``e stops before dot``() = assertText "open$ System.Collections.Generic" "e" "open System$.Collections.Generic" [] let ``Move to end of line``() = assertText "aa$a aaa\nbbb" "$" "aaa aaa$\nbbb" [] let ``Move to end of document``() = assertText "aa$aaaa\nbbbbbb" "G" "aaaaaa\nb$bbbbb" [] let ``Move to start of document``() = assertText "aaaaaa\nbb$bbbb" "gg" "a$aaaaa\nbbbbbb" [] let ``Move to line 2``() = assertText "a$aaaaa\n bbbbbb" "2gg" "aaaaaa\n b$bbbbb" [] let ``Move to line 3``() = assertText "a$aaaaa\nbbbbbb\ncccccc\ndddddd" "3G" "aaaaaa\nbbbbbb\nc$ccccc\ndddddd" [] let ``Move down to desired column``() = assertText "12345$6\n123\n123456" "jj" "123456\n123\n12345$6" [] let ``Move down to last column``() = assertText "12345$6\n123\n123456" "j" "123456\n123$\n123456" [] let ``Move across then down``() = assertText "1$2\n12\n" "lj" "12\n12$\n" [] let ``Move ten right``() = assertText "a$bcdefghijkl" "10l" "abcdefghijk$l" [] let ``Does not move right past delimiter``() = assertText "a$b\n" "ll" "ab$\n" [] let ``Find moves to digit``() = assertText "abc$ d1 d2 d3" "f2" "abc d1 d2$ d3" [] let ``Reverse find moves to digit``() = assertText "abc d1 d2 d$3" "F1" "abc d1$ d2 d3" [] let ``Till moves to digit``() = assertText "abc$ d1 d2 d3" "t2" "abc d1 d$2 d3" [] let ``Reverse till moves to digit``() = assertText "abc d1 d2 d$3" "T1" "abc d1 $d2 d3" [] let ``2fd moves to second d``() = assertText "abc$ d1 d2 d3" "2fd" "abc d1 d$2 d3" [] let ``F finds previous char``() = assertText "a a$" "Fa" "a$ a" [] let ``f is repeatable with ;``() = assertText " $ a1 a2" "fa;" " a1 a$2" [] let ``f is reversed with ,``() = assertText " $ a1 a2" "fa;," " a$1 a2" [] let ``t does not move if caret is already just before search char``() = assertText " $a1 a2" "ta" " $a1 a2" [] let ``T does not move if caret is already just after search char``() = assertText "a1 a2$" "Ta" "a1 a2$" [] let ``t is repeatable with ;``() = assertText " $a1 a2" "ta;" " a1 $a2" [] let ``T is repeatable with ;``() = assertText "a1 a2$" "Ta;" "a1$ a2" [] let ``ge moves back to end of last word``() = assertText "abc de$f" "ge" "abc$ def" [] let ``ge between words moves back to end of last word``() = assertText "abc $def" "ge" "abc$ def" [] let ``ge stops at first character``() = assertText "abc$" "ge" "a$bc" [] let ``gE moves back to end of last WORD``() = assertText "abc def.gh$i" "gE" "abc$ def.ghi" [] let ``l stops at EOL``() = assertText "abc$\ndef" "l" "abc$\ndef" [] let ``space moves past EOL``() = assertText "abc$\ndef" " " "abc\nd$ef" [] let ``% moves to matching parens``() = assertText "($foo(bar))" "%" "(foo(bar))$" [] let ``[{ goes to previous unmatched {``() = assertText "func { case { a } case$ { b } }" "[{" "func {$ case { a } case { b } }" [] let ``[( goes to previous unmatched (``() = assertText "if (a == (b)c$)" "[(" "if ($a == (b)c)" [] let ``]} goes to next unmatched }``() = assertText "func { case$ { a } case { b } }" "]}" "func { case { a } case { b } }$" [] let ``]) goes to next unmatched )``() = assertText "if (a$ == (b)c)" "])" "if (a == (b)c)$" ================================================ FILE: XSVim.Tests/Properties/AssemblyInfo.fs ================================================ namespace XSVim.Tests open System.Reflection open System.Runtime.CompilerServices [] [] [] [] [] [] [] // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} [] //[] //[] () ================================================ FILE: XSVim.Tests/TestHelpers.fs ================================================ namespace XSVim.Tests open System open System.Text.RegularExpressions open System.Threading.Tasks open MonoDevelop.Ide.Editor.Extension open NUnit.Framework open XSVim open MonoDevelop.Core open MonoDevelop.Ide.Editor [] module FsUnit = open System.Diagnostics open NUnit.Framework.Constraints [] let should (f : 'a -> #Constraint) x (y : obj) = let c = f x let y = match y with | :? (unit -> unit) -> box (new TestDelegate(y :?> unit -> unit)) | _ -> y Assert.That(y, c) let shouldnot (f : 'a -> #Constraint) x (y : obj) = let c = f x let y = match y with | :? (unit -> unit) -> box (new TestDelegate(y :?> unit -> unit)) | _ -> y Assert.That(y, new NotConstraint(c)) let equal x = new EqualConstraint(x) // like "should equal", but validates same-type let shouldEqual (x: 'a) (y: 'a) = Assert.AreEqual(x, y, sprintf "Expected: %A\nActual: %A" x y) let replaceLineEnding (s:string) = s.Replace("\r\n", "\n") let shouldEqualIgnoringLineEndings (x: string) (y: string) = Assert.AreEqual((replaceLineEnding x), (replaceLineEnding y), sprintf "Expected: %A\nActual: %A" x y) let notEqual x = new NotConstraint(new EqualConstraint(x)) let NOT c = new NotConstraint(c) let contain x = new ContainsConstraint(x) let haveLength n = Has.Length.EqualTo(n) let haveCount n = Has.Count.EqualTo(n) let NotEmpty = Has.Length.GreaterThan(0) let endWith (s:string) = new EndsWithConstraint(s) let startWith (s:string) = new StartsWithConstraint(s) let be = id let Null = new NullConstraint() let Empty = new EmptyConstraint() let EmptyString = new EmptyStringConstraint() let NullOrEmptyString = new NullOrEmptyStringConstraint() let True = new TrueConstraint() let False = new FalseConstraint() let sameAs x = new SameAsConstraint(x) let throw = Throws.TypeOf module FixtureSetup = let firstRun = ref true let initialiseMonoDevelop() = if !firstRun then firstRun := false printf "initialising" Environment.SetEnvironmentVariable ("MONO_ADDINS_REGISTRY", "/tmp") Runtime.Initialize (true) // Initialize FontService Runtime.ServiceProvider.GetService() :> Task else Task.CompletedTask [] module TestHelpers = let (|CtrlKey|_|) (s:string) = match s with | s when s.Length = 3 && s.StartsWith "C-" -> Some s.[2] | _ -> None let keyToDescriptor key (state: VimState) layout = let lastKeyPress = state.keys |> List.tryLast let noremap = // The next key press is not translated for keyboard layout [ 'm'; '@'; 'r'; 'f'; 'q'] |> List.map (mapFromQwerty.remap layout) |> List.map Key |> List.map Some let shouldRemapKey = match state.mode, noremap |> List.contains lastKeyPress with | _, true -> false | NormalMode, _ | VisualMode, _ | VisualLineMode, _ | VisualBlockMode, _ -> true | _ -> false match key, shouldRemapKey with | "esc", _ -> KeyDescriptor.FromGtk(Gdk.Key.Escape, '\000', Gdk.ModifierType.None) | "ret", _ -> KeyDescriptor.FromGtk(Gdk.Key.Return, '\000', Gdk.ModifierType.None) | "bs" , _ -> KeyDescriptor.FromGtk(Gdk.Key.BackSpace, '\000', Gdk.ModifierType.None) | "del", _ -> KeyDescriptor.FromGtk(Gdk.Key.Delete, '\000', Gdk.ModifierType.None) | CtrlKey ch, _ -> KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), ch, Gdk.ModifierType.ControlMask) | key, false -> KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), char key, Gdk.ModifierType.None) | key, true -> KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), mapFromQwerty.remap layout (char key), Gdk.ModifierType.None) let getEditorText (editor:TextEditor) state = if state.mode = InsertMode then editor.Text.Insert(editor.CaretOffset, "|") else if editor.CaretOffset = editor.Text.Length then editor.Text + "$" else editor.Text.Insert(editor.CaretOffset+1, "$") let sendKeysToEditor (editor:TextEditor) keys config = let keygroups = Regex.Replace(keys, "<(.*?)>", "§$1§").Split '§' let keygroups = keygroups |> Array.collect(fun g -> match g with | "esc" -> [|g|] | "ret" -> [|g|] | "bs" -> [|g|] | "del" -> [|g|] | CtrlKey ch -> [|g|] | _ -> Seq.toArray g |> Array.map string) // "abc" -> [|"a"; "b"; "c" |] let newState = keygroups |> Array.fold(fun state keys -> let state = Vim.editorStates.[editor.FileName] printfn "%A" state.keys let descriptor = keyToDescriptor keys state config.keyboardLayout printfn "%A" state.mode printfn "%A" descriptor let handledState, handledKeyPress = Vim.handleKeyPress state descriptor editor config printfn "%A" handledState printfn "\"%s\"" (getEditorText editor handledState) if state.mode = InsertMode && descriptor.ModifierKeys <> ModifierKeys.Control && descriptor.SpecialKey <> SpecialKey.Escape then Vim.processVimKey editor (Vim.keyPressToVimKey descriptor) handledState) Vim.editorStates.[editor.FileName] let text = getEditorText editor newState text, newState, editor let testWithEol (source:string) (keys:string) eolMarker layout = let config = { Config.Default with keyboardLayout = layout } let editor = TextEditorFactory.CreateNewEditor() editor.FileName <- FilePath "test.txt" editor.TextChanged.Add(fun changes -> Subscriptions.textChanged editor changes) let caret = source.IndexOf "$" if caret = 0 then failwith "$ can't be the first position. It needs to be after the char the caret would appear over." if caret = -1 then failwith "No caret found in test code" editor.Text <- source.Replace("$", "") editor.CaretOffset <- caret-1 editor.Options <- new CustomEditorOptions(TabsToSpaces=true, IndentationSize=4, IndentStyle=IndentStyle.Smart, TabSize=4, DefaultEolMarker=eolMarker) Vim.editorStates.[editor.FileName] <- VimState.Default sendKeysToEditor editor keys config let test source keys = testWithEol source keys "\n" Qwerty let switchLineEndings (s:string) = s.Replace("\n", "\r\n") let assertText (source:string) (keys:string) expected = let actual, _, _ = testWithEol source keys "\n" Qwerty Assert.AreEqual(expected, actual, "Failed with \n") let actual, _, _ = testWithEol source keys "\n" Colemak Assert.AreEqual(expected, actual, "Failed with colemak") let actual, _, _ = testWithEol source keys "\n" Dvorak Assert.AreEqual(expected, actual, "Failed with dvorak") if source.Contains("\n") || actual.Contains("\n") then // Run the test again with \r\n line endings let actual, _, _ = testWithEol (source |> switchLineEndings) keys "\r\n" Qwerty Assert.AreEqual(expected |> switchLineEndings, actual.Replace("\r$\n", "\r\n$"), "Failed with \r\n") ================================================ FILE: XSVim.Tests/TextObjectSelectionTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Text object selection tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() // Tests for aw. Reference: http://vimdoc.sourceforge.net/htmldoc/motion.html#aw module aw = [] let ``daw deletes around word``() = assertText "a b$c d" "daw" "a d$" [] let ``daw on a word``() = assertText "word1 word2$ word3" "daw" "word1 w$ord3" // three spaces before word2 preserved, two after removed [] let ``daw from whitespace before a word``() = assertText "word1 $ word2 word3" "daw" "word1 $ word3" // four spaces before word2 removed, three after preserved [] let ``daw puts caret at next character``() = assertText "a b$ ." "daw" "a .$" [] let ``daw deletes whitespace only``() = assertText "a. b$;" "daw" "a.;$" [] let ``daw deletes leading whitespace if there's no trailing``() = assertText "a b$" "daw" "a$" // [] let ``daw stops searching at EOL``() = assertText "word1$ \n word2" "daw" "\n$ word2" [] let ``caw on a word``() = assertText "word1 word2$ word3" "caw" "word1 |word3" [] let ``caw finds end of word``() = assertText "a [wo$rd_ ] b" "caw" "a [|] b" [] let ``caw on a word with digits and underscores``() = assertText ".a_8$ b" "caw" ".|b" [] let ``daw on a word delimited by punctuation``() = assertText "w1.w2$.w3" "daw" "w1..$w3" [] let ``caw on a word delimited by punctuation``() = assertText "w1(w2$)w3" "caw" "w1(|)w3" [] let ``daw on sequence of other characters``() = assertText "a ~!@#%^$&*=+:;?/<>(){}b " "daw" "ab$ " [] let ``daw from white space at EOL extends to next line``() = assertText "a $ \n b" "daw" "a$" module aW = // Tests for aW. Reference: http://vimdoc.sourceforge.net/htmldoc/motion.html#aW [] let ``daW deletes a WORD``() = assertText "a W.W$ b" "daW" "a b$" [] let ``daW removes trailing space``() = assertText "a W.W$ " "daW" "a $" [] let ``daW removes leading space if no trailing``() = assertText "a W.W$" "daW" "a$" [] let ``caW changes a WORD``() = assertText "a W.W$ b" "caW" "a |b" module iw = // Tests for iw. Reference: http://vimdoc.sourceforge.net/htmldoc/motion.html#iw [] let ``diw deletes inside word``() = assertText "a b$c d" "diw" "a $d" [] let ``diw on sequence of other characters``() = assertText "a ~!@#%^&*=+:;?/<>(){}$b " "diw" "a b$ " [] let ``ciw changes a word``() = assertText "a b$ c" "ciw" "a | c" [] let ``diw stops at non-word character``() = assertText "%b$_123." "diw" "%.$" [] let ``diw deletes whitespace``() = assertText "a $ b" "diw" "ab$" [] let ``diw does not extend to next line``() = assertText "a $ \n b" "diw" "a$\n b" [] let ``ciw changes inside word``() = assertText "a b$c d" "ciw" "a | d" // Tests for iW. [] let ``diW deletes a WORD``() = assertText "a W.W$ b" "diW" "a $b" [] let ``ciW changes a WORD``() = assertText "a W.W2$ b" "ciW" "a | b" // Tests for quoted strings. Reference: http://vimdoc.sourceforge.net/htmldoc/motion.html#a` // Handling of different quotes is identical. The tests alternate between ', " and ` [] let ``ci' before quoted string``() = assertText "var$ a = 'value'" "ci'" "var a = '|'" [] let ``ci" inside quoted string``() = assertText "var a = \"value$\"" "ci\"" "var a = \"|\"" [] let ``di` before quoted string``() = assertText "var$ a = `value`" "di`" "var a = ``$" [] let ``di' inside quoted string``() = assertText "var a = 'value$'" "di'" "var a = ''$" // TODO: ca" and da" tests cheat. The commands should delete the white space after the closing quote [] let ``ca" before quoted string``() = assertText "var$ a = \"value\"" "ca\"" "var a = |" [] let ``ca` inside quoted string``() = assertText "var a = `value$`" "ca`" "var a = |" [] let ``da' before quoted string``() = assertText "var$ a = 'value'" "da'" "var a = $" [] let ``da` inside quoted string``() = assertText "var a = `value$`" "da`" "var a = $" [] [] let ``ci" does nothing when no quoted text on line``() = assertText "var$ a = b\n" "ci\"" "var$ a = b\n" [] [] let ``ci" handles escaped quote``() = assertText """ var$ a = "\"" """ "ci\"" " var a = \"$\" " // Tests for braces. Reference: http://vimdoc.sourceforge.net/htmldoc/motion.html#a) [] let ``ci( handles nested parentheses backwards``() = assertText "(ignored)\n((abc)\n de$f)\n(ignored)" "ci(" "(ignored)\n(|)\n(ignored)" [] let ``ci( handles nested parentheses forwards``() = assertText "(ignored)\n(abc$\n (def))\n(ignored)" "ci(" "(ignored)\n(|)\n(ignored)" [] let ``da{ handles nested braces forwards``() = assertText "{a$ {b}}\n{ignored}" "da{" "\n${ignored}" // Tags. Reeference: http://vimdoc.sourceforge.net/htmldoc/motion.html#tag-blocks module at = [] let ``dat from tag content``() = assertText "val$" "dat" "<$/a>" [] let ``dat from tag declaration``() = assertText "val" "dat" "<$/a>" [] let ``dat ignores unclosed tag``() = assertText "val" "dat" "val" [] let ``dat ignores self-closing tag``() = assertText "" "dat" "$" [] let ``dat handles tags with attributes``() = assertText "val$" "dat" "<$/a>" [] let ``dat handles tags with namespaces``() = assertText "val$" "dat" "<$/a>" [] let ``dat finds unmatched closing tag``() = assertText "" "dat" "<$/a>" [] let ``dat when closing > is on separate line``() = assertText "val$" "dat" "$" [] let ``cat from tag content``() = assertText "val$" "cat" "|" [] let ``cat from tag declaration``() = assertText "val" "cat" "|" [] [] let ``cat ignores unclosed tag``() = assertText "val" "cat" "val" [] let ``cat handles tags with attributes``() = assertText "val$" "cat" "|" [] let ``cat ignores self-closing tag``() = assertText "" "cat" "|" module it = [] let ``dit from tag content``() = assertText "val$" "dit" "<$/b>" [] let ``dit from tag declaration``() = assertText "val" "dit" "<$/b>" [] let ``dit finds unmatched tag``() = assertText "" "dit" "<$/b>" [] let ``dit outside a tag does nothing``() = assertText " $ val" "dit" " $ val" [] let ``cit from tag content``() = assertText "val$" "cit" "|" [] let ``cit from tag declaration``() = assertText "val" "cit" "|" [] let ``cit finds unmatched tag``() = assertText "" "cit" "|" [] let ``cit when closing > is on separate line``() = assertText "val$" "cit" "|" [] [] let ``cit outside a tag does nothing``() = assertText " $ val" "cit" " $ val" ================================================ FILE: XSVim.Tests/VisualTests.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Visual tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() let getClipboard() = Vim.registers.[EmptyRegister].content [] let ``Visual to end of line``() = let _ = test "abc$ def\nghi" "v$y" getClipboard() |> should equal "c def" [] let ``Visual to end of word``() = let _ = test "ab$c def\nghi" "vey" getClipboard() |> should equal "bc" [] let ``Visual to d``() = let _ = test "ab$cdef" "vtdy" getClipboard() |> should equal "bc" [] let ``Visual to d inclusive``() = let _ = test "ab$cdef" "vfdy" getClipboard() |> should equal "bcd" [] let ``Visual supports multipler``() = let _ = test "a$bcdef" "3vy" getClipboard() |> should equal "abc" [] let ``Visual line``() = let _ = test "aaa\nbb$b\nddd" "Vy" getClipboard() |> should equal "bbb\n" [] let ``Visual to end of document``() = let _ = test "abc\nde$f\nghi" "vGy" getClipboard() |> should equal "ef\ng" [] let ``Visual to start of document``() = let _ = test "abc\nde$f\nghi" "vggy" getClipboard() |> should equal "abc\nde" [] let ``Visual line to end of document``() = let _ = test "abc\nde$f\nghi" "VGy" getClipboard() |> should equal "def\nghi" [] let ``Visual line to start of document``() = let _ = test "abc\nde$f\nghi" "Vggy" getClipboard() |> should equal "abc\ndef\n" [] let ``Visual line supports multipler``() = let _ = test "abc\nde$f\nghi" "2Vy" getClipboard() |> should equal "def\nghi" [] let ``Goto visual goes to last selection``() = let _ = test "abc\nde$f\nghi" "2V1Ggvy" getClipboard() |> should equal "def\nghi" [] let ``Visual inside quotes``() = let _ = test "let s = \"some$ string\"" "vi\"y" getClipboard() |> should equal "some string" [] let ``v]) goes to next unmatched )``() = let _ = test "if (a$ == (b)c)" "v])y" getClipboard() |> should equal "a == (b)c)" [] let ``caret moves to other end of selection``() = assertText "abc$def" "vlllo" "abc$def" [] let ``selection is not affected when you move to other end``() = let _ = test "abc$def" "vllloy" getClipboard() |> should equal "cdef" [] let ``Moving to the EOF in visual mode does select text``() = let _ = test " $\na\n" "vGy" Vim.registers.[EmptyRegister].content |> should equal " \na\n" [] let ``Moving by a paragraph to the start of file does select text``() = let _ = test "start\n $" "v{y" Vim.registers.[EmptyRegister].content |> should equal "start\n " ================================================ FILE: XSVim.Tests/XSVim.Tests.fsproj ================================================  Debug AnyCPU {3C522649-67F6-4F65-9CC9-D5847768FE68} Library XSVim.Tests XSVim.Tests v4.7.2 /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/vstool.exe run-md-tests true full false bin\Debug DEBUG prompt true bin\Release prompt true $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets ..\lib\MonoDevelop.SourceEditor.dll ..\lib\MonoDevelop.Ide.dll ..\lib\MonoDevelop.Core.dll True True ..\lib\GuiUnit.exe ..\packages\FSharp.Core.4.6.2\lib\net45\FSharp.Core.dll {9DB313D4-4CD1-455F-846F-42CD234DE626} XSVim ================================================ FILE: XSVim.Tests/YankAndPut.fs ================================================ namespace XSVim.Tests open NUnit.Framework open XSVim open System.Runtime.CompilerServices open System.Threading.Tasks [] module ``Yank and put tests`` = [)>] let ``run before tests``() = FixtureSetup.initialiseMonoDevelop() [] let ``Yanking puts cursor at original position before selection was made``() = assertText "a$bc" "vly" "a$bc" [] let ``Yanking line supports multiplier``() = let _ = test "a$bc\ndef\nghi" "2yy" Vim.registers.[EmptyRegister].content |> should equal "abc\ndef\n" [] let ``Yanking doesn't move caret when there is no selection'``() = assertText "a$bcdef" "vlly$" "abc$def" [] let ``Should put line at last line``() = assertText " abc$\ndef" "yyjp" " abc\ndef\n a$bc" [] let ``Should put ab after``() = assertText "a$bc" "vldp" "cab$" [] let ``Should put abc over selection in visual mode``() = assertText "a$bc" "vlyvllp" "ab$" [] let ``P acts like p in visual mode``() = assertText "a$bc" "vlyvllP" "ab$" [] let ``Can yank into a named register``() = let _ = test "ab$cd ef" "\"dyl" Vim.registers.[Register 'd'].content |> should equal "b" [] let ``yw at the end of a line consumes entire line``()= assertText "a$bc" "ywp" "aabc$bc" [] let ``Visual line selection should work at EOF``() = assertText "123\na$bc" "Vyp" "123\nabc\na$bc" [] let ``Single line yank should work at EOF``() = assertText "123\na$bc" "yyp" "123\nabc\na$bc" [] let ``Line yank should work at EOF``() = assertText "abc\nde$f" "yyp" "abc\ndef\nd$ef" [] let ``Single line yank containing delimiter``() = assertText "1$23\nabc" "yyp" "123\n1$23\nabc" [] let ``Linewise put places caret at start of line``() = assertText " $ 123\n" "yyp" " 123\n 1$23\n" [] let ``Linewise Put places caret at start of line``() = assertText " $ 123\n" "yyP" " 1$23\n 123\n" [] let ``Linewise put at EOF places caret at start of line``() = assertText "\n $ 123" "yyp" "\n 123\n 1$23" [] let ``Multi line put places caret at top line of paste``() = assertText "aa$a\nbbb\nccc\n" "Vjdp" "ccc\na$aa\nbbb\n" [] let ``Multi line put on line without delimiter places caret at top line of paste``() = assertText "aa$a\nbbb\nccc" "Vjdp" "ccc\na$aa\nbbb" [] let ``x with multiplier stops at EOL``() = assertText "ab$cdef\n" "100xp" "abcdef$\n" [] let ``2dw yank two words``() = assertText "p$ublic int someInt = 1;" "2dwP" "public int s$omeInt = 1;" [] let ``2d} yank two paragraphs``() = let _ = test " $\na\n\nb\n" "2d}" Vim.registers.[EmptyRegister].content |> should equal " \na\n\nb\n" ================================================ FILE: XSVim.Tests/packages.config ================================================  ================================================ FILE: XSVim.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "XSVim", "XSVim\XSVim.fsproj", "{9DB313D4-4CD1-455F-846F-42CD234DE626}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E0ACA694-13C8-4B99-9FC9-196E85272651}" ProjectSection(SolutionItems) = preProject addin-project.xml = addin-project.xml .travis.yml = .travis.yml build.sh = build.sh test.sh = test.sh EndProjectSection EndProject Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "XSVim.Tests", "XSVim.Tests\XSVim.Tests.fsproj", "{3C522649-67F6-4F65-9CC9-D5847768FE68}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9DB313D4-4CD1-455F-846F-42CD234DE626}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9DB313D4-4CD1-455F-846F-42CD234DE626}.Debug|Any CPU.Build.0 = Debug|Any CPU {9DB313D4-4CD1-455F-846F-42CD234DE626}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DB313D4-4CD1-455F-846F-42CD234DE626}.Release|Any CPU.Build.0 = Release|Any CPU {3C522649-67F6-4F65-9CC9-D5847768FE68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3C522649-67F6-4F65-9CC9-D5847768FE68}.Debug|Any CPU.Build.0 = Debug|Any CPU {3C522649-67F6-4F65-9CC9-D5847768FE68}.Release|Any CPU.ActiveCfg = Release|Any CPU {3C522649-67F6-4F65-9CC9-D5847768FE68}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal ================================================ FILE: addin-project.xml ================================================ XSVim/bin/Debug/XSVim.dll XSVim.sln Debug ================================================ FILE: build.sh ================================================ /Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild XSVim.sln ================================================ FILE: copy-assemblies.sh ================================================ #!/usr/bin/env bash cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/bin/Mono.Addins.dll lib/ cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/bin/MonoDevelop.Core.dll lib/ cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/bin/MonoDevelop.Ide.dll lib/ cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/AddIns/DisplayBindings/SourceEditor/MonoDevelop.SourceEditor.dll lib/ cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/bin/System.Collections.Immutable.dll lib/ cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/bin/Xamarin.Mac.dll lib/ cp /Applications/Visual\ Studio.app/Contents/Resources/lib/monodevelop/bin/Xwt.dll lib/ ================================================ FILE: run-from-source.sh ================================================ #! /bin/bash MONODEVELOP_CONSOLE_LOG_LEVEL=All MONODEVELOP_DEV_ADDINS=$(pwd)/XSVim/bin/Debug /Applications/Visual\ Studio.app/Contents/MacOS/VisualStudio --no-redirect XSVim.sln ================================================ FILE: test.sh ================================================ mono "/Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/vstool.exe" run-md-tests XSVim.Tests/bin/Debug/XSVim.Tests.dll -labels