Skip to content

Commit

Permalink
Handle backspace and delete keys when repeated with dot
Browse files Browse the repository at this point in the history
Fixes #214
  • Loading branch information
nosami committed May 1, 2018
1 parent ff69ad0 commit 6e258d2
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 64 deletions.
2 changes: 1 addition & 1 deletion XSVim.Tests/KeyParsing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ open NUnit.Framework
[<TestFixture>]
module ``Key parsing tests`` =
let test keys =
let keys = [for c in keys -> c.ToString()]
let keys = [for c in keys -> Key c]
let state = { VimState.Default with keys=keys }
let config = { insertModeEscapeKey = None }
let action, _state = Vim.parseKeys state config
Expand Down
8 changes: 8 additions & 0 deletions XSVim.Tests/MiscTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ module ``Miscellaneous tests`` =
let ``Repeat typed chars``() =
assertText " $" "iabc <esc>." "abcabc $ "

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

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

[<Test>]
let ``<C-[> escapes``() =
assertText " abc$" "i<C-[>" " ab$c"
Expand Down
21 changes: 12 additions & 9 deletions XSVim.Tests/TestHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,22 @@ module FixtureSetup =

[<AutoOpen>]
module TestHelpers =
type KeyPair = KeyDescriptor * VimKey

let (|CtrlKey|_|) (s:string) =
match s with
| s when s.Length = 3 && s.StartsWith "C-" -> Some s.[2]
| _ -> None

let groupToKeys = function
| "esc" -> [| KeyDescriptor.FromGtk(Gdk.Key.Escape, '\000', Gdk.ModifierType.None) |]
| "ret" -> [| KeyDescriptor.FromGtk(Gdk.Key.Return, '\000', Gdk.ModifierType.None) |]
| "bs" -> [| KeyDescriptor.FromGtk(Gdk.Key.BackSpace, '\000', Gdk.ModifierType.None) |]
| CtrlKey ch -> [| KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), ch, Gdk.ModifierType.ControlMask) |]
| "esc" -> [| KeyDescriptor.FromGtk(Gdk.Key.Escape, '\000', Gdk.ModifierType.None), VimKey.Esc |]
| "ret" -> [| KeyDescriptor.FromGtk(Gdk.Key.Return, '\000', Gdk.ModifierType.None), VimKey.Ret |]
| "bs" -> [| KeyDescriptor.FromGtk(Gdk.Key.BackSpace, '\000', Gdk.ModifierType.None), VimKey.Backspace |]
| "del" -> [| KeyDescriptor.FromGtk(Gdk.Key.Delete, '\000', Gdk.ModifierType.None), VimKey.Delete |]
| CtrlKey ch -> [| KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), ch, Gdk.ModifierType.ControlMask), VimKey.Control ch |]
| keys ->
keys.ToCharArray()
|> Array.map (fun c -> KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), c, Gdk.ModifierType.None))
|> Array.map (fun c -> KeyDescriptor.FromGtk(Gdk.Key.a (* important? *), c, Gdk.ModifierType.None), VimKey.Key c)

let parseKeys (keys:string) =
let keys = Regex.Replace(keys, "<(.*?)>", "§$1§")
Expand All @@ -122,12 +125,12 @@ module TestHelpers =
let keyDescriptors = parseKeys keys
let newState =
keyDescriptors
|> Array.fold(fun state c ->
let handledState, handledKeyPress = Vim.handleKeyPress state c editor config
|> Array.fold(fun state (descriptor, vimKey) ->
let handledState, handledKeyPress = Vim.handleKeyPress state descriptor editor config
printfn "%A" handledState
printfn "%s" editor.Text
if state.mode = InsertMode && c.ModifierKeys <> ModifierKeys.Control && c.SpecialKey <> SpecialKey.Escape then
editor.InsertAtCaret (c.KeyChar.ToString())
if state.mode = InsertMode && descriptor.ModifierKeys <> ModifierKeys.Control && descriptor.SpecialKey <> SpecialKey.Escape then
Vim.processVimKey editor vimKey
handledState) VimState.Default

let cursor = if newState.mode = InsertMode then "|" else "$"
Expand Down
2 changes: 1 addition & 1 deletion XSVim/Properties/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ open System.Runtime.CompilerServices
[<AutoOpen>]
module AddinVersion =
[<Literal>]
let version = "0.54.4"
let version = "0.54.5"

[<assembly: AssemblyTitle("XSVim")>]
// The assembly version has the format {Major}.{Minor}.{Build}.{Revision}
Expand Down
34 changes: 32 additions & 2 deletions XSVim/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ type Jump =
| 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 TextObject =
| Jump of Jump
| Character
Expand Down Expand Up @@ -132,7 +162,7 @@ type CommandType =
| DoNothing
| Star of BeforeOrAfter
| ToggleCase
| InsertChar of string
| InsertChar of VimKey
| IncrementNumber
| DecrementNumber
| SetMark of string
Expand Down Expand Up @@ -164,7 +194,7 @@ and Macro = Macro of char
and VimSelection = { start: int; finish: int; mode: VimMode }

and VimState = {
keys: string list
keys: VimKey list
mode: VimMode
visualStartOffset: int
lastSelection: VimSelection option
Expand Down
120 changes: 69 additions & 51 deletions XSVim/XSVim.fs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ module VimHelpers =
| ToCharExclusiveBackwards c ->
let startOffset =
match vimState.keys with
| ";" :: _ when c = editor.[editor.CaretOffset-1].ToString() ->
| Key ';' :: _ when c = editor.[editor.CaretOffset-1].ToString() ->
editor.CaretOffset-1
| _ -> editor.CaretOffset
match findStringCharBackwardsOnLine editor line startOffset c with
Expand All @@ -442,7 +442,7 @@ module VimHelpers =
| ToCharExclusive c ->
let startOffset =
match vimState.keys with
| ";" :: _ when c = editor.[editor.CaretOffset+1].ToString() ->
| Key ';' :: _ when c = editor.[editor.CaretOffset+1].ToString() ->
editor.CaretOffset+1
| _ -> editor.CaretOffset
match findCharForwardsOnLine editor line startOffset c with
Expand Down Expand Up @@ -608,7 +608,7 @@ module VimHelpers =
| Jump (ToSearch search) ->
let startOffset =
match vimState.keys with
| ["n"] | ["N"] -> editor.CaretOffset + 1
| [Key 'n'] | [Key 'N'] -> editor.CaretOffset + 1
| _ -> editor.CaretOffset
let offset = findNextSearchOffset editor search startOffset |> Option.defaultValue editor.CaretOffset
editor.CaretOffset, offset
Expand Down Expand Up @@ -755,6 +755,21 @@ module Vim =
vimState.undoGroup |> Option.iter(fun d -> d.Dispose())
{ 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 vimState editor command =
let delete state start finish =
let finish =
Expand Down Expand Up @@ -1153,7 +1168,7 @@ module Vim =
| InsertChar c ->
match vimState.mode with
| InsertMode ->
editor.InsertAtCaret c
processVimKey editor c
vimState
| _ -> vimState
| IncrementNumber -> modifyNumber (fun i -> i + 1)
Expand Down Expand Up @@ -1381,11 +1396,10 @@ module Vim =
match config.insertModeEscapeKey with
| Some combo ->
combo.insertModeEscapeKey1, combo.insertModeEscapeKey2, combo.insertModeEscapeTimeout
| None ->
"", "", 0
| None -> "", "", 0

let parseKeys (state:VimState) (config: Config) =
let keyList = state.keys
let keyList = state.keys |> List.map string
let numericArgument, keyList =
match keyList, state.mode with
| "r" :: _, _
Expand Down Expand Up @@ -1437,7 +1451,7 @@ module Vim =
| InsertMode, [ c; _ ] when c = insertModeEscapeFirstChar ->
[ run CancelFunc Nothing
run (ChangeState { state with keys = [] }) Nothing
typeChar insertModeEscapeFirstChar ]
typeChar (Key (char insertModeEscapeFirstChar)) ]
| _, [ Escape ] -> [ switchMode NormalMode; run Move Left ]
| NotInsertMode, [ "G" ] ->
match numericArgument with
Expand Down Expand Up @@ -1650,34 +1664,38 @@ module Vim =
| _ -> resetKeys
action, newState

let handleKeyPress state (keyPress:KeyDescriptor) editor config =
let insertModeEscapeFirstChar, _insertModeEscapeSecondChar, _insertModeTimeout =
getInsertModeEscapeCombo config
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 newKeys, insertChar =
match state.mode, keyPress.KeyChar with
| _, c when keyPress.ModifierKeys = ModifierKeys.Control ->
state.keys @ [sprintf "<C-%c>" c], None
| _, c when keyPress.ModifierKeys = ModifierKeys.Command ->
state.keys @ [sprintf "<D-%c>" c], None
| _, 'z' when keyPress.ModifierKeys = ModifierKeys.Command ->
state.keys @ ["u"], None
| NotInsertMode, c when keyPress.KeyChar <> '\000' ->
state.keys @ [c |> string], None
| InsertMode, c when (c |> string) = insertModeEscapeFirstChar ->
state.keys @ [insertModeEscapeFirstChar], None
| InsertMode, c when state.keys |> List.tryHead = Some insertModeEscapeFirstChar ->
state.keys @ [c |> string], None
| _ ->
match keyPress.SpecialKey with
| SpecialKey.Escape -> state.keys @ ["<esc>"], None
| SpecialKey.Return -> state.keys @ ["<ret>"], None
| SpecialKey.Left -> state.keys @ ["<left>"], None
| SpecialKey.Down -> state.keys @ ["<down>"], None
| SpecialKey.Up -> state.keys @ ["<up>"], None
| SpecialKey.Right -> state.keys @ ["<right>"], None
| _ -> state.keys, Some keyPress.KeyChar
let newState = { state with keys = newKeys }
let handleKeyPress state (keyPress:KeyDescriptor) editor config =
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
Expand Down Expand Up @@ -1706,7 +1724,7 @@ module Vim =
|> Option.iter(fun (Macro c) -> macros.[c] <- macros.[c] @ action)
performActions action newState false

match state.mode, newState.keys with
match state.mode, newState.keys |> List.map string with
| ExMode _, [ Escape ] -> processKey()
| ExMode _, _ ->
let state, actions = exMode.processKey state keyPress
Expand All @@ -1716,21 +1734,21 @@ module Vim =
let firstAction = action |> List.head

let newState =
match insertChar, firstAction.commandType, keyPress.KeyChar with
| Some c, _, _ ->
match state.mode, vimKey, firstAction.commandType with
| InsertMode, _, _ ->
newState.macro |> Option.iter(fun (Macro m) ->
macros.[m] <- macros.[m] @ [ typeChar (c |> string) ])
{ newState with lastAction = newState.lastAction @ [ typeChar (c |> string) ]}
| None, Delete, _
| None, Change, _
| None, Indent, _
| None, UnIndent, _
| None, Put _, _
| None, ReplaceChar _, _
| None, _, 'a'
| None, _, 'i'
| None, _, 'o'
| None, _, 'O'
| None, _, 'A' -> { newState with lastAction = action }
macros.[m] <- macros.[m] @ [ typeChar vimKey ])
{ newState with lastAction = newState.lastAction @ [ typeChar vimKey ]}
| NotInsertMode, _, Delete
| NotInsertMode, _, Change
| NotInsertMode, _, Indent
| NotInsertMode, _, UnIndent
| NotInsertMode, _, Put _
| NotInsertMode, _, ReplaceChar _
| NotInsertMode, Key 'a', _
| NotInsertMode, Key 'i', _
| NotInsertMode, Key 'o', _
| NotInsertMode, Key 'O', _
| NotInsertMode, Key 'A', _ -> { newState with lastAction = action }
| _ -> newState
newState, handled

0 comments on commit 6e258d2

Please sign in to comment.