amend 'dw' motion in line with previous commit

This commit is contained in:
2025-12-27 07:32:22 +00:00
parent 9846750c51
commit 742b571b4e
6 changed files with 93 additions and 28 deletions

View File

@@ -16,8 +16,11 @@ struct
val newlineToNewline: Word8.word = 0w9 val newlineToNewline: Word8.word = 0w9
val chrToNewline: Word8.word = 0w10 val chrToNewline: Word8.word = 0w10
val alphaToPunct: Word8.word = 0w11 val newlineToAlpha: Word8.word = 0w11
val punctToAlpha: Word8.word = 0w12 val newlineToPunct: Word8.word = 0w12
val alphaToPunct: Word8.word = 0w13
val punctToAlpha: Word8.word = 0w14
fun makeStart i = fun makeStart i =
let let
@@ -63,10 +66,10 @@ struct
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 newlineToAlpha
else if chr = #"\n" then newlineToNewline else if chr = #"\n" then newlineToNewline
else if Char.isSpace chr then startSpace else if Char.isSpace chr then startSpace
else startPunct else newlineToPunct
end end
val startTable = Vector.tabulate (255, makeStart) val startTable = Vector.tabulate (255, makeStart)
@@ -99,6 +102,9 @@ struct
, newlineTable , newlineTable
, newlineTable , newlineTable
, newlineTable , newlineTable
, startAlphaTable
, startPunctTable
] ]
structure StartOfNextWord = structure StartOfNextWord =
@@ -117,6 +123,8 @@ struct
orelse currentState = spaceToAlpha orelse currentState = spaceToAlpha
orelse currentState = spaceToPunct orelse currentState = spaceToPunct
orelse currentState = newlineToNewline orelse currentState = newlineToNewline
orelse currentState = newlineToAlpha
orelse currentState = newlineToPunct
fun finish x = x fun finish x = x
end) end)

View File

@@ -33,6 +33,9 @@ struct
val low = val low =
if Cursor.isOnNewlineAfterChr (buffer, low) then low - 1 else low if Cursor.isOnNewlineAfterChr (buffer, low) then low - 1 else low
val buffer =
if #textLength buffer = 0 then LineGap.fromString "\n" else buffer
in in
finishAfterDeletingBuffer (app, low, buffer, time, initialMsg) finishAfterDeletingBuffer (app, low, buffer, time, initialMsg)
end end
@@ -324,6 +327,52 @@ struct
end end
end end
fun deleteWord (app as {buffer, ...}: app_type, count, time) =
if #textLength buffer = 1 then
NormalFinish.clearMode app
else
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val high = Cursor.nextWord (buffer, cursorIdx, count)
val buffer = LineGap.goToIdx (high, buffer)
in
(* The Cursor.nextWord skips newlines in some cases,
* which makes sense for the 'w' move motion.
* However, we sometimes don't want to skip newlines when deleting.
* For example, when 'dw' is used on the last word in a line,
* the 'w' motion would go to the first character of the next line
* but the 'dw' motion should delete until the newline. *)
if high >= #textLength buffer then
let
val finish = #textLength buffer - 1
val buffer = LineGap.goToIdx (finish, buffer)
val length =
if Cursor.isCursorAtStartOfLine (buffer, finish) then
finish - cursorIdx
else
high - cursorIdx
in
deleteAndFinish (app, cursorIdx, length, buffer, time)
end
else if Cursor.isCursorAtStartOfLine (buffer, high) then
deleteAndFinish (app, cursorIdx, high - cursorIdx, buffer, time)
else
let
val beforeHigh = high - 1
val buffer = LineGap.goToIdx (beforeHigh, buffer)
val high =
if
Cursor.isCursorAtStartOfLine (buffer, beforeHigh)
andalso beforeHigh <> cursorIdx
then beforeHigh
else high
in
deleteAndFinish (app, cursorIdx, high - cursorIdx, buffer, time)
end
end
fun deleteByDfa (app as {buffer, ...}: app_type, count, fMove, time) = fun deleteByDfa (app as {buffer, ...}: app_type, count, fMove, time) =
if #textLength buffer = 1 then if #textLength buffer = 1 then
NormalFinish.clearMode app NormalFinish.clearMode app

View File

