From 1df0952f08680ce9814176e3fbe6620cf0d9cfd2 Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Sat, 27 Dec 2025 08:42:54 +0000 Subject: [PATCH] implement the difference between the 'w' motion and 'dw' motion using the transition table in vi-word-dfa, rather than convoluted if-branching --- fcore/cursor-dfa/vi-word-dfa.sml | 35 ++++++++++++++++++++++ fcore/cursor.sml | 1 + fcore/normal-mode/make-normal-delete.sml | 38 ++---------------------- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/fcore/cursor-dfa/vi-word-dfa.sml b/fcore/cursor-dfa/vi-word-dfa.sml index 24d44b2..7a3e718 100644 --- a/fcore/cursor-dfa/vi-word-dfa.sml +++ b/fcore/cursor-dfa/vi-word-dfa.sml @@ -132,6 +132,40 @@ struct val fStart = Folder.foldNext end) + (* This is the same as StartOfNextWord, except for the `isFinal` function. + * The difference is that the `isFinal` function here considers + * the state where any character goes to a newline, + * to be a final state. + * This is because, in Vim, the 'w' motion will move past a newline + * when that newline is preceded by a non-newline character. + * However, the 'dw' motion deletes until that newline + * (not including the newline itself). + * It is easier, less fragile, and perhaps clearer, + * to implement the difference using a transition table like this + * than convoluted if-statements. *) + structure StartOfNextWordForDelete = + MakeNextDfaLoop + (struct + val startState = startState + + structure Folder = + MakeCharFolderNext + (struct + val startState = startState + val tables = tables + + fun isFinal currentState = + currentState = alphaToPunct orelse currentState = punctToAlpha + orelse currentState = spaceToAlpha + orelse currentState = spaceToPunct + orelse currentState = chrToNewline + + fun finish x = x + end) + + val fStart = Folder.foldNext + end) + structure EndOfPrevWord = MakePrevDfaLoop (struct @@ -232,6 +266,7 @@ struct (* w *) val startOfNextWord = StartOfNextWord.next + val startOfNextWordForDelete = StartOfNextWordForDelete.next (* ge *) val endOfPrevWord = EndOfPrevWord.prev (* b *) diff --git a/fcore/cursor.sml b/fcore/cursor.sml index 0bae153..c162d3a 100644 --- a/fcore/cursor.sml +++ b/fcore/cursor.sml @@ -60,6 +60,7 @@ struct (* equivalent of vi's 'w' command *) val nextWord = ViWordDfa.startOfNextWord + val nextWordForDelete = ViWordDfa.startOfNextWordForDelete (* equivalent of vi's 'W' command *) val nextWORD = ViCapsWordDfa.startOfNextWORD diff --git a/fcore/normal-mode/make-normal-delete.sml b/fcore/normal-mode/make-normal-delete.sml index 36057ac..8436ab9 100644 --- a/fcore/normal-mode/make-normal-delete.sml +++ b/fcore/normal-mode/make-normal-delete.sml @@ -335,42 +335,10 @@ struct val {buffer, cursorIdx, ...} = app val buffer = LineGap.goToIdx (cursorIdx, buffer) - val high = Cursor.nextWord (buffer, cursorIdx, count) - val buffer = LineGap.goToIdx (high, buffer) + val high = Cursor.nextWordForDelete (buffer, cursorIdx, count) + val length = high - cursorIdx 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 + deleteAndFinish (app, cursorIdx, length, buffer, time) end fun deleteByDfa (app as {buffer, ...}: app_type, count, fMove, time) =