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)

This commit is contained in:
2025-12-27 06:05:36 +00:00
parent 9e809e146a
commit 9846750c51
3 changed files with 81 additions and 28 deletions

View File

@@ -12,14 +12,19 @@ struct
val spaceToAlpha: Word8.word = 0w6 val spaceToAlpha: Word8.word = 0w6
val spaceToPunct: Word8.word = 0w7 val spaceToPunct: Word8.word = 0w7
val alphaToPunct: Word8.word = 0w8 val startNewline: Word8.word = 0w8
val punctToAlpha: Word8.word = 0w9 val newlineToNewline: Word8.word = 0w9
val chrToNewline: Word8.word = 0w10
val alphaToPunct: Word8.word = 0w11
val punctToAlpha: Word8.word = 0w12
fun makeStart i = fun makeStart i =
let let
val chr = Char.chr i val chr = Char.chr i
in in
if Char.isAlphaNum chr orelse chr = #"_" then startAlpha if Char.isAlphaNum chr orelse chr = #"_" then startAlpha
else if chr = #"\n" then startNewline
else if Char.isSpace chr then startSpace else if Char.isSpace chr then startSpace
else startPunct else startPunct
end end
@@ -29,6 +34,7 @@ struct
val chr = Char.chr i val chr = Char.chr i
in in
if Char.isAlphaNum chr orelse chr = #"_" then startAlpha if Char.isAlphaNum chr orelse chr = #"_" then startAlpha
else if chr = #"\n" then chrToNewline
else if Char.isSpace chr then alphaToSpace else if Char.isSpace chr then alphaToSpace
else alphaToPunct else alphaToPunct
end end
@@ -38,6 +44,7 @@ struct
val chr = Char.chr i val chr = Char.chr i
in in
if Char.isAlphaNum chr orelse chr = #"_" then spaceToAlpha if Char.isAlphaNum chr orelse chr = #"_" then spaceToAlpha
else if chr = #"\n" then chrToNewline
else if Char.isSpace chr then startSpace else if Char.isSpace chr then startSpace
else spaceToPunct else spaceToPunct
end end
@@ -47,10 +54,21 @@ struct
val chr = Char.chr i val chr = Char.chr i
in in
if Char.isAlphaNum chr orelse chr = #"_" then punctToAlpha if Char.isAlphaNum chr orelse chr = #"_" then punctToAlpha
else if chr = #"\n" then chrToNewline
else if Char.isSpace chr then punctToSpace else if Char.isSpace chr then punctToSpace
else startPunct else startPunct
end 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 startTable = Vector.tabulate (255, makeStart)
val startAlphaTable = Vector.tabulate (255, makeStartAlpha) val startAlphaTable = Vector.tabulate (255, makeStartAlpha)
@@ -63,6 +81,8 @@ struct
val spaceToAlphaTable = startAlphaTable val spaceToAlphaTable = startAlphaTable
val spaceToPunctTable = startPunctTable val spaceToPunctTable = startPunctTable
val newlineTable = Vector.tabulate (255, makeStartNewline)
val tables = val tables =
#[ startTable #[ startTable
@@ -75,6 +95,10 @@ struct
, spaceToAlphaTable , spaceToAlphaTable
, spaceToPunctTable , spaceToPunctTable
, newlineTable
, newlineTable
, newlineTable
] ]
structure StartOfNextWord = structure StartOfNextWord =
@@ -92,6 +116,7 @@ struct
currentState = alphaToPunct orelse currentState = punctToAlpha currentState = alphaToPunct orelse currentState = punctToAlpha
orelse currentState = spaceToAlpha orelse currentState = spaceToAlpha
orelse currentState = spaceToPunct orelse currentState = spaceToPunct
orelse currentState = newlineToNewline
fun finish x = x fun finish x = x
end) end)
@@ -130,6 +155,8 @@ struct
fun isFinal currentState = fun isFinal currentState =
currentState = alphaToSpace orelse currentState = punctToSpace currentState = alphaToSpace orelse currentState = punctToSpace
orelse currentState = alphaToPunct orelse currentState = punctToAlpha orelse currentState = alphaToPunct orelse currentState = punctToAlpha
orelse currentState = chrToNewline
orelse currentState = newlineToNewline
fun finish idx = idx + 1 fun finish idx = idx + 1
end) end)
@@ -151,6 +178,7 @@ struct
fun isFinalForEndOfCurrentWord currentState = fun isFinalForEndOfCurrentWord currentState =
currentState = alphaToSpace orelse currentState = punctToSpace currentState = alphaToSpace orelse currentState = punctToSpace
orelse currentState = alphaToPunct orelse currentState = punctToAlpha orelse currentState = alphaToPunct orelse currentState = punctToAlpha
orelse currentState = chrToNewline
structure EndOfCurrentWordFolder = structure EndOfCurrentWordFolder =
MakeCharFolderNext MakeCharFolderNext

View File

@@ -1,4 +1,4 @@
hello abc_123 world hello
fun hello () =
print (##)lo" world

View File

@@ -624,26 +624,21 @@ struct
in in
Expect.isTrue (chr = #"w") Expect.isTrue (chr = #"w")
end) end)
, test "moves cursor past newline when next word is after newline" (fn _ => , test
(* This behaviour makes behaviour different from vi, "moves cursor to first char-after-newline \
* where "w" when a newline is in between causes cursor \when cursor is on last word of line \
* to go to newline and not next word. \and there is another line after this one"
* I don't personally like this behaviour from vi (fn _ =>
* since one can just press "j" to go to the newline instead let
* and it is more intuitive for the cursor to go the next word (* arrange *)
* as usual with "w". *) val app = TestUtils.init "hello \n\n\n world\n"
let
(* arrange *)
val app = TestUtils.init "hello \n\n\n world\n"
(* act *) (* act *)
val app = TestUtils.update (app, CHAR_EVENT #"w") val {cursorIdx, ...} = TestUtils.update (app, CHAR_EVENT #"w")
in
(* assert *) (* assert *)
val cursorChr = getChr app Expect.isTrue (cursorIdx = 7)
in end)
Expect.isTrue (cursorChr = #"w")
end)
, test "does not break on undescore when cursor is on alphanumeric char" , test "does not break on undescore when cursor is on alphanumeric char"
(fn _ => (fn _ =>
let let
@@ -949,10 +944,10 @@ struct
in in
Expect.isTrue (cursorChr = #")") Expect.isTrue (cursorChr = #")")
end) end)
, test "skips 'space' chars: '\\n', '\\t', ' '" (fn _ => , test "skips past tab" (fn _ =>
let let
(* arrange *) (* 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) val app = AppWith.idx (app, 4)
(* act *) (* act *)
@@ -992,6 +987,23 @@ struct
in in
Expect.isTrue chrIsEnd Expect.isTrue chrIsEnd
end) 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'" val EMove = describe "move motion 'E'"
@@ -1050,10 +1062,10 @@ struct
in in
Expect.isTrue (cursorChr = #"o") Expect.isTrue (cursorChr = #"o")
end) end)
, test "skips 'space' chars: '\\n', '\\t', ' '" (fn _ => , test "skips past tab: '\\t'" (fn _ =>
let let
(* arrange *) (* 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) val app = AppWith.idx (app, 4)
(* act *) (* act *)
@@ -1175,6 +1187,19 @@ struct
(* assert *) (* assert *)
Expect.isTrue (getChr app = #"!") Expect.isTrue (getChr app = #"!")
end) 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'" val BMove = describe "move motion 'B'"