@@ -257,7 +257,7 @@ struct
* other cursor motions *) * other cursor motions *)
| #"j" => NormalDelete.deleteLineDown (app, count, time) | #"j" => NormalDelete.deleteLineDown (app, count, time)
| #"k" => NormalDelete.deleteLineBack (app, count, time) | #"k" => NormalDelete.deleteLineBack (app, count, time)
| #"w" => NormalDelete.deleteByDfa (app, count, Cursor.nextWord, time) | #"w" => NormalDelete.deleteWord (app, count, time)
| #"W" => NormalDelete.deleteByDfa (app, count, Cursor.nextWORD, time) | #"W" => NormalDelete.deleteByDfa (app, count, Cursor.nextWORD, time)
| #"b" => NormalDelete.deleteByDfa (app, count, Cursor.prevWord, time) | #"b" => NormalDelete.deleteByDfa (app, count, Cursor.prevWord, time)
| #"B" => NormalDelete.deleteByDfa (app, count, Cursor.prevWORD, time) | #"B" => NormalDelete.deleteByDfa (app, count, Cursor.prevWORD, time)
@@ -346,7 +346,7 @@ struct
* other cursor motions *) * other cursor motions *)
| #"j" => NormalYankDelete.deleteLineDown (app, count, time) | #"j" => NormalYankDelete.deleteLineDown (app, count, time)
| #"k" => NormalYankDelete.deleteLineBack (app, count, time) | #"k" => NormalYankDelete.deleteLineBack (app, count, time)
| #"w" => NormalYankDelete.deleteByDfa (app, count, Cursor.nextWord, time) | #"w" => NormalYankDelete.deleteWord (app, count, time)
| #"W" => NormalYankDelete.deleteByDfa (app, count, Cursor.nextWORD, time) | #"W" => NormalYankDelete.deleteByDfa (app, count, Cursor.nextWORD, time)
| #"b" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWord, time) | #"b" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWord, time)
| #"B" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWORD, time) | #"B" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWORD, time)

View File

@@ -1,4 +1,3 @@
hello hello
world world
again

View File

@@ -944,30 +944,27 @@ struct
in in
Expect.isTrue (stringsAreExpected andalso cursorsAreExpected) Expect.isTrue (stringsAreExpected andalso cursorsAreExpected)
end) end)
, test , test "does not delete newline following word" (fn _ =>
"deletes newline when there is a newline after current word \ let
\and there is another word following that newline" (* arrange *)
(fn _ => val originalString = "hello\nworld\nagain\n"
let val app = TestUtils.init originalString
(* arrange *) val app = AppWith.idx (app, 0)
val originalString = "hello\nworld\nagain\n"
val app = TestUtils.init originalString
val app = AppWith.idx (app, 1)
(* act *) (* act *)
val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw") val {buffer, cursorIdx, ...} = TestUtils.updateMany (app, "dw")
(* assert *) (* assert *)
val expectedString = "hworld\nagain\n" val expectedString = "\nworld\nagain\n"
val expectedCursor = 1 val expectedCursor = 0
val actualString = LineGap.toString buffer val actualString = LineGap.toString buffer
val stringIsExpected = expectedString = actualString val stringIsExpected = expectedString = actualString
val cursorIsExpected = expectedCursor = cursorIdx val cursorIsExpected = expectedCursor = cursorIdx
in in
Expect.isTrue (stringIsExpected andalso cursorIsExpected) Expect.isTrue (stringIsExpected andalso cursorIsExpected)
end) end)
, test , test
"deletes until first punctuation char when on an alpha char \ "deletes until first punctuation char when on an alpha char \
\and there is no space between alpha and punctuation" \and there is no space between alpha and punctuation"

View File

@@ -754,6 +754,18 @@ struct
(* assert *) (* assert *)
Expect.isTrue (#cursorIdx app = 4) Expect.isTrue (#cursorIdx app = 4)
end) end)
, test "moves cursor to character after newline" (fn _ =>
let
(* arrange *)
val app = TestUtils.init "hello\nworld\nagain\n"
val app = AppWith.idx (app, 0)
(* act *)
val app = TestUtils.update (app, CHAR_EVENT #"w")
in
(* assert *)
Expect.isTrue (#cursorIdx app = 6)
end)
] ]
val WMove = describe "move motion 'W'" val WMove = describe "move motion 'W'"