move normal-mode deletion functions to their own file
This commit is contained in:
@@ -6,380 +6,6 @@ struct
|
||||
open DrawMsg
|
||||
open InputMsg
|
||||
|
||||
(* text-delete functions *)
|
||||
(** equivalent of vi's 'x' command **)
|
||||
fun helpRemoveChr (app: app_type, buffer, cursorIdx, count) =
|
||||
if count = 0 then
|
||||
let
|
||||
val searchString = #searchString app
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
in
|
||||
Finish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
(* Explanation of how Vi's 'x' command behaves:
|
||||
* If the cursor is at the end of the file,
|
||||
* then it is decremented by 1.
|
||||
* If the character after the cursor is a line break,
|
||||
* then it is also decremented by 1.
|
||||
* If the character before the cursor is a linee break, the cursor stays
|
||||
* where it is.
|
||||
* If the chracter AT the cursor is a line break and the characater
|
||||
* AFTER the cursor is also a line break, then nothing is deleted.
|
||||
* Otherwise, the same cursor is returned.
|
||||
* All decrement cases do not decrement when the cursor is 0. *)
|
||||
val cursorIsStart = Cursor.isCursorAtStartOfLine (buffer, cursorIdx)
|
||||
val nextIsEnd = Cursor.isNextChrEndOfLine (buffer, cursorIdx)
|
||||
in
|
||||
if nextIsEnd andalso cursorIsStart then
|
||||
(* vi simply doesn't do anything on 'x' command
|
||||
* when cursor is at start of line, and next chr is line break
|
||||
* so skip to end of loop by passing count of 0 *)
|
||||
helpRemoveChr (app, buffer, cursorIdx, 0)
|
||||
else if cursorIsStart then
|
||||
helpRemoveChr (app, buffer, cursorIdx, 0)
|
||||
else if nextIsEnd then
|
||||
let
|
||||
(* delete char at cursor and then decrement cursorIdx by 1
|
||||
* if cursorIdx is not 0 *)
|
||||
val searchString = #searchString app
|
||||
val buffer = LineGap.delete (cursorIdx, 1, buffer)
|
||||
|
||||
val cursorIdx =
|
||||
if
|
||||
Cursor.isPrevChrStartOfLine (buffer, cursorIdx)
|
||||
orelse cursorIdx = 0
|
||||
then cursorIdx
|
||||
else cursorIdx - 1
|
||||
in
|
||||
helpRemoveChr (app, buffer, cursorIdx, count - 1)
|
||||
end
|
||||
else
|
||||
let
|
||||
val searchString = #searchString app
|
||||
val buffer = LineGap.delete (cursorIdx, 1, buffer)
|
||||
in
|
||||
helpRemoveChr (app, buffer, cursorIdx, count - 1)
|
||||
end
|
||||
end
|
||||
|
||||
fun removeChr (app: app_type, count) =
|
||||
helpRemoveChr (app, #buffer app, #cursorIdx app, count)
|
||||
|
||||
fun helpDelete (app: app_type, buffer, cursorIdx, otherIdx, count, fMove) =
|
||||
(* As a small optimisation to reduce allocations,
|
||||
* we accumulate otherIdx by calling fMove with it and the buffer
|
||||
* on each loop.
|
||||
* Then, at the end of the loop, we perform the actual deletion.
|
||||
* This is faster than performing the actual deletion on every loop
|
||||
* because we only delete once, and avoid allocating intermediary buffers.
|
||||
* The behaviour between the two is equivalent. *)
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val high = Cursor.clipIdx (buffer, high)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val searchString = #searchString app
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
(* If we have deleted from the buffer so that cursorIdx
|
||||
* is no longer a valid idx,
|
||||
* clip cursorIdx to the end. *)
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val cursorIdx = Cursor.clipIdx (buffer, low)
|
||||
in
|
||||
Finish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
(* get otherIdx, where cursor will want to go after motion. *)
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val newOtherIdx = fMove (buffer, otherIdx)
|
||||
val newCount = if newOtherIdx = otherIdx then 0 else count - 1
|
||||
in
|
||||
helpDelete (app, buffer, cursorIdx, newOtherIdx, newCount, fMove)
|
||||
end
|
||||
|
||||
fun delete (app: app_type, count, fMove) =
|
||||
helpDelete (app, #buffer app, #cursorIdx app, #cursorIdx app, count, fMove)
|
||||
|
||||
fun deleteByDfa (app: app_type, count, fMove) =
|
||||
let
|
||||
val {buffer, cursorIdx, searchString, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = fMove (buffer, cursorIdx, count)
|
||||
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun deleteToEndOfLine (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
in
|
||||
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
|
||||
(* if we are on \n, we don't want to delete or do anything
|
||||
* so reset the mode *)
|
||||
Finish.clearMode app
|
||||
else
|
||||
let
|
||||
(* viDlr takes us to the last chr in the line
|
||||
* but does not delete that last chr
|
||||
* so we call helpRemoveChr to delete that last chr.
|
||||
* We also rely on helpRemoveChr to handle backwards-movement logic:
|
||||
* If cursorIdx is at \n after deletion, then stop.
|
||||
* Else, move back one chr. *)
|
||||
val lastChr = Cursor.viDlr (buffer, cursorIdx, 1)
|
||||
val length = lastChr - cursorIdx
|
||||
val buffer = LineGap.delete (cursorIdx, length, buffer)
|
||||
in
|
||||
helpRemoveChr (app, buffer, cursorIdx, 1)
|
||||
end
|
||||
end
|
||||
|
||||
fun deleteLine (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, searchString, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val startIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
val finishIdx = Cursor.viDlrForDelete (buffer, cursorIdx, count)
|
||||
|
||||
val length = finishIdx - startIdx
|
||||
val buffer = LineGap.delete (startIdx, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (startIdx, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, startIdx, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun helpDeleteLineBack (app, buffer, low, high, count) =
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.max (low, 0)
|
||||
val length = high - low
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val searchString = #searchString app
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (low + 777, buffer)
|
||||
val searchList = SearchList.buildRange (buffer, searchString, low - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val low = Cursor.viH (buffer, low)
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val low = Cursor.vi0 (buffer, low)
|
||||
val newCount = if low = 0 then 0 else count - 1
|
||||
in
|
||||
helpDeleteLineBack (app, buffer, low, high, newCount)
|
||||
end
|
||||
|
||||
fun deleteLineBack (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val low = Cursor.vi0 (buffer, cursorIdx)
|
||||
val high = Cursor.viDlr (buffer, cursorIdx, 1) + 1
|
||||
in
|
||||
helpDeleteLineBack (app, buffer, low, high, count)
|
||||
end
|
||||
|
||||
fun deleteToFirstNonSpaceChr (app: app_type) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, searchString
|
||||
, ...
|
||||
} = app
|
||||
|
||||
(* move LineGap and buffer to start of line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
(* move cursorIdx to first character on line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = Cursor.firstNonSpaceChr (buffer, otherIdx)
|
||||
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun helpDeleteToChr
|
||||
(app: app_type, buffer, cursorIdx, otherIdx, count, fMove, fInc, chr) =
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val searchString = #searchString app
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
in
|
||||
Finish.buildTextAndClearAfterChr (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val newOtherIdx = fMove (buffer, otherIdx, chr)
|
||||
val newCount = if newOtherIdx = otherIdx then 0 else count - 1
|
||||
val newOtherIdx = fInc (newOtherIdx, 1)
|
||||
in
|
||||
helpDeleteToChr
|
||||
(app, buffer, cursorIdx, newOtherIdx, newCount, fMove, fInc, chr)
|
||||
end
|
||||
|
||||
fun deleteToChr (app: app_type, count, fMove, fInc, chr) =
|
||||
helpDeleteToChr
|
||||
( app
|
||||
, #buffer app
|
||||
, #cursorIdx app
|
||||
, #cursorIdx app
|
||||
, count
|
||||
, fMove
|
||||
, fInc
|
||||
, chr
|
||||
)
|
||||
|
||||
fun deleteToStart (app: app_type) =
|
||||
let
|
||||
val {cursorIdx, buffer, windowWidth, windowHeight, searchString, ...} =
|
||||
app
|
||||
|
||||
val buffer = LineGap.delete (0, cursorIdx, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, #searchString app)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
val cursorIdx = 0
|
||||
val startLine = 0
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val drawMsg = TextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, searchString
|
||||
, initialMsg
|
||||
)
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
AppWith.bufferAndCursorIdx
|
||||
(app, buffer, cursorIdx, mode, startLine, searchList, drawMsg)
|
||||
end
|
||||
|
||||
fun helpDeleteToMatch (app: app_type, low, high) =
|
||||
let
|
||||
val {buffer, searchString, ...} = app
|
||||
val length = high - low
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (low + 777, buffer)
|
||||
val searchList = SearchList.buildRange (buffer, searchString, low - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun deleteToNextMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, ...} = app
|
||||
val newCursorIdx = SearchList.nextMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if newCursorIdx = ~1 orelse newCursorIdx <= cursorIdx then Finish.clearMode app
|
||||
else helpDeleteToMatch (app, cursorIdx, newCursorIdx)
|
||||
end
|
||||
|
||||
fun deleteToPrevMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, ...} = app
|
||||
val newCursorIdx = SearchList.prevMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if newCursorIdx = ~1 orelse newCursorIdx >= cursorIdx then Finish.clearMode app
|
||||
else helpDeleteToMatch (app, newCursorIdx, cursorIdx)
|
||||
end
|
||||
|
||||
(* command-parsing functions *)
|
||||
(** number of characters which are integers *)
|
||||
fun getNumLength (pos, str) =
|
||||
@@ -467,7 +93,7 @@ struct
|
||||
if String.size str = 0 then NormalMove.moveToEnd app
|
||||
else NormalMove.moveToLine (app, count - 1)
|
||||
| #"%" => NormalMove.moveToMatchingPair app
|
||||
| #"x" => removeChr (app, count)
|
||||
| #"x" => NormalDelete.removeChr (app, count)
|
||||
(* multi-char commands which can be appended *)
|
||||
| #"t" => appendChr (app, chr, str)
|
||||
| #"T" => appendChr (app, chr, str)
|
||||
@@ -495,26 +121,26 @@ struct
|
||||
CHAR_EVENT chr =>
|
||||
(case chr of
|
||||
(* terminal commands: require no input after *)
|
||||
#"h" => delete (app, count, Cursor.viH)
|
||||
| #"l" => delete (app, count, Cursor.viL)
|
||||
#"h" => NormalDelete.delete (app, count, Cursor.viH)
|
||||
| #"l" => NormalDelete.delete (app, count, Cursor.viL)
|
||||
(* vi's 'j' and 'k' commands move up or down a column
|
||||
* but 'dj' or 'dk' delete whole lines
|
||||
* so their implementation differs from
|
||||
* other cursor motions *)
|
||||
| #"j" => deleteLine (app, count + 1)
|
||||
| #"k" => deleteLineBack (app, count)
|
||||
| #"w" => deleteByDfa (app, count, Cursor.nextWord)
|
||||
| #"W" => deleteByDfa (app, count, Cursor.nextWORD)
|
||||
| #"b" => deleteByDfa (app, count, Cursor.prevWord)
|
||||
| #"B" => deleteByDfa (app, count, Cursor.prevWORD)
|
||||
| #"e" => deleteByDfa (app, count, Cursor.endOfWordForDelete)
|
||||
| #"E" => deleteByDfa (app, count, Cursor.endOfWORDForDelete)
|
||||
| #"0" => delete (app, 1, Cursor.vi0)
|
||||
| #"$" => deleteToEndOfLine app
|
||||
| #"^" => deleteToFirstNonSpaceChr app
|
||||
| #"d" => deleteLine (app, count)
|
||||
| #"n" => deleteToNextMatch (app, count)
|
||||
| #"N" => deleteToPrevMatch (app, count)
|
||||
| #"j" => NormalDelete.deleteLine (app, count + 1)
|
||||
| #"k" => NormalDelete.deleteLineBack (app, count)
|
||||
| #"w" => NormalDelete.deleteByDfa (app, count, Cursor.nextWord)
|
||||
| #"W" => NormalDelete.deleteByDfa (app, count, Cursor.nextWORD)
|
||||
| #"b" => NormalDelete.deleteByDfa (app, count, Cursor.prevWord)
|
||||
| #"B" => NormalDelete.deleteByDfa (app, count, Cursor.prevWORD)
|
||||
| #"e" => NormalDelete.deleteByDfa (app, count, Cursor.endOfWordForDelete)
|
||||
| #"E" => NormalDelete.deleteByDfa (app, count, Cursor.endOfWORDForDelete)
|
||||
| #"0" => NormalDelete.delete (app, 1, Cursor.vi0)
|
||||
| #"$" => NormalDelete.deleteToEndOfLine app
|
||||
| #"^" => NormalDelete.deleteToFirstNonSpaceChr app
|
||||
| #"d" => NormalDelete.deleteLine (app, count)
|
||||
| #"n" => NormalDelete.deleteToNextMatch (app, count)
|
||||
| #"N" => NormalDelete.deleteToPrevMatch (app, count)
|
||||
(* non-terminal commands which require appending chr *)
|
||||
| #"t" => appendChr (app, chr, str)
|
||||
| #"T" => appendChr (app, chr, str)
|
||||
@@ -533,7 +159,7 @@ struct
|
||||
(* delete till chr, forwards *)
|
||||
(case newCmd of
|
||||
CHAR_EVENT chr =>
|
||||
deleteToChr (app, 1, Cursor.tillNextChr, op+, chr)
|
||||
NormalDelete.deleteToChr (app, 1, Cursor.tillNextChr, op+, chr)
|
||||
| KEY_ESC => Finish.clearMode app
|
||||
| RESIZE_EVENT (width, height) => Finish.resizeText (app, width, height)
|
||||
| WITH_SEARCH_LIST searchList => Finish.withSearchList (app, searchList))
|
||||
@@ -541,14 +167,14 @@ struct
|
||||
(* delete till chr, backwards *)
|
||||
(case newCmd of
|
||||
CHAR_EVENT chr =>
|
||||
deleteToChr (app, 1, Cursor.tillPrevChr, op-, chr)
|
||||
NormalDelete.deleteToChr (app, 1, Cursor.tillPrevChr, op-, chr)
|
||||
| KEY_ESC => Finish.clearMode app
|
||||
| RESIZE_EVENT (width, height) => Finish.resizeText (app, width, height)
|
||||
| WITH_SEARCH_LIST searchList => Finish.withSearchList (app, searchList))
|
||||
| #"f" =>
|
||||
(case newCmd of
|
||||
CHAR_EVENT chr =>
|
||||
deleteToChr (app, count, Cursor.toNextChr, op+, chr)
|
||||
NormalDelete.deleteToChr (app, count, Cursor.toNextChr, op+, chr)
|
||||
| KEY_ESC => Finish.clearMode app
|
||||
| RESIZE_EVENT (width, height) => Finish.resizeText (app, width, height)
|
||||
| WITH_SEARCH_LIST searchList => Finish.withSearchList (app, searchList))
|
||||
@@ -556,7 +182,7 @@ struct
|
||||
(* delete to chr, backwards *)
|
||||
(case newCmd of
|
||||
CHAR_EVENT chr =>
|
||||
deleteToChr (app, count, Cursor.toPrevChr, op-, chr)
|
||||
NormalDelete.deleteToChr (app, count, Cursor.toPrevChr, op-, chr)
|
||||
| KEY_ESC => Finish.clearMode app
|
||||
| RESIZE_EVENT (width, height) => Finish.resizeText (app, width, height)
|
||||
| WITH_SEARCH_LIST searchList => Finish.withSearchList (app, searchList))
|
||||
@@ -565,9 +191,9 @@ struct
|
||||
(case newCmd of
|
||||
CHAR_EVENT chr =>
|
||||
(case chr of
|
||||
#"e" => deleteByDfa (app, count, Cursor.endOfPrevWord)
|
||||
| #"E" => deleteByDfa (app, count, Cursor.endOfPrevWORD)
|
||||
| #"g" => deleteToStart app
|
||||
#"e" => NormalDelete.deleteByDfa (app, count, Cursor.endOfPrevWord)
|
||||
| #"E" => NormalDelete.deleteByDfa (app, count, Cursor.endOfPrevWORD)
|
||||
| #"g" => NormalDelete.deleteToStart app
|
||||
| _ => Finish.clearMode app)
|
||||
| KEY_ESC => Finish.clearMode app
|
||||
| RESIZE_EVENT (width, height) => Finish.resizeText (app, width, height)
|
||||
|
||||
378
fcore/normal-mode/normal-delete.sml
Normal file
378
fcore/normal-mode/normal-delete.sml
Normal file
@@ -0,0 +1,378 @@
|
||||
structure NormalDelete =
|
||||
struct
|
||||
open AppType
|
||||
open MailboxType
|
||||
|
||||
(* equivalent of vi's 'x' command **)
|
||||
fun helpRemoveChr (app: app_type, buffer, cursorIdx, count) =
|
||||
if count = 0 then
|
||||
let
|
||||
val searchString = #searchString app
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
in
|
||||
Finish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
(* Explanation of how Vi's 'x' command behaves:
|
||||
* If the cursor is at the end of the file,
|
||||
* then it is decremented by 1.
|
||||
* If the character after the cursor is a line break,
|
||||
* then it is also decremented by 1.
|
||||
* If the character before the cursor is a linee break, the cursor stays
|
||||
* where it is.
|
||||
* If the chracter AT the cursor is a line break and the characater
|
||||
* AFTER the cursor is also a line break, then nothing is deleted.
|
||||
* Otherwise, the same cursor is returned.
|
||||
* All decrement cases do not decrement when the cursor is 0. *)
|
||||
val cursorIsStart = Cursor.isCursorAtStartOfLine (buffer, cursorIdx)
|
||||
val nextIsEnd = Cursor.isNextChrEndOfLine (buffer, cursorIdx)
|
||||
in
|
||||
if nextIsEnd andalso cursorIsStart then
|
||||
(* vi simply doesn't do anything on 'x' command
|
||||
* when cursor is at start of line, and next chr is line break
|
||||
* so skip to end of loop by passing count of 0 *)
|
||||
helpRemoveChr (app, buffer, cursorIdx, 0)
|
||||
else if cursorIsStart then
|
||||
helpRemoveChr (app, buffer, cursorIdx, 0)
|
||||
else if nextIsEnd then
|
||||
let
|
||||
(* delete char at cursor and then decrement cursorIdx by 1
|
||||
* if cursorIdx is not 0 *)
|
||||
val searchString = #searchString app
|
||||
val buffer = LineGap.delete (cursorIdx, 1, buffer)
|
||||
|
||||
val cursorIdx =
|
||||
if
|
||||
Cursor.isPrevChrStartOfLine (buffer, cursorIdx)
|
||||
orelse cursorIdx = 0
|
||||
then cursorIdx
|
||||
else cursorIdx - 1
|
||||
in
|
||||
helpRemoveChr (app, buffer, cursorIdx, count - 1)
|
||||
end
|
||||
else
|
||||
let
|
||||
val searchString = #searchString app
|
||||
val buffer = LineGap.delete (cursorIdx, 1, buffer)
|
||||
in
|
||||
helpRemoveChr (app, buffer, cursorIdx, count - 1)
|
||||
end
|
||||
end
|
||||
|
||||
fun removeChr (app: app_type, count) =
|
||||
helpRemoveChr (app, #buffer app, #cursorIdx app, count)
|
||||
|
||||
fun helpDelete (app: app_type, buffer, cursorIdx, otherIdx, count, fMove) =
|
||||
(* As a small optimisation to reduce allocations,
|
||||
* we accumulate otherIdx by calling fMove with it and the buffer
|
||||
* on each loop.
|
||||
* Then, at the end of the loop, we perform the actual deletion.
|
||||
* This is faster than performing the actual deletion on every loop
|
||||
* because we only delete once, and avoid allocating intermediary buffers.
|
||||
* The behaviour between the two is equivalent. *)
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val high = Cursor.clipIdx (buffer, high)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val searchString = #searchString app
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
(* If we have deleted from the buffer so that cursorIdx
|
||||
* is no longer a valid idx,
|
||||
* clip cursorIdx to the end. *)
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val cursorIdx = Cursor.clipIdx (buffer, low)
|
||||
in
|
||||
Finish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
(* get otherIdx, where cursor will want to go after motion. *)
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val newOtherIdx = fMove (buffer, otherIdx)
|
||||
val newCount = if newOtherIdx = otherIdx then 0 else count - 1
|
||||
in
|
||||
helpDelete (app, buffer, cursorIdx, newOtherIdx, newCount, fMove)
|
||||
end
|
||||
|
||||
fun delete (app: app_type, count, fMove) =
|
||||
helpDelete (app, #buffer app, #cursorIdx app, #cursorIdx app, count, fMove)
|
||||
|
||||
fun deleteByDfa (app: app_type, count, fMove) =
|
||||
let
|
||||
val {buffer, cursorIdx, searchString, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = fMove (buffer, cursorIdx, count)
|
||||
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun deleteToEndOfLine (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
in
|
||||
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
|
||||
(* if we are on \n, we don't want to delete or do anything
|
||||
* so reset the mode *)
|
||||
Finish.clearMode app
|
||||
else
|
||||
let
|
||||
(* viDlr takes us to the last chr in the line
|
||||
* but does not delete that last chr
|
||||
* so we call helpRemoveChr to delete that last chr.
|
||||
* We also rely on helpRemoveChr to handle backwards-movement logic:
|
||||
* If cursorIdx is at \n after deletion, then stop.
|
||||
* Else, move back one chr. *)
|
||||
val lastChr = Cursor.viDlr (buffer, cursorIdx, 1)
|
||||
val length = lastChr - cursorIdx
|
||||
val buffer = LineGap.delete (cursorIdx, length, buffer)
|
||||
in
|
||||
helpRemoveChr (app, buffer, cursorIdx, 1)
|
||||
end
|
||||
end
|
||||
|
||||
fun deleteLine (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, searchString, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val startIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
val finishIdx = Cursor.viDlrForDelete (buffer, cursorIdx, count)
|
||||
|
||||
val length = finishIdx - startIdx
|
||||
val buffer = LineGap.delete (startIdx, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (startIdx, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, startIdx, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun helpDeleteLineBack (app, buffer, low, high, count) =
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.max (low, 0)
|
||||
val length = high - low
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val searchString = #searchString app
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (low + 777, buffer)
|
||||
val searchList = SearchList.buildRange (buffer, searchString, low - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val low = Cursor.viH (buffer, low)
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val low = Cursor.vi0 (buffer, low)
|
||||
val newCount = if low = 0 then 0 else count - 1
|
||||
in
|
||||
helpDeleteLineBack (app, buffer, low, high, newCount)
|
||||
end
|
||||
|
||||
fun deleteLineBack (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val low = Cursor.vi0 (buffer, cursorIdx)
|
||||
val high = Cursor.viDlr (buffer, cursorIdx, 1) + 1
|
||||
in
|
||||
helpDeleteLineBack (app, buffer, low, high, count)
|
||||
end
|
||||
|
||||
fun deleteToFirstNonSpaceChr (app: app_type) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, searchString
|
||||
, ...
|
||||
} = app
|
||||
|
||||
(* move LineGap and buffer to start of line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
(* move cursorIdx to first character on line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = Cursor.firstNonSpaceChr (buffer, otherIdx)
|
||||
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun helpDeleteToChr
|
||||
(app: app_type, buffer, cursorIdx, otherIdx, count, fMove, fInc, chr) =
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val searchString = #searchString app
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
in
|
||||
Finish.buildTextAndClearAfterChr (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val newOtherIdx = fMove (buffer, otherIdx, chr)
|
||||
val newCount = if newOtherIdx = otherIdx then 0 else count - 1
|
||||
val newOtherIdx = fInc (newOtherIdx, 1)
|
||||
in
|
||||
helpDeleteToChr
|
||||
(app, buffer, cursorIdx, newOtherIdx, newCount, fMove, fInc, chr)
|
||||
end
|
||||
|
||||
fun deleteToChr (app: app_type, count, fMove, fInc, chr) =
|
||||
helpDeleteToChr
|
||||
( app
|
||||
, #buffer app
|
||||
, #cursorIdx app
|
||||
, #cursorIdx app
|
||||
, count
|
||||
, fMove
|
||||
, fInc
|
||||
, chr
|
||||
)
|
||||
|
||||
fun deleteToStart (app: app_type) =
|
||||
let
|
||||
val {cursorIdx, buffer, windowWidth, windowHeight, searchString, ...} =
|
||||
app
|
||||
|
||||
val buffer = LineGap.delete (0, cursorIdx, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, #searchString app)]
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx + 777, buffer)
|
||||
val searchList =
|
||||
SearchList.buildRange (buffer, searchString, cursorIdx - 777)
|
||||
|
||||
val cursorIdx = 0
|
||||
val startLine = 0
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val drawMsg = TextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, searchString
|
||||
, initialMsg
|
||||
)
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
AppWith.bufferAndCursorIdx
|
||||
(app, buffer, cursorIdx, mode, startLine, searchList, drawMsg)
|
||||
end
|
||||
|
||||
fun helpDeleteToMatch (app: app_type, low, high) =
|
||||
let
|
||||
val {buffer, searchString, ...} = app
|
||||
val length = high - low
|
||||
val buffer = LineGap.delete (low, length, buffer)
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val initialMsg = [SEARCH (buffer, searchString)]
|
||||
|
||||
val buffer = LineGap.goToIdx (low + 777, buffer)
|
||||
val searchList = SearchList.buildRange (buffer, searchString, low - 777)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
in
|
||||
Finish.buildTextAndClear (app, buffer, low, searchList, initialMsg)
|
||||
end
|
||||
|
||||
fun deleteToNextMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, ...} = app
|
||||
val newCursorIdx = SearchList.nextMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if newCursorIdx = ~1 orelse newCursorIdx <= cursorIdx then Finish.clearMode app
|
||||
else helpDeleteToMatch (app, cursorIdx, newCursorIdx)
|
||||
end
|
||||
|
||||
fun deleteToPrevMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, ...} = app
|
||||
val newCursorIdx = SearchList.prevMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if newCursorIdx = ~1 orelse newCursorIdx >= cursorIdx then Finish.clearMode app
|
||||
else helpDeleteToMatch (app, newCursorIdx, cursorIdx)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user