From 9846750c51dd676cb10d277e749febb18ca7c6fb Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Sat, 27 Dec 2025 06:05:36 +0000 Subject: [PATCH] change implementation of 'word' so that we stop at newlines when we press 'w' motion, like in Vim and add tests to verify this behaviour (todo: fix regressions in deletion tests) --- fcore/cursor-dfa/vi-word-dfa.sml | 32 +++++++++++++- temp.txt | 6 +-- test/normal-move.sml | 71 +++++++++++++++++++++----------- 3 files changed, 81 insertions(+), 28 deletions(-) diff --git a/fcore/cursor-dfa/vi-word-dfa.sml b/fcore/cursor-dfa/vi-word-dfa.sml index d2c9eb7..abde534 100644 --- a/fcore/cursor-dfa/vi-word-dfa.sml +++ b/fcore/cursor-dfa/vi-word-dfa.sml @@ -12,14 +12,19 @@ struct val spaceToAlpha: Word8.word = 0w6 val spaceToPunct: Word8.word = 0w7 - val alphaToPunct: Word8.word = 0w8 - val punctToAlpha: Word8.word = 0w9 + val startNewline: Word8.word = 0w8 + val newlineToNewline: Word8.word = 0w9 + val chrToNewline: Word8.word = 0w10 + + val alphaToPunct: Word8.word = 0w11 + val punctToAlpha: Word8.word = 0w12 fun makeStart i = let val chr = Char.chr i in if Char.isAlphaNum chr orelse chr = #"_" then startAlpha + else if chr = #"\n" then startNewline else if Char.isSpace chr then startSpace else startPunct end @@ -29,6 +34,7 @@ struct val chr = Char.chr i in if Char.isAlphaNum chr orelse chr = #"_" then startAlpha + else if chr = #"\n" then chrToNewline else if Char.isSpace chr then alphaToSpace else alphaToPunct end @@ -38,6 +44,7 @@ struct val chr = Char.chr i in if Char.isAlphaNum chr orelse chr = #"_" then spaceToAlpha + else if chr = #"\n" then chrToNewline else if Char.isSpace chr then startSpace else spaceToPunct end @@ -47,10 +54,21 @@ struct val chr = Char.chr i in if Char.isAlphaNum chr orelse chr = #"_" then punctToAlpha + else if chr = #"\n" then chrToNewline else if Char.isSpace chr then punctToSpace else startPunct end + fun makeStartNewline i = + let + val chr = Char.chr i + in + if Char.isAlphaNum chr orelse chr = #"_" then startAlpha + else if chr = #"\n" then newlineToNewline + else if Char.isSpace chr then startSpace + else startPunct + end + val startTable = Vector.tabulate (255, makeStart) val startAlphaTable = Vector.tabulate (255, makeStartAlpha) @@ -63,6 +81,8 @@ struct val spaceToAlphaTable = startAlphaTable val spaceToPunctTable = startPunctTable + val newlineTable = Vector.tabulate (255, makeStartNewline) + val tables = #[ startTable @@ -75,6 +95,10 @@ struct , spaceToAlphaTable , spaceToPunctTable + + , newlineTable + , newlineTable + , newlineTable ] structure StartOfNextWord = @@ -92,6 +116,7 @@ struct currentState = alphaToPunct orelse currentState = punctToAlpha orelse currentState = spaceToAlpha orelse currentState = spaceToPunct + orelse currentState = newlineToNewline fun finish x = x end) @@ -130,6 +155,8 @@ struct fun isFinal currentState = currentState = alphaToSpace orelse currentState = punctToSpace orelse currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = chrToNewline + orelse currentState = newlineToNewline fun finish idx = idx + 1 end) @@ -151,6 +178,7 @@ struct fun isFinalForEndOfCurrentWord currentState = currentState = alphaToSpace orelse currentState = punctToSpace orelse currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = chrToNewline structure EndOfCurrentWordFolder = MakeCharFolderNext diff --git a/temp.txt b/temp.txt index 0424254..917cd57 100644 --- a/temp.txt +++ b/temp.txt @@ -1,4 +1,4 @@ -hello abc_123 world +hello -fun hello () = - print (##)lo" + +world diff --git a/test/normal-move.sml b/test/normal-move.sml index eacbea1..03a35eb 100644 --- a/test/normal-move.sml +++ b/test/normal-move.sml @@ -624,26 +624,21 @@ struct in Expect.isTrue (chr = #"w") end) - , test "moves cursor past newline when next word is after newline" (fn _ => - (* This behaviour makes behaviour different from vi, - * where "w" when a newline is in between causes cursor - * to go to newline and not next word. - * I don't personally like this behaviour from vi - * since one can just press "j" to go to the newline instead - * and it is more intuitive for the cursor to go the next word - * as usual with "w". *) - let - (* arrange *) - val app = TestUtils.init "hello \n\n\n world\n" + , test + "moves cursor to first char-after-newline \ + \when cursor is on last word of line \ + \and there is another line after this one" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello \n\n\n world\n" - (* act *) - val app = TestUtils.update (app, CHAR_EVENT #"w") - - (* assert *) - val cursorChr = getChr app - in - Expect.isTrue (cursorChr = #"w") - end) + (* act *) + val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"w") + in + (* assert *) + Expect.isTrue (cursorIdx = 7) + end) , test "does not break on undescore when cursor is on alphanumeric char" (fn _ => let @@ -949,10 +944,10 @@ struct in Expect.isTrue (cursorChr = #")") end) - , test "skips 'space' chars: '\\n', '\\t', ' '" (fn _ => + , test "skips past tab" (fn _ => let (* arrange *) - val app = TestUtils.init "0123 \t \n \t 789\n" + val app = TestUtils.init "0123 \t \t \t 789\n" val app = AppWith.idx (app, 4) (* act *) @@ -992,6 +987,23 @@ struct in Expect.isTrue chrIsEnd end) + , test + "moves cursor to end of current word \ + \when current word is last word in line" + (fn _ => + let + (* arrange *) + val app = TestUtils.init "hello\n\n\nworld\n" + val app = AppWith.idx (app, 0) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"e") + + (* assert *) + val isAtEndOfHello = getChr app = #"o" + in + Expect.isTrue isAtEndOfHello + end) ] val EMove = describe "move motion 'E'" @@ -1050,10 +1062,10 @@ struct in Expect.isTrue (cursorChr = #"o") end) - , test "skips 'space' chars: '\\n', '\\t', ' '" (fn _ => + , test "skips past tab: '\\t'" (fn _ => let (* arrange *) - val app = TestUtils.init "0123 \t \n \t 789\n" + val app = TestUtils.init "0123 \t \t \t 789\n" val app = AppWith.idx (app, 4) (* act *) @@ -1175,6 +1187,19 @@ struct (* assert *) Expect.isTrue (getChr app = #"!") end) + , test "stops when char preceding word is newline" (fn _ => + let + (* arrange *) + val originalString = "hello\n\n\nworld\n" + val app = TestUtils.init originalString + val app = AppWith.idx (app, String.size originalString - 2) + + (* act *) + val app = TestUtils.update (app, CHAR_EVENT #"b") + in + (* assert *) + Expect.isTrue (getChr app = #"w") + end) ] val BMove = describe "move motion 'B'"