Full Code of nosami/XSVim for AI

8.1 cec402b3509d cached
47 files
218.4 KB
57.3k tokens
1 requests
Download .txt
Showing preview only (231K chars total). Download the full file or copy to clipboard to get everything.
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. `<C-w>s` and `<C-w>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 `<esc>` 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<string>() 
    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<SearchResultPad> 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<Gdk.Key>, 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

    [<CommandUpdateHandler ("MonoDevelop.Ide.Commands.EditCommands.Undo")>]
    // We handle cmd-z ourselves to use the vim undo stack
    member x.CanUndo(ci:CommandInfo) = ci.Enabled <- false

    [<CommandUpdateHandler ("MonoDevelop.Ide.Commands.EditCommands.Rename")>]
    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.
    [<CommandHandler ("XSVim.HalfPageDown")>]
    member x.HalfPageDown() = ctrl "d"

    [<CommandHandler ("XSVim.PageDown")>]
    member x.PageDown() = ctrl "f"

    [<CommandHandler ("XSVim.PageUp")>]
    member x.PageUp() = ctrl "b"

    [<CommandHandler ("XSVim.FindFile")>]
    member x.FindFile() = ctrl "p"

    [<CommandHandler ("XSVim.DynamicAbbrev")>]
    member x.DynamicAbbrev() = ctrl "n"

    [<CommandHandler ("XSVim.NavigateBackwards")>]
    member x.NavigateBackwards() = ctrl "o"

    [<CommandHandler ("XSVim.NavigateForwards")>]
    member x.NavigateForwards() = ctrl "i"

    [<CommandHandler ("XSVim.IncrementNumber")>]
    member x.IncrementNumber() = ctrl "x"

    [<CommandHandler ("XSVim.DecrementNumber")>]
    member x.DecrementNumber() = ctrl "a"

    [<CommandHandler ("XSVim.Escape")>]
    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
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<scheme version="1.0">
    <!-- Vim keybinding scheme containing all the keys that
         conflict with keybindings in the Visual Studio keybinding scheme-->
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.DeleteRightChar" shortcut="" />
    <binding command="XSVim.HalfPageDown" shortcut="Control+D" />
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.LineUp" shortcut="" />
    <binding command="XSVim.FindFile" shortcut="Control+P" />
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.CharRight" shortcut="" />
    <binding command="XSVim.PageDown" shortcut="Control+F" />
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.CharLeft" shortcut="" />
    <binding command="XSVim.PageUp" shortcut="Control+B" />
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.LineDown" shortcut="" />
    <binding command="XSVim.DynamicAbbrev" shortcut="Control+N" />
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.InsertNewLinePreserveCaretPosition" shortcut="" />
    <binding command="XSVim.NavigateBackwards" shortcut="Control+O" />
    <binding command="MonoDevelop.Ide.CodeFormatting.CodeFormattingCommands.FormatBuffer" shortcut="" />
    <binding command="XSVim.NavigateForwards" shortcut="Control+I" />
    <binding command="MonoDevelop.Ide.Commands.TextEditorCommands.LineStart" shortcut = "Meta+Left"/> <!-- removed ctrl-a -->
    <binding command="XSVim.DecrementNumber" shortcut="Control+A" />
    <binding command="XSVim.IncrementNumber" shortcut="Control+X" />
    <binding command="XSVim.Escape" shortcut="Escape Control+C Control+[" />
</scheme>

================================================
FILE: XSVim/Mapping.fs
================================================
namespace XSVim

[<AutoOpen>]
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
[<assembly:Addin (
  "XSVim",
  Namespace = "XSVim",
  Version = version
)>]

[<assembly:AddinName ("Vim")>]
[<assembly:AddinCategory ("IDE extensions")>]
[<assembly:AddinDescription ("Vim emulation layer for Xamarin Studio / Visual Studio for Mac.")>]
[<assembly:AddinUrl ("https://github.com/nosami/XSVim")>]
[<assembly:AddinAuthor ("jason")>]
[<assembly:AddinDependency ("::MonoDevelop.Core", "8.1")>]
[<assembly:AddinDependency ("::MonoDevelop.Ide", "8.1")>]
[<assembly:AddinDependency ("::MonoDevelop.SourceEditor2", "8.1")>]
()


================================================
FILE: XSVim/Properties/AssemblyInfo.fs
================================================
namespace XSVim
open System.Reflection
open System.Runtime.CompilerServices

[<AutoOpen>]
module AddinVersion =
    [<Literal>]
    let version = "0.65.12.81"

[<assembly: AssemblyTitle("XSVim")>]


[<assembly: AssemblyVersion(version)>]

//[<assembly: AssemblyDelaySign(false)>]
//[<assembly: AssemblyKeyFile("")>]

()


================================================
FILE: XSVim/Properties/Manifest.addin.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionModel>
    <Runtime>
    </Runtime>
    <Extension path="/MonoDevelop/Ide/TextEditorExtensions">
        <Class class="XSVim.XSVim" />
    </Extension>
    <Extension path = "/MonoDevelop/Ide/Commands">
        <Category _name = "Vim" id = "Vim" >
            <!-- Override undo to use the vim undo stack -->
            <Command id = "MonoDevelop.Ide.Commands.EditCommands.Undo"
                _label = "_Undo"
                icon = "gtk-undo"
                _description = "Undo (vim)"
                shortcut = "Control|Z"
                macShortcut = "Meta|Z" />
            <!-- Commands that may conflict with built in keybinding schemes -->
            <Command id="XSVim.HalfPageDown" _label="Half page down" _description="Move half a page down" shortcut="Control|D" />
            <Command id="XSVim.FindFile" _label="Find file" shortcut="Control|P" />
            <Command id="XSVim.PageDown" _label="Page down" shortcut="Control|F" />
            <Command id="XSVim.PageUp" _label="Page up" shortcut="Control|B" />
            <Command id="XSVim.DynamicAbbrev" _label="Complete from file" shortcut="Control|N" />
            <Command id="XSVim.NavigateBackwards" _label="Navigate Backwards" shortcut="Control|O" />
            <Command id="XSVim.NavigateForwards" _label="Navigate Forwards" shortcut="Control|I" />
            <Command id="XSVim.NavigateForwards" _label="Navigate Forwards" shortcut="Control|I" />
            <Command id="XSVim.IncrementNumber" _label="Increment Number" shortcut="Control|X" />
            <Command id="XSVim.DecrementNumber" _label="Decrement Number" shortcut="Control|A" />
            <Command id="XSVim.Escape" _label="Return to normal mode" shortcut="Escape Control|C Control|[" />
        </Category>
    </Extension>
    <Extension path="/MonoDevelop/Ide/GlobalOptionsDialog/Other">
        <Section id="VimSettings" _label="Vim Settings" class = "XSVim.SettingsPanel" icon="md-prefs-source" />
    </Extension>
    <Extension path = "/MonoDevelop/Ide/KeyBindingSchemes">
        <Scheme id="XSVim" _name = "Visual Studio + Vim" resource="KeyBindingSchemeVim.xml" forMac="true" />
    </Extension>
</ExtensionModel>


================================================
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<unit> then [| |]
              elif not(FSharpType.IsTuple(argType)) then [| args |]
              else FSharpValue.GetTupleFields(args)

            // Static member call (on value of type System.Type)?
            if (typeof<System.Type>).IsAssignableFrom(o.GetType()) then
                let methods = (unbox<Type> o).GetMethods(staticFlags) |> Array.map asMethodBase
                let ctors = (unbox<Type> 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<System.Type>).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<System.Type>)) 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 -> "<esc>"
        | Backspace -> "<bs>"
        | Ret -> "<ret>"
        | Delete -> "<del>"
        | Control k -> sprintf "<C-%c>" k
        | Super k -> sprintf "<D-%c>" k
        | Down -> "<down>"
        | Up -> "<up>"
        | Left -> "<left>"
        | Right -> "<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

[<AutoOpen>]
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<obj>
            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

[<AutoOpen>]
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<string, Marker>()

    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 = "</" + tagName + ">"
                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<Register, XSVim.Selection>()
    let editorStates = Dictionary<FilePath, VimState>()

    registers.[EmptyRegister] <- { linewise=false; content="" }

    let macros = Dictionary<char, VimAction list>()

    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>

            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<Match>

                        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<Match>

                        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
        | ["<left>"]
        | ["h"] -> Some Left
        | ["<down>"]
        | ["j"] -> Some Down
        | ["<up>"]
        | ["k"] -> Some Up
        | ["<right>"]
        | ["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)
        | ["<C-d>"] -> Some (Jump HalfPageDown)
        | ["<C-u>"] -> Some (Jump HalfPageUp)
        | ["<C-f>"] -> Some (Jump PageDown)
        | ["<C-b>"] -> 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
        | "<C-v>" -> Some VisualBlockMode
        | "<C-q>" -> Some VisualBlockMode
        | "V" -> Some VisualLineMode
        | "R" -> Some ReplaceMode
        | _ -> None

    let (|Escape|_|) = function
        | "<esc>" | "<C-c>" | "<C-[>" -> 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, [ "<C-r>" ] -> [ 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, [ "<C-y>" ] -> [ dispatch TextEditorCommands.ScrollLineUp ]
            | NormalMode, [ "<C-e>" ] -> [ dispatch TextEditorCommands.ScrollLineDown ]
            | NormalMode, [ "<C-o>" ] -> [ dispatch NavigationCommands.NavigateBack ]
            | NormalMode, [ "<C-i>" ] -> [ dispatch NavigationCommands.NavigateForward ]
            | NormalMode, RemappedMatches layout [ "r" ] _ -> wait
            | NormalMode, [ RemappedMatchesChar layout "r" _; "<ret>" ] -> [ 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, [ "<ret>" ] -> [ 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, [ "<C-p>" ] -> [ dispatch SearchCommands.GotoFile ]
            | NotInsertMode, [ "<C-w>" ] -> wait
            | NotInsertMode, [ "<C-w>"; "w" ]
            | NotInsertMode, [ "<C-w>"; "<C-w>" ] -> [ func Window.switchWindow ]
            | NotInsertMode, [ "<C-w>"; "h" ] -> [ func Window.leftWindow ]
            | NotInsertMode, [ "<C-w>"; "l" ] -> [ func Window.rightWindow ]
            // These commands don't work the same way as vim yet, but better than nothing
            | NotInsertMode, [ "<C-w>"; "o" ] -> [ dispatch FileTabCommands.CloseAllButThis ]
            | NotInsertMode, [ "<C-w>"; "c" ] -> [ func Window.closeTab ]
            | NotInsertMode, [ "<C-w>"; "v" ]
            | NotInsertMode, [ "<C-w>"; "s" ]
            | NotInsertMode, [ "<C-w>"; "<C-v>" ]
            | NotInsertMode, [ "<C-w>"; "<C-s>" ] ->
                let notebooks = Window.getNotebooks()
                if notebooks.Length < 2 then
                    [ dispatch "MonoDevelop.Ide.Commands.ViewCommands.SideBySideMode" ]
                else
                    resetKeys
            | InsertMode, [ "<C-n>" ] -> [ dispatch TextEditorCommands.DynamicAbbrev ]
            | NotInsertMode, [ "<C-a>" ] -> [ run IncrementNumber Nothing; switchMode NormalMode ]
            | NotInsertMode, [ "<C-x>" ] -> [ 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
================================================
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{9DB313D4-4CD1-455F-846F-42CD234DE626}</ProjectGuid>
    <OutputType>Library</OutputType>
    <RootNamespace>XSVim</RootNamespace>
    <AssemblyName>XSVim</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <UseMSBuildEngine>false</UseMSBuildEngine>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>portable</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug</OutputPath>
    <DefineConstants>DEBUG</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <PlatformTarget></PlatformTarget>
    <StartAction>Program</StartAction>
    <OtherFlags>--warnon:1182</OtherFlags>
    <StartAction>Program</StartAction>
    <StartProgram>/Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/VisualStudio.exe</StartProgram>
    <StartWorkingDirectory>/Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin</StartWorkingDirectory>
    <Commandlineparameters>-no-redirect</Commandlineparameters>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>portable</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release</OutputPath>
    <DefineConstants></DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <GenerateTailCalls>true</GenerateTailCalls>
    <PlatformTarget></PlatformTarget>
  </PropertyGroup>
  <PropertyGroup>
    <FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="MonoDevelop.SourceEditor">
      <HintPath>..\lib\MonoDevelop.SourceEditor.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="MonoDevelop.Core">
      <HintPath>..\lib\MonoDevelop.Core.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="MonoDevelop.Ide">
      <HintPath>..\lib\MonoDevelop.Ide.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
    <Reference Include="Mono.Addins">
      <HintPath>..\lib\Mono.Addins.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
    <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
    <Reference Include="System.Collections.Immutable">
      <HintPath>..\lib\System.Collections.Immutable.dll</HintPath>
    </Reference>
    <Reference Include="Xamarin.Mac">
      <HintPath>..\lib\Xamarin.Mac.dll</HintPath>
    </Reference>
    <Reference Include="Xwt">
      <HintPath>..\lib\Xwt.dll</HintPath>
    </Reference>
    <Reference Include="atk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
    <Reference Include="pango-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
    <Reference Include="Mono.Cairo" />
    <Reference Include="FSharp.Core">
      <HintPath>..\packages\FSharp.Core.4.5.4\lib\net45\FSharp.Core.dll</HintPath>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.fs" />
    <Compile Include="Properties\AddinInfo.fs" />
    <EmbeddedResource Include="Properties\Manifest.addin.xml" />
    <Compile Include="SettingsPanel.fs" />
    <Compile Include="Reflection.fs" />
    <Compile Include="Classes.fs" />
    <Compile Include="Types.fs" />
    <Compile Include="WindowManagement.fs" />
    <Compile Include="SubstituteCommand.fs" />
    <Compile Include="ExMode.fs" />
    <Compile Include="PadTreeViews.fs" />
    <Compile Include="TreeViewPads.fs" />
    <Compile Include="Mapping.fs" />
    <Compile Include="XSVim.fs" />
    <Compile Include="Addin.fs" />
    <EmbeddedResource Include="KeyBindingSchemeVim.xml" />
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(FSharpTargetsPath)" />
</Project>

================================================
FILE: XSVim/packages.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="FSharp.Core" version="4.5.4" targetFramework="net472" />
</packages>

================================================
FILE: XSVim.Tests/ChangeTests.fs
================================================
namespace XSVim.Tests
open NUnit.Framework
open System.Runtime.CompilerServices
open System.Threading.Tasks

[<TestFixture>]
module ``Change tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``cc non empty line``() =
        assertText "abc\nd$ef\nghi" "cc" "abc\n|\nghi"

    [<Test>]
    let ``cw changes word``() =
        assertText "a$bc    def" "cw" "|    def"

    [<Test>]
    let ``C changes last character``() =
        assertText "abc$" "C" "ab|"

    [<Test>]
    let ``cw changes space``() =
        assertText "abc $def" "cw" "abc|def"

    [<Test>]
    let ``c2w changes two words``() =
        assertText "a$bc def ghi" "c2w" "| ghi"

    [<Test>]
    let ``undo works after cw``() =
        assertText "a$bc def ghi" "cw<esc>u" "a$bc def ghi"

    [<Test>]
    let ``undo works after c2w``() =
        assertText "a$bc def ghi" "c2w<esc>u" "a$bc def ghi"

    [<Test>]
    let ``ce changes word``() =
        assertText "a$bc def" "ce" "| def"

    [<Test>]
    let ``ce changes last character``() =
        assertText "a$ bcd" "ce" "| bcd"

    [<Test>]
    let ``c% changes to matching parens``() =
        assertText "abc($def)ghi" "c%" "abc|ghi"

    [<Test>]
    let ``Change to end of word does not include dot``() =
        assertText "open Mon$o.Addins" "ce" "open Mo|.Addins"

    [<Test>]
    let ``Change to end of word includes dot``() =
        assertText "open Mono$.Addins" "ce" "open Mon|Addins"

    [<Test>]
    let ``cc empty line``() =
        assertText "abc\n\n$def" "cc" "abc\n|\ndef"

    [<Test>]
    let ``ci backtick``() =
        assertText "``some t$ext``" "ci`" "``|``"

    [<Test>]
    let ``S changes entire line``() =
        assertText " line1 \n line2$ \n line3 " "S" " line1 \n |\n line3 "

    [<Test>]
    let ``2S changes two lines``() =
        assertText " line1 \n line2$ \n line3 \n line4 " "2S" " line1 \n |\n line4 "

    [<Test>]
    let ``s before the end of line``() =
        assertText "a$b" "s" "|b"

    [<Test>]
    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

[<TestFixture>]
module ``Delete tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``Vjd test``() =
        let source =
            @"aaaaaa
              bb$bbbb
              cccccc
              dddddd
              eeeeee";

        let expected =
            @"aaaaaa
              d$ddddd
              eeeeee";
        assertText source "Vjd" expected

    [<Test>]
    let ``Delete line and line below``() =
        let source =
            @"aaaaaa
              bb$bbbb
              cccccc
              dddddd
              eeeeee";

        let expected =
            @"aaaaaa
              d$ddddd
              eeeeee";
        assertText source "dj" expected

    [<Test>]
    let ``Delete line and next two lines``() =
        let source =
            @"aaaaaa
              bb$bbbb
              cccccc
              dddddd
              eeeeee";

        let expected =
            @"aaaaaa
              e$eeeee";
        assertText source "d2j" expected

    [<Test>]
    let ``dd first line``() =
        assertText "ab$c\n  def" "dd" "  d$ef"

    [<Test>]
    let ``dd next line is blank``() =
        assertText

            """
{
    fo$o

    bar
}
            """

            "dd"
// $ is over \n here
            """
{

$    bar
}
            """

    [<Test>]
    let ``2dd deletes 2 lines``() =
        assertText "ab$c\ndef\nghi" "2dd" "g$hi"

    [<Test>]
    let ``2ddp puts 2 lines back``() =
        assertText "abc\nde$f\nghi" "2ddp" "abc\nd$ef\nghi"

    [<Test>]
    let ``dd last line at EOF``() =
        assertText "abc\ndef\ngh$i" "dd" "abc\nd$ef"

    [<Test>]
    let ``dd only line``() =
        assertText "a$bc" "dd" "$"

    [<Test>]
    let ``Delete char under caret``() =
        assertText "abc$def" "x" "abd$ef"

    [<Test>]
    let ``Delete char at EOL``() =
        assertText "abcdef$\n" "xx" "abcd$\n"

    [<Test>]
    let ``x with multiplier stops at EOL (caret at EOL)``() =
        assertText "abcdef$\n" "4x" "abcde$\n"

    [<Test>]
    let ``x with multiplier stops at EOL``() =
        assertText "ab$cdef\n" "100x" "a$\n"

    [<Test>]
    let ``Delete char to left of caret``() =
        assertText "abc$def" "X" "ac$def"

    [<Test>]
    let ``Delete to end of line``() =
        assertText "abc$ def\nghi" "d$" "ab$\nghi"

    [<Test>]
    let ``Delete to end of document``() =
        assertText "abc\nde$f\nghi" "dG" "abc\n$"

    [<Test>]
    let ``Delete to start of document``() =
        assertText "abc\nde$f\nghi" "dgg" "g$hi"

    [<Test>]
    let ``Delete to end of line using D``() =
        assertText "abc$ def\nghi" "D" "ab$\nghi"

    [<Test>]
    let ``Delete to end of line from start keeps caret on current line``() =
        assertText "abc\nd$ef\nghi" "D" "abc\n\n$ghi"

    [<Test>]
    let ``Deletes word``() =
        assertText "a$bc     def" "dw" "d$ef"

    [<Test>]
    let ``Delete to end of word``() =
        assertText "ab$c def" "de" "a $def"

    [<Test>]
    let ``Delete to end of word does not include dot``() =
        assertText "open Mon$o.Addins" "de" "open Mo.$Addins"

    [<Test>]
    let ``Delete to end of word includes dot``() =
        assertText "open Mono$.Addins" "de" "open MonA$ddins"

    [<Test>]
    let ``Delete word does not include dot``() =
        assertText "open Mo$no.Addins" "dw" "open M.$Addins"

    [<Test>]
    let ``Delete to end of WORD``() =
        assertText "ab$c.def ghi" "dE" "a $ghi"

    [<Test>]
    let ``Deleting last word doesn't delete delimiter'``() =
        assertText "abc d$ef  \nghi" "dw" "abc $\nghi"

    [<Test>]
    let ``Deleting last word touching EOL doesn't delete delimiter'``() =
        assertText "abc d$ef\nghi" "dw" "abc $\nghi"

    [<Test>]
    let ``Delete char to left doesn't delete past start of line``() =
        assertText "abcdef\nab$cdef" "XXX" "abcdef\nb$cdef"

    [<Test>]
    let ``dw last word at EOF``() =
        assertText "a$bc" "dw" "$"

    [<Test>]
    let ``dw to brace #167``() =
        assertText "abc\n $  {" "dw" "abc\n{$"

    [<Test>]
    let ``d]) deletes to next unmatched )``() =
        assertText "if (a$ == (b)c)" "d])" "if ($"
    [<Test>]
    let ``dib deletes nested brackets``() =
        assertText "(fo$o (bar (foo) )  )" "dib" "()$"

    [<Test>]
    let ``dib deletes nested brackets backwards``() =
        assertText "(foo (bar (foo) ) $ )" "dib" "()$"

    [<Test>]
    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

[<TestFixture>]
module ``Ex mode tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``/ searches for word``() =
        assertText "ab$c abc" "/abc<ret>" "abc a$bc"

    [<Test>]
    let ``/ is case insensitive``() =
        assertText "ab$c ABC" "/abc<ret>" "abc A$BC"

    [<Test>]
    let ``/ is case sensitive``() =
        assertText "ab$c ABC Abc" "/Abc<ret>" "abc ABC A$bc"

    [<Test>]
    let ``deletes to search term``() =
        assertText "ab$c ABC Abc 123" "d/123<ret>" "a1$23"

    [<Test>]
    let ``n searches for next word``() =
        assertText "ab$c abc abc" "/abc<ret>n" "abc abc a$bc"

    [<Test>]
    let ``n wraps to start``() =
        assertText "ab$c abc abc" "/abc<ret>nn" "a$bc abc abc"

    [<Test>]
    let ``N searches for previous word``() =
        assertText "ab$c abc abc" "/abc<ret>N" "a$bc abc abc"

    [<Test>]
    let ``n searches for previous word after ?``() =
        assertText "abc abc a$bc" "?abc<ret>n" "a$bc abc abc"

    [<Test>]
    let ``? searches for word backwards``() =
        assertText "abc abc a$bc" "?abc<ret>" "abc a$bc abc"

    [<Test>]
    let ``:2 jumps to line 2``() =
        assertText "l$ine1\nline2" ":2<ret>" "line1\nl$ine2"

    [<Test>]
    let ``Backspacing ex mode returns to normal mode``() =
        let _, state, _ = test "abc abc a$bc" "/a<bs><bs>"
        state.mode |> should equal NormalMode

    [<Test>]
    let ``<esc> returns to normal mode``() =
        let _, state, _ = test "abc abc a$bc" "/<esc>"
        state.mode |> should equal NormalMode

    [<Test>]
    let ``<C-c> returns to normal mode``() =
        let _, state, _ = test "abc abc a$bc" "/<C-c>"
        state.mode |> should equal NormalMode

    [<Test>]
    let ``<C-[> returns to normal mode``() =
        let _, state, _ = test "abc abc a$bc" "/<C-[>"
        state.mode |> should equal NormalMode

    [<Test>]
    let ``Displays could not parse message``() =
        let _, state, _ = test "a$bc" ":garbage<ret>"
        state.statusMessage.Value |> should equal "Could not parse :garbage"

    [<Test>]
    let ``Could not parse message is reset``() =
        let _, state, _ = test "a$bc" ":garbage<ret>l"
        state.statusMessage |> should equal None

    [<Test>]
    let ``Deletes lines 2 to 4``() =
        assertText
            """11111
22222
33333
44444
55555$"""

            ":2,4d<ret>"

            """11111
5$5555"""

    [<Test>]
    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

[<TestFixture>]
module ``Indentation tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``>> indents right in normal mode``() =
        assertText "a$bc" ">>" "    a$bc"

    [<Test>]
    let ``indent is repeatable``() =
        assertText "a$bc" ">>." "        a$bc"

    [<Test>]
    let ``V> indents line right``() =
        let text, state, _ = test "a$bc\ndef" "V>"
        text |> should equal "    a$bc\ndef"
        state.mode |> should equal NormalMode

    [<Test>]
    let ``V2> indents line right twice``() =
        assertText "a$bc\ndef" "V2>" "        a$bc\ndef"

    [<Test>]
    let ``>j indents current line and line below``() =
        assertText "a$bc\ndef" ">j" "    a$bc\n    def"

    [<Test>]
    let ``<j unindents current line and line below``() =
        assertText "    a$bc\n    def" "<j" "a$bc\ndef"

    [<Test;Ignore("Doesn't place caret at correct location")>]
    let ``>2j indents current line and two lines below``() =
        assertText "a$bc\ndef\nghi" ">2j" "    a$bc\n    def\n    ghi"

    [<Test>]
    let ``>gg indents to top of file``() =
        assertText "abc\ndef\ngh$i" ">gg" "    abc\n    def\n    gh$i"

    [<Test>]
    let ``V> indents line``() =
        assertText "abc\ndef\ngh$i" ">gg" "    abc\n    def\n    gh$i"

    [<Test>]
    let ``>2gg indents to line 2``() =
        assertText "abc\ndef\ngh$i" ">2gg" "abc\n    def\n    gh$i"

    [<Test>]
    let ``>2G indents to line 2``() =
        assertText "abc\ndef\ngh$i" ">2G" "abc\n    def\n    gh$i"

    [<Test>]
    let ``== autoindents line``() =
        assertText "abc\n    def\ngh$i" "==" "abc\n    def\n    g$hi"

    [<Test>]
    let ``= autoindents selection``() =
        assertText "abc\n    def\ngh$i" "V=" "abc\n    def\n    g$hi"

    [<Test>]
    let ``= autoindents multiple line selection``() =
            assertText "abc\n    de$f\n   ghi\n   jkl" "Vj=" "abc\nd$ef\nghi\n   jkl"

    [<Test>]
    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

[<TestFixture>]
module ``Insertion tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``'O' should insert line above``() =
        //TODO: 'O' is broken on the top line
        assertText " \n a$bcdef" "O" " \n |\n abcdef"

    [<Test>]
    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

[<TestFixture>]
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

    [<Test>]
    let ``10j``() =
        test "10j" |> should equal (Some 10, Move, Down)

    [<Test>]
    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

[<TestFixture>]
module ``Macro tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``Start recording macro q``() =
        let _, state, _ = test " $" "qq"
        state.macro |> should equal (Some (Macro 'q'))

    [<Test>]
    let ``Stop recording macro q``() =
        let _, state, _ = test " $" "qqq"
        state.macro |> should equal None

    [<Test>]
    let ``Replay macro q``() =
        assertText "a$bc abc" "qqfcad<esc>q@q" "abcd abcd$"

    [<Test>]
    let ``Macros are repeatable``() =
        assertText "a$bc abc abc abc" "qqfcad<esc>q3@q" "abcd abcd abcd abcd$"

    [<Test>]
    let ``Macros containing repeats are repeatable``() =
        assertText " $aa aa aa aa" "qq2faab<esc>q3@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

[<TestFixture>]
module ``Marker tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()
    [<Test>]
    let ``ma adds marker a``() =
        assertText "a$bc" "mall`a" "a$bc"

    [<Test>]
    let ``'a moves to start of line of marker a``() =
        assertText "123 a$bc" "mall'a" "1$23 abc"

    [<Test>]
    let ``'. jumps to last edit line``() =
        assertText "  123 a$bc" "i<esc>'." "  1$23 abc"

    [<Test>]
    let ``'' jumps to last jump location line``() =
        assertText "ab$c\ndef" "G''" "a$bc\ndef"

    [<Test>]
    let ``Deletes from 4 up to marker a``() =
        assertText "1234XXXXXXXa$5678" "maF4d`a" "123a$5678"

    [<Test>]
    let ``Selects from 4 up to marker a``() =
        let _ = test "1234XXXXXXXa$5678" "maF4v`ay" 
        Vim.registers.[EmptyRegister].content
        |> should equal "4XXXXXXXa"

    [<Test>]
    let ``Deletes linewise between marker a and marker b``() =
        assertText
            """
.........
aaaaa$aaaa
.........
.........
bbbbbbbbb
.........
            """


              "ma/b<ret>mb:'a,'bd<ret>"

            """
.........
.$........
            """

================================================
FILE: XSVim.Tests/MiscTests.fs
================================================
namespace XSVim.Tests
open NUnit.Framework
open XSVim
open System.Runtime.CompilerServices
open System.Threading.Tasks

[<TestFixture>]
module ``Miscellaneous tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()

    [<Test>]
    let ``'A' should put caret at end of the line``() =
        assertText "abc$def\n" "A" "abcdef|\n"

    [<Test>]
    let ``'A' should put caret at EOF``() =
        assertText "abc$def" "A" "abcdef|"

    [<Test>]
    let ``'a' should append after``() =
        assertText "a$bcdef" "a" "a|bcdef"

    [<Test>]
    let ``'a' should append after last char``() =
        assertText "abcdef$\n" "a" "abcdef|\n"

    [<Test>]
    let ``'a' should append before EOF``() =
        assertText "abcdef$" "a" "abcdef|"

    [<Test>]
    let ``'a' on empty line should keep cursor on the current line``() =
        assertText "\n$abc" "a" "|\nabc"

    [<Test>]
    let ``'I' should insert at first non whitespace``() =
        assertText "   abcdef$" "I" "   |abcdef"

    [<Test>]
    let ``Undo repeat``() =
        assertText "a$bc def ghi" "3dwu" "a$bc def ghi"

    [<Test>]
    let ``Repeat typed chars``() =
        assertText "d$" "iabc <esc>." "abcabc $ d"

    [<Test>]
    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 "<esc>j." config
        let text = getEditorText editor newState

        text
        |> should equal
                """
                System.Collections
                System.Collections$
                """

    [<Test>]
    let ``Large text addition is not too slow!``() =
        let _, state, editor =
            test " $" ""
        editor.InsertText(0, new System.String('x', 200000))

    [<Test>]
    let ``backspace is repeated``() =
        assertText "d$" "iabc<bs> <esc>." "abab $ d"

    [<Test; Ignore("TODO")>]
    let ``delete key is repeated``() =
        assertText "d$" "i<del>abc<esc>." "ababc$"

    [<Test>]
    let ``<C-[> escapes``() =
        assertText "    abc$" "i<C-[>" "    ab$c"

    [<Test>]
    let ``Return to normal mode doesn't move past start of line``() =
        assertText "abc\nd$ef" "i<esc>" "abc\nd$ef"

    [<Test>]
    let ``dot repeats at start of line``() =
        assertText 
            """
            def$
            def
            """ 

            "Iabc<esc>j."

            """
            abcdef
            abc$def
            """ 

    [<Test>]
    let ``dot repeats at end of line``() =
        assertText 
            """
            a$bc
            a$bc
            """ 

            "Adef<esc>j."

            """
            abcdef
            abcdef$
            """ 

    [<Test>]
    let ``Repeat delete word``() =
        assertText "a$bc de fgh" "dww." "de $"

    [<Test>]
    let ``Repeat change word``() =
        assertText "a$bc de fgz " "cwxxx<esc>ww." "xxx de xxx$ "

    [<Test>]
    let ``r should be repeatable``() =
        assertText "a$aaa" "rb$." "baab$"

    [<Test>]
    let ``r<ret> inserts <ret> and indents``() =
        assertText "   aaa$\nbbb" "r<ret>" "   aa\n   \n$bbb"

    [<Test>]
    let ``R switches to replace mode``() =
        let _, state, _ = test "a$bc" "R"
        state.mode |> should equal ReplaceMode

    [<Test>]
    let ``R replaces characters``() =
        assertText "a$bc" "RABCD" "ABCD$"

    [<Test>]
    let ``R replaces digits``() =
        assertText "a$bc" "R123" "123$"

    [<Test>]
    let ``Replace mode inserts at end of line``() =
        assertText "a$bc\ndef" "RABCD" "ABCD\n$def"

    [<Test>]
    let ``Replace mode is undoable``() =
        assertText "a$bc\ndef" "RABCD<esc>u" "a$bc\ndef"

    [<Test>]
    let ``Undo insert mode``() =
        assertText "abc$" "adef ghi jkl<esc>u" "abc$"

    [<Test>]
    let ``J puts caret between joined lines``() =
        assertText "a$bc\ndef" "J" "abc $def"

    [<Test>]
    let ``* finds next word``() =
        assertText " $ abc" "*" "  a$bc"

    [<Test>]
    let ``* does not match substring``() =
        assertText "a$bc abcde abc" "*" "abc abcde a$bc"

    [<Test>]
    let ``* finds next word at caret``() =
        assertText "a$bc abc" "*" "abc a$bc"

    [<Test>]
    let ``n finds next match``() =
        assertText "a$bc abc cba abc" "*n" "abc abc cba a$bc"

    [<Test>]
    let ``* finds next word when on last word char``() =
        assertText "abc$ abc" "*" "abc a$bc"

    [<Test>]
    let ``* wraps to start``() =
        assertText "abc a$bc" "*" "a$bc abc"

    [<Test>]
    let ``* finds next word at EOF``() =
        assertText "abc abc$" "*" "a$bc abc"

    [<Test>]
    let ``# finds previous word at caret``() =
        assertText "abc abc a$bc" "#" "abc a$bc abc"

    [<Test>]
    let ``# matches exact word``() =
        assertText "abc abcde a$bc" "#" "a$bc abcde abc"

    [<Test>]
    let ``£ wraps to end``() =
        assertText "a$bc abc" "£" "abc a$bc"

    [<Test>]
    let ``~ toggles case of char at caret``() =
        assertText "a$bc abc" "~" "Ab$c abc"

    [<Test>]
    let ``~ toggles case of selection``() =
        assertText "A$bC abc" "vll~" "a$Bc abc"

    [<Test>]
    let ``<esc> doesn't move caret left onto newline'``() =
        assertText "\nA$bC abc" "o<esc>" "\nAbC abc\n$"

    [<Test>]
    let ``<C-a> increments next number``() =
        assertText "a$bc 9 " "<C-a>" "abc 10$ "

    [<Test>]
    let ``<C-a> increments second number``() =
        assertText "abc 0 $1 " "<C-a>" "abc 0 2$ "

    [<Test>]
    let ``<C-a> increments same width number``() =
        assertText "a$bc 0 " "<C-a>" "abc 1$ "

    [<Test>]
    let ``<C-a> increments number at caret``() =
        assertText "abc 9$ " "<C-a>" "abc 10$ "

    [<Test>]
    let ``<C-a> increments next negative number``() =
        assertText "a$bc -1 " "<C-a>" "abc 0$ "

    [<Test>]
    let ``<C-x> decrements next number``() =
        assertText "a$bc 10 " "<C-x>" "abc 9$ "

    [<Test>]
    let ``<C-x> decrements -1``() =
        assertText "abc -1$ " "<C-x>" "abc -2$ "

    [<Test>]
    let ``dot repeats 2dd``() =
        assertText 
           """
           a$aaaa
           aaaaa
           bbbbb
           bbbbb
           ccccc
           """

           "2dd."

           """
           c$cccc
           """           

    [<Test>]
    let ``dot repeats 2dj``() =
        assertText
            """
            a$aaaa
            aaaaa
            aaaaa
            bbbbb
            bbbbb
            bbbbb
            """

            "2dj."

            """
            $"""

    [<Test>]
    let ``dot repeats 3S``() =
        assertText
            """
            a$aaaa
            aaaaa
            bbbbb
            bbbbb
            ccccc
            ccccc"""

            "3S<esc>."

            """

$            ccccc"""

================================================
FILE: XSVim.Tests/Movement.fs
================================================
namespace XSVim.Tests
open NUnit.Framework
open XSVim
open System.Runtime.CompilerServices
open System.Threading.Tasks

[<TestFixture>]
module ``Movement tests`` =
    [<SetUp;AsyncStateMachine(typeof<Task>)>]
    let ``run before tests``() =
        FixtureSetup.initialiseMonoDevelop()
    [<Test>]
    let ``Move to next word``() =
        assertText "aa$a bbb" "w" "aaa b$bb"

    [<Test>]
    let ``Move to next word on next line``() =
        assertText "aa$a\n  bbb" "w" "aaa\n  b$bb"

    [<Test>]
    let ``Move to empty line``() =
        assertText "aa$a\n\nbbb" "w" "aaa\n\n$bbb"

    [<Test>]
    let ``Moves to EOF when there is no next word``() =
        assertText "aa$aa" "w" "aaaa$"

    [<Test>]
    let ``w skips over tabs``() =
        assertText "\t$\t\taaaa" "w" "\t\t\ta$aaa"

    [<Test>]
    let ``Move to word end``() =
        assertText "aa$a bbb" "e" "aaa$ bbb"

    [<Test>]
    let ``Move to next word end``() =
        assertText "aaa$ bbb" "e" "aaa bbb$"

    [<Test>]
    let ``Move to second word end``() =
        assertText "aa$a bbb" "ee" "aaa bbb$"

    [<Test>]
    let ``e jumps over punctuation``() =
        assertText "int model$);\n " "e" "int model);$\n "

    [<Test>]
    let ``e jumps over chevrons``() =
        assertText "Task<List<SomeWord$>> nextWord" "e" "Task<List<SomeWord>>$ nextWord"

    [<Test>]
    let ``e jumps from chevron to end of next word``() =
        assertText "Task<List<SomeWord>>$ nextWord" "e" "Task<List<SomeWord>> nextWord$"

    [<Test>]
    let ``e jumps over spaces``() =
        assertText " $  abcde" "e" "   abcde$"

    [<Test>]
    let ``e stops before dot``() =
        assertText "open$ System.Collections.Generic" "e" "open System$.Collections.Generic"

    [<Test>]
    let ``Move to end of line``() =
        assertText "aa$a aaa\nbbb" "$" "aaa aaa$\nbbb"

    [<Test>]
    let ``Move to end of document``() =
        assertText "aa$aaaa\nbbbbbb" "G" "aaaaaa\nb$bbbbb"

    [<Test>]
    let ``Move to start of document``() =
        assertText "aaaaaa\nbb$bbbb" "gg" "a$aaaaa\nbbbbbb"

    [<Test>]
    let ``Move to line 2``() =
        assertText "a$aaaaa\n  bbbbbb" "2gg" "aaaaaa\n  b$bbbbb"

    [<Test>]
    let ``Move to line 3``() =
        assertText "a$aaaaa\nbbbbbb\ncccccc\ndddddd" "3G" "aaaaaa\nbbbbbb\nc$ccccc\ndddddd"

    [<Test>]
    let ``Move down to desired column``() =
        assertText "12345$6\n123\n123456" "jj" "123456\n123\n12345$6"

    [<Test>]
    let ``Move down to last column``() =
        assertText "12345$6\n123\n123456" "j" "123456\n123$\n123456"

    [<Test>]
    let ``Move across then down``() =
        assertText "1$2\n12\n" "lj" "12\n12$\n"

    [<Test>]
    let ``Move ten right``() =
        assertText "a$bcdefghijkl" "10l" "abcdefghijk$l"

    [<Test>]
    let ``Does not move right past delimiter``() =
        assertText "a$b\n" "ll" "ab$\n"

    [<Test>]
    let ``Find moves to digit``() =
        assertText "abc$ d1 d2 d3" "f2" "abc d1 d2$ d3"

    [<Test>]
    let ``Reverse find moves to digit``() =
        assertText "abc d1 d2 d$3" "F1" "abc d1$ d2 d3"

    [<Test>]
    let ``Till moves to digit``() =
        assertText "abc$ d1 d2 d3" "t2" "abc d1 d$2 d3"

    [<Test>]
    let ``Reverse till moves to digit``() =
        assertText "abc d1 d2 d$3" "T1" "abc d1 $d2 d3"

    [<Test>]
    let ``2fd moves to second d``() =
        assertText "abc$ d1 d2 d3" "2fd" "abc d1 d$2 d3"

    [<Test>]
    let ``F finds previous char``() =
        assertText "a a$" "Fa" "a$ a"

    [<Test>]
    let ``f is repeatable with ;``() =
        assertText " $ a1 a2" "fa;" "  a1 a$2"

    [<Test>]
    let ``f is reversed with ,``() =
        assertText " $ a1 a2" "fa;," "  a$1 a2"

    [<Test>]
    let ``t does not move if caret is already just before search char``() =
        assertText " $a1 a2" "ta" " $a1 a2"

    [<Test>]
    let ``T does not move if caret is already just after search char``() =
        assertText "a1 a2$" "Ta" "a1 a2$"

    [<Test>]
    let ``t is repeatable with ;``() =
        assertText " $a1 a2" "ta;" " a1 $a2"

    [<Test>]
    let ``T is repeatable with ;``() =
        assertText "a1 a2$" "Ta;" "a1$ a2"

    [<Test>]
    let ``ge moves back to end of last word``() =
        assertText "abc de$f" "ge" "abc$ def"

    [<Test>]
    let ``ge between words moves back to end of last word``() =
        assertText "abc  $def" "ge" "abc$  def"

    [<Test>]
    let ``ge stops at first character``() =
        assertText "abc$" "ge" "a$bc"

    [<Test>]
    let ``gE moves back to end of last WORD``() =
        assertText "abc def.gh$i" "gE" "abc$ def.ghi"

    [<Test>]
    let ``l stops at EOL``() =
        assertText "abc$\ndef" "l" "abc$\ndef"

    [<Test>]
    let ``space moves past EOL``() =
        assertText "abc$\ndef" " " "abc\nd$ef"

    [<Test>]
    let ``% moves to matching parens``() =
        assertText "($foo(bar))" "%" "(foo(bar))$"

    [<Test>]
    let ``[{ goes to previous unmatched {``() =
        assertText "func { case { a } case$ { b } }" "[{" "func {$ case { a } case { b } }"

    [<Test>]
    let ``[( goes to previous unmatched (``() =
        assertText "if (a == (b)c$)" "[(" "if ($a == (b)c)"

    [<Test>]
    let ``]} goes to next unmatched }``() =
        assertText "func { case$ { a } case { b } }" "]}" "func { case { a } case { b } }$"

    [<Test>]
    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

[<assembly: AssemblyTitle("XSVim.Tests")>]
[<assembly: AssemblyDescription("")>]
[<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("")>]
[<assembly: AssemblyProduct("")>]
[<assembly: AssemblyCopyright("(c) Jason Imison")>]
[<assembly: AssemblyTrademark("")>]

// The assembly version has the format {Major}.{Minor}.{Build}.{Revision}

[<assembly: AssemblyVersion("1.0.0.0")>]

//[<assembly: AssemblyDelaySign(false)>]
//[<assembly: AssemblyKeyFile("")>]

()


================================================
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
[<AutoOpen>]
module FsUnit =

    open System.Diagnostics
    open NUnit.Framework.Constraints

    [<DebuggerNonUserCode>]
    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 firs
Download .txt
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
Condensed preview — 47 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (236K chars).
[
  {
    "path": ".gitattributes",
    "chars": 569,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n*.sln"
  },
  {
    "path": ".gitignore",
    "chars": 207,
    "preview": "# F#\n[Bb]in/\n[Oo]bj/\n.fake/\nrepository/*\n*.suo\n*.pidb\n*.userprefs\n*.GhostDoc.xml\n*.user\n*.dll\n*.pdb\n*.cache\n*.swp\n*.swo\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1429,
    "preview": "language: csharp\nos: osx\nmono: latest\nsolution: XSVim.sln\ninstall:\n- nuget restore XSVim.sln\n- wget https://download.vi"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2016 Jason Imison\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 4233,
    "preview": "# XSVim [![Gitter](https://badges.gitter.im/XSVim/Lobby.svg)](https://gitter.im/XSVim/Lobby?utm_source=badge&utm_medium="
  },
  {
    "path": "XSVim/Addin.fs",
    "chars": 9632,
    "preview": "namespace XSVim\n\nopen System\nopen System.Collections.Generic\nopen MonoDevelop.Components.Commands\nopen MonoDevelop.Core"
  },
  {
    "path": "XSVim/Classes.fs",
    "chars": 1117,
    "preview": "namespace XSVim\nopen MonoDevelop.Core\nopen MonoDevelop.Core.Text\nopen MonoDevelop.Ide\nopen MonoDevelop.Ide.Editor\nopen "
  },
  {
    "path": "XSVim/ExMode.fs",
    "chars": 7219,
    "preview": "namespace XSVim\nopen System\nopen System.Text.RegularExpressions\nopen Reflection\nopen MonoDevelop.Ide\nopen MonoDevelop.I"
  },
  {
    "path": "XSVim/KeyBindingSchemeVim.xml",
    "chars": 1692,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<scheme version=\"1.0\">\n    <!-- Vim keybinding scheme containing all the keys t"
  },
  {
    "path": "XSVim/Mapping.fs",
    "chars": 2428,
    "preview": "namespace XSVim\n\n[<AutoOpen>]\nmodule mapping =\n    let colemakToQwerty = function\n        | \"f\" -> \"e\"\n        | \"p\" ->"
  },
  {
    "path": "XSVim/PadTreeViews.fs",
    "chars": 1096,
    "preview": "namespace XSVim\n\nopen Gtk\nopen MonoDevelop.Ide.Gui.Components\n\nmodule padTreeViews =\n    let select (tree:TreeView) pat"
  },
  {
    "path": "XSVim/Properties/AddinInfo.fs",
    "chars": 586,
    "preview": "namespace XSVim\n\nopen Mono.Addins\nopen MonoDevelop\n[<assembly:Addin (\n  \"XSVim\",\n  Namespace = \"XSVim\",\n  Version = ver"
  },
  {
    "path": "XSVim/Properties/AssemblyInfo.fs",
    "chars": 321,
    "preview": "namespace XSVim\nopen System.Reflection\nopen System.Runtime.CompilerServices\n\n[<AutoOpen>]\nmodule AddinVersion =\n    [<L"
  },
  {
    "path": "XSVim/Properties/Manifest.addin.xml",
    "chars": 2224,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ExtensionModel>\n    <Runtime>\n    </Runtime>\n    <Extension path=\"/MonoDevelop/"
  },
  {
    "path": "XSVim/Reflection.fs",
    "chars": 4928,
    "preview": "namespace XSVim\nopen System\nopen System.Reflection\nopen Microsoft.FSharp.Reflection\n\nmodule Reflection =\n    // Various"
  },
  {
    "path": "XSVim/SettingsPanel.fs",
    "chars": 4281,
    "preview": "namespace XSVim\nopen Gtk\nopen MonoDevelop.Components\nopen MonoDevelop.Core\nopen MonoDevelop.Ide.Gui.Dialogs\n\ntype Setti"
  },
  {
    "path": "XSVim/SubstituteCommand.fs",
    "chars": 1630,
    "preview": "namespace XSVim\nopen System\nopen MonoDevelop.Core\nopen MonoDevelop.Core.ProgressMonitoring\nopen MonoDevelop.Ide\nopen Mo"
  },
  {
    "path": "XSVim/TreeViewPads.fs",
    "chars": 4969,
    "preview": "namespace XSVim\nopen System\nopen Gtk\nopen MonoDevelop.Ide\nopen MonoDevelop.Ide.Gui.Components\nopen MonoDevelop.Ide.Gui."
  },
  {
    "path": "XSVim/Types.fs",
    "chars": 6437,
    "preview": "namespace XSVim\nopen System\nopen System.Threading\nopen System.Threading.Tasks\nopen MonoDevelop.Ide\nopen MonoDevelop.Ide."
  },
  {
    "path": "XSVim/WindowManagement.fs",
    "chars": 4146,
    "preview": "namespace XSVim\nopen MonoDevelop.Core\nopen MonoDevelop.Ide\nopen MonoDevelop.Ide.Commands\nopen Reflection\n\nmodule Window"
  },
  {
    "path": "XSVim/XSVim.fs",
    "chars": 97248,
    "preview": "namespace XSVim\n\nopen System\nopen System.Collections.Generic\nopen System.Text.RegularExpressions\nopen System.Threading\n"
  },
  {
    "path": "XSVim/XSVim.fsproj",
    "chars": 4499,
    "preview": "<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Prop"
  },
  {
    "path": "XSVim/packages.config",
    "chars": 134,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"FSharp.Core\" version=\"4.5.4\" targetFramework=\"net472\" "
  },
  {
    "path": "XSVim.Tests/ChangeTests.fs",
    "chars": 2165,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n\n[<TestFixt"
  },
  {
    "path": "XSVim.Tests/DeleteTests.fs",
    "chars": 4715,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n\n[<TestFixt"
  },
  {
    "path": "XSVim.Tests/ExModeTests.fs",
    "chars": 2810,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/IndentationTests.fs",
    "chars": 2255,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/InsertionTests.fs",
    "chars": 551,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/KeyParsing.fs",
    "chars": 610,
    "preview": "namespace XSVim.Tests\nopen XSVim\nopen NUnit.Framework\n\n[<TestFixture>]\nmodule ``Key parsing tests`` =\n    let test keys"
  },
  {
    "path": "XSVim.Tests/KeyboardMap.fs",
    "chars": 2436,
    "preview": "namespace XSVim.Tests\n\nopen XSVim\nmodule mapFromQwerty =\n    let qwertyToColemak = function\n        | 'e' -> 'f'\n      "
  },
  {
    "path": "XSVim.Tests/MacrosTests.fs",
    "chars": 953,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/MarkerTests.fs",
    "chars": 1321,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/MiscTests.fs",
    "chars": 7308,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/Movement.fs",
    "chars": 5473,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/Properties/AssemblyInfo.fs",
    "chars": 560,
    "preview": "namespace XSVim.Tests\nopen System.Reflection\nopen System.Runtime.CompilerServices\n\n[<assembly: AssemblyTitle(\"XSVim.Tes"
  },
  {
    "path": "XSVim.Tests/TestHelpers.fs",
    "chars": 7892,
    "preview": "namespace XSVim.Tests\n\nopen System\nopen System.Text.RegularExpressions\nopen System.Threading.Tasks\nopen MonoDevelop.Ide"
  },
  {
    "path": "XSVim.Tests/TextObjectSelectionTests.fs",
    "chars": 9150,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/VisualTests.fs",
    "chars": 3091,
    "preview": "namespace XSVim.Tests\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks\n"
  },
  {
    "path": "XSVim.Tests/XSVim.Tests.fsproj",
    "chars": 4056,
    "preview": "<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Prop"
  },
  {
    "path": "XSVim.Tests/YankAndPut.fs",
    "chars": 3060,
    "preview": "namespace XSVim.Tests\n\nopen NUnit.Framework\nopen XSVim\nopen System.Runtime.CompilerServices\nopen System.Threading.Tasks"
  },
  {
    "path": "XSVim.Tests/packages.config",
    "chars": 134,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"FSharp.Core\" version=\"4.6.2\" targetFramework=\"net472\" "
  },
  {
    "path": "XSVim.sln",
    "chars": 1582,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 2012\nProject(\"{f2a71f9b-5d33-465a-a702-920d"
  },
  {
    "path": "addin-project.xml",
    "chars": 230,
    "preview": "<AddinProject appVersion=\"7.4\">\n    <Project>\n        <AddinFile>XSVim/bin/Debug/XSVim.dll</AddinFile>\n        <BuildFil"
  },
  {
    "path": "build.sh",
    "chars": 79,
    "preview": "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild XSVim.sln\n"
  },
  {
    "path": "copy-assemblies.sh",
    "chars": 756,
    "preview": "#!/usr/bin/env bash\n\ncp /Applications/Visual\\ Studio.app/Contents/Resources/lib/monodevelop/bin/Mono.Addins.dll lib/\ncp "
  },
  {
    "path": "run-from-source.sh",
    "chars": 179,
    "preview": "#! /bin/bash\n\nMONODEVELOP_CONSOLE_LOG_LEVEL=All MONODEVELOP_DEV_ADDINS=$(pwd)/XSVim/bin/Debug /Applications/Visual\\ Stud"
  },
  {
    "path": "test.sh",
    "chars": 148,
    "preview": "mono \"/Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/vstool.exe\" run-md-tests XSVim.Tests/bin/De"
  }
]

About this extraction

This page contains the full source code of the nosami/XSVim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 47 files (218.4 KB), approximately 57.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!