Files
sml-projects/fcore/normal-mode/make-normal-delete.sml

827 lines
28 KiB
Standard ML
Raw Normal View History

signature MAKE_NORMAL_DELETE =
sig
val initMsgs: int * int * LineGap.t -> MailboxType.t list
end
functor MakeNormalDelete(Fn: MAKE_NORMAL_DELETE) =
struct
open AppType
2025-09-12 13:47:14 +01:00
open DrawMsg
open MailboxType
fun finishAfterDeletingBuffer (app: app_type, low, buffer, time, msgs) =
let
val buffer = LineGap.goToIdx (low, buffer)
val searchString = #searchString app
val buffer = LineGap.goToStart buffer
val msgs = SEARCH (buffer, searchString, time) :: msgs
val buffer = LineGap.goToIdx (low - 1111, buffer)
val searchList = SearchList.buildRange (buffer, searchString, low + 1111)
val buffer = LineGap.goToIdx (low, buffer)
in
NormalFinish.buildTextAndClear (app, buffer, low, searchList, msgs, time)
end
fun deleteAndFinish (app: app_type, low, length, buffer, time) =
let
val buffer = LineGap.goToIdx (low + length, buffer)
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
in
finishAfterDeletingBuffer (app, low, buffer, time, initialMsg)
end
(* equivalent of vi's 'x' command **)
fun removeChr (app: app_type, count, time) =
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
in
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
NormalFinish.clearMode app
else
let
val lineStart = Cursor.vi0 (buffer, cursorIdx)
val lineEnd = Cursor.viDlrForDelete (buffer, cursorIdx, 1)
val buffer = LineGap.goToIdx (lineEnd, buffer)
(* if specified length (cursorIdx + count) extends
* beyond current line's length,
* then clip the length, to ensure we don't delete the newline *)
val high = cursorIdx + count
val high =
if Cursor.isOnNewlineAfterChr (buffer, lineEnd) then lineEnd
else Int.min (lineEnd - 1, high)
val length = high - cursorIdx
val initialMsg = Fn.initMsgs (cursorIdx, length, buffer)
val buffer = LineGap.delete (cursorIdx, length, buffer)
(* figure out where to place cursor *)
val buffer = LineGap.goToIdx (lineStart, buffer)
val lineEndAfterDelete = Cursor.viDlr (buffer, lineStart, 1)
val cursorIdx = Int.min (lineEndAfterDelete, cursorIdx)
in
if cursorIdx >= #textLength buffer - 1 then
(* special case: when buffer does not end with newline
* and we are deleting from the last line *)
let
val cursorIdx = #textLength buffer - 1
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val cursorIdx =
if Cursor.isOnNewlineAfterChr (buffer, cursorIdx) then
cursorIdx - 1
else
cursorIdx
in
finishAfterDeletingBuffer
(app, cursorIdx, buffer, time, initialMsg)
end
else
finishAfterDeletingBuffer (app, cursorIdx, buffer, time, initialMsg)
end
end
(* Note: The below implementation of removing line breaks with the 'J'
* command slightly differs from the implementation in Vim.
* In Vim, the J, 1J, and 2J commands all have the same effect.
* 3J, 4J, 5J, etc. all have different effects because of the different counts
* though.
* Our implementation has a different effect for each count.
* 1J delettes 1 line break, 2J deletes 2, and so on. *)
fun helpRemoveLineBreaks (app, buffer, cursorIdx, count, time) =
if count = 0 then
(* we don't use Fn.initMsgs in this function.
* Removing line breaks is a discrete action which doesn't operate
* as a range the way that motions like 'dw' or 'diw'.
* Instead, a single character is deleted at different places.
* So it doesn't make any sense to use Fn.initMsgs
* which expects a range. *)
finishAfterDeletingBuffer (app, cursorIdx, buffer, time, [])
else
let
val buffer = LineGap.goToIdx (cursorIdx, buffer)
in
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
if cursorIdx >= #textLength buffer - 2 then
finishAfterDeletingBuffer (app, cursorIdx, buffer, time, [])
else
(* if the cursor is at a linebreak, delete the linebreak
* and don't insert a space. *)
let val buffer = LineGap.delete (cursorIdx, 1, buffer)
in helpRemoveLineBreaks (app, buffer, cursorIdx, count - 1, time)
end
else
let
val newCursorIdx = Cursor.toNextChr (buffer, cursorIdx, #"\n")
in
if newCursorIdx >= #textLength buffer - 2 then
finishAfterDeletingBuffer (app, cursorIdx, buffer, time, [])
else
let
val buffer = LineGap.delete (newCursorIdx, 1, buffer)
val buffer = LineGap.insert (newCursorIdx, " ", buffer)
in
helpRemoveLineBreaks
(app, buffer, newCursorIdx, count - 1, time)
end
end
end
fun removeLineBreaks (app: app_type, count, time) =
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
in
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
if cursorIdx >= #textLength buffer - 2 then
NormalFinish.clearMode app
else
let val buffer = LineGap.delete (cursorIdx, 1, buffer)
in helpRemoveLineBreaks (app, buffer, cursorIdx, count - 1, time)
end
else
let
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val newCursorIdx = Cursor.toNextChr (buffer, cursorIdx, #"\n")
val buffer = LineGap.goToIdx (newCursorIdx, buffer)
in
if
cursorIdx = newCursorIdx
orelse newCursorIdx >= #textLength buffer - 2
then
(* no change, either because no #"\n" was found
* or because the next #"\n" found is the last character in the
* file.
* We don't delete the last #"\n" in the file
* because Unix convention is that text files always
* end with newlines. *)
NormalFinish.clearMode app
else
let
val buffer = LineGap.delete (newCursorIdx, 1, buffer)
val buffer = LineGap.insert (newCursorIdx, " ", buffer)
in
helpRemoveLineBreaks (app, buffer, newCursorIdx, count - 1, time)
end
end
end
fun helpDelete
(app: app_type, buffer, cursorIdx, otherIdx, count, fMove, time) =
(* 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 =
if high >= #textLength buffer - 2 then
Int.max (#textLength buffer - 2, 0)
else
high
val length = high - low
val buffer = LineGap.goToIdx (high, buffer)
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToStart buffer
val searchString = #searchString app
val initialMsg = SEARCH (buffer, searchString, time) :: initialMsg
val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer)
val searchList =
SearchList.buildRange (buffer, searchString, cursorIdx + 1111)
(* 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 =
if low >= #textLength buffer then
Int.max (#textLength buffer - 2, low)
else
low
in
NormalFinish.buildTextAndClear
(app, buffer, cursorIdx, searchList, initialMsg, time)
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, time)
end
fun delete (app: app_type, count, fMove, time) =
let val {buffer, cursorIdx, ...} = app
in helpDelete (app, buffer, cursorIdx, cursorIdx, count, fMove, time)
end
fun finishDeleteByDfa (app, low, high, buffer, time) =
let
val length = high - low
val buffer = LineGap.goToIdx (high, buffer)
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
in
finishAfterDeletingBuffer (app, low, buffer, time, initialMsg)
end
fun deleteByDfa (app as {buffer, ...}: app_type, count, fMove, time) =
if #textLength buffer = 1 then
NormalFinish.clearMode app
else
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val otherIdx = fMove (buffer, cursorIdx, count)
in
if otherIdx > cursorIdx then
if otherIdx >= #textLength buffer then
2025-09-23 07:04:41 +01:00
(* prevent us from deleting last newline
* to help us preserve unix-style line endings *)
let
(* if we're on the first character/column in a line,
* we would like to delete the preceding newline too *)
val cursorIdx =
if Cursor.isPrevChrStartOfLine (buffer, cursorIdx) then
cursorIdx - 1
else
cursorIdx
val high = #textLength buffer - 1
in
finishDeleteByDfa (app, cursorIdx, high, buffer, time)
end
else
finishDeleteByDfa (app, cursorIdx, otherIdx, buffer, time)
else if otherIdx < cursorIdx then
finishDeleteByDfa (app, otherIdx, cursorIdx, buffer, time)
else
NormalFinish.clearMode app
end
fun deleteCharsLeft (app: app_type, count, time) =
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val startOfLine = Cursor.vi0 (buffer, cursorIdx)
val low = Cursor.viH (buffer, cursorIdx, count)
val low = if low < startOfLine then startOfLine else low
val length = cursorIdx - low
in
deleteAndFinish (app, low, length, buffer, time)
end
fun deleteToEndOfPrevWord (app: app_type, count, time) =
let
val {buffer, cursorIdx, searchString, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val low = Cursor.endOfPrevWord (buffer, cursorIdx, count)
val length = (cursorIdx + 1) - low
in
deleteAndFinish (app, low, length, buffer, time)
end
fun deleteToEndOfPrevWORD (app: app_type, count, time) =
let
val {buffer, cursorIdx, searchString, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val low = Cursor.endOfPrevWORD (buffer, cursorIdx, count)
val length = (cursorIdx + 1) - low
in
deleteAndFinish (app, low, length, buffer, time)
end
fun deleteToEndOfLine (app: app_type, time) =
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
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 *)
NormalFinish.clearMode app
else
let
val lineStart = Cursor.vi0 (buffer, cursorIdx)
val high = Cursor.viDlrForDelete (buffer, cursorIdx, 1)
val length = high - cursorIdx
val buffer = LineGap.goToIdx (high, buffer)
val initialMsg = Fn.initMsgs (cursorIdx, length, buffer)
val buffer = LineGap.delete (cursorIdx, length, buffer)
(* calculate new cursorIdx.
* Because we deleted the cursor that this line is on,
* we need to set the cursorIdx to the place
* that is considered to be the "end of line"
* after having performed the deletion. *)
val buffer = LineGap.goToIdx (lineStart, buffer)
val cursorIdx = Cursor.viDlr (buffer, lineStart, 1)
in
finishAfterDeletingBuffer (app, cursorIdx, buffer, time, initialMsg)
end
end
fun deleteLine (app: app_type, count, time) =
let
val {buffer, cursorIdx, searchString, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val startIdx = Cursor.vi0 (buffer, cursorIdx)
2025-09-16 07:40:58 +01:00
val buffer = LineGap.goToIdx (startIdx, buffer)
val finishIdx = Cursor.viDlr (buffer, cursorIdx, count) + 2
val length = finishIdx - startIdx
val textLengthBeforeDelete = #textLength buffer
val buffer = LineGap.goToIdx (startIdx, buffer)
val initialMsg = Fn.initMsgs (startIdx, length, buffer)
val buffer = LineGap.delete (startIdx, length, buffer)
in
if finishIdx >= textLengthBeforeDelete - 1 then
(* we deleted to the end of the buffer.
* Let's check if there is a newline at the end
* and insert one if there isn't. *)
let
val lastPosAfterDelete = #textLength buffer - 1
val buffer = LineGap.goToIdx (lastPosAfterDelete, buffer)
val buffer =
if lastPosAfterDelete < 0 then
LineGap.append ("\n", buffer)
else if Cursor.isCursorAtStartOfLine (buffer, lastPosAfterDelete) then
buffer
else
LineGap.append ("\n", buffer)
in
finishAfterDeletingBuffer (app, startIdx, buffer, time, initialMsg)
end
else
finishAfterDeletingBuffer (app, startIdx, buffer, time, initialMsg)
end
fun finishDeleteLineBack (app, buffer, lineIdx, length, endOfLine, time) =
if endOfLine >= #textLength buffer - 2 then
(* deleting from last line *)
let
(* go to first column of previous line *)
val buffer = LineGap.goToIdx (lineIdx, buffer)
val newCursorIdx = Cursor.viH (buffer, lineIdx, 1)
val buffer = LineGap.goToIdx (newCursorIdx, buffer)
val newCursorIdx = Cursor.vi0 (buffer, newCursorIdx)
(* clip endOfLine so we leave a newline at end of file *)
val endOfLine = Int.max (0, #textLength buffer - 1)
val buffer = LineGap.goToIdx (endOfLine, buffer)
val initialMsg = Fn.initMsgs (lineIdx, length, buffer)
val buffer = LineGap.delete (lineIdx, length, buffer)
in
finishAfterDeletingBuffer (app, newCursorIdx, buffer, time, initialMsg)
end
else
let
val buffer = LineGap.goToIdx (endOfLine, buffer)
val initialMsg = Fn.initMsgs (lineIdx, length, buffer)
val buffer = LineGap.delete (lineIdx, length, buffer)
(* make sure the cursorIdx is at the first column
* of the previous line, after deleting from buffer *)
val buffer = LineGap.goToIdx (lineIdx, buffer)
val newCursorIdx = Cursor.viH (buffer, lineIdx, 1)
val buffer = LineGap.goToIdx (newCursorIdx, buffer)
val newCursorIdx = Cursor.vi0 (buffer, newCursorIdx)
in
finishAfterDeletingBuffer (app, newCursorIdx, buffer, time, initialMsg)
end
fun deleteLineBack (app: app_type, count, time) =
let
val {buffer, cursorIdx, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val cursorLineNumber =
if Cursor.isNextChrEndOfLine (buffer, cursorIdx) then
LineGap.idxToLineNumber (cursorIdx + 1, buffer)
else
LineGap.idxToLineNumber (cursorIdx, buffer)
val newCursorLineNumber = Int.max (cursorLineNumber - count, 0)
in
if cursorLineNumber = 0 then
NormalFinish.clearMode app
else if newCursorLineNumber = 0 then
(* deleting from current line to start of file *)
let val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1) + 2
in finishDeleteLineBack (app, buffer, 0, endOfLine, endOfLine, time)
end
else
let
val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1) + 1
val buffer = LineGap.goToLine (newCursorLineNumber, buffer)
val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer)
val length = endOfLine - lineIdx
in
finishDeleteLineBack (app, buffer, lineIdx, length, endOfLine, time)
end
end
fun deleteToFirstNonSpaceChr (app: app_type, time) =
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
in
deleteAndFinish (app, low, length, buffer, time)
end
fun helpDeleteToChr
(app: app_type, buffer, cursorIdx, otherIdx, count, fMove, fInc, chr, time) =
if count = 0 then
let
val low = Int.min (cursorIdx, otherIdx)
val high = Int.max (cursorIdx, otherIdx)
val length = high - low
in
deleteAndFinish (app, low, length, buffer, time)
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
, time
)
end
fun deleteToChr (app: app_type, count, fMove, fInc, chr, time) =
helpDeleteToChr
( app
, #buffer app
, #cursorIdx app
, #cursorIdx app
, count
, fMove
, fInc
, chr
, time
)
2025-09-12 13:47:14 +01:00
fun deleteToStart (app: app_type, time) : AppType.app_type =
let
val {cursorIdx, buffer, windowWidth, windowHeight, searchString, ...} =
app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val cursorIdx = Cursor.viDlrForDelete (buffer, cursorIdx, 1)
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val initialMsg = Fn.initMsgs (0, cursorIdx, buffer)
val buffer = LineGap.delete (0, cursorIdx, buffer)
val buffer = LineGap.goToStart buffer
val initialMsg = SEARCH (buffer, #searchString app, time) :: initialMsg
val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer)
val searchList =
SearchList.buildRange (buffer, searchString, cursorIdx + 1111)
val cursorIdx = 0
val startLine = 0
val visualScrollColumn = 0
val buffer = LineGap.goToIdx (cursorIdx, buffer)
2025-09-12 13:47:14 +01:00
val drawMsg = NormalModeTextBuilder.build
( startLine
, cursorIdx
, buffer
, windowWidth
, windowHeight
, searchList
, searchString
2025-09-12 13:47:14 +01:00
, visualScrollColumn
)
2025-09-12 13:47:14 +01:00
val drawMsg = Vector.concat drawMsg
val drawMsg = DRAW_TEXT drawMsg
2025-09-12 13:47:14 +01:00
val msgs = DRAW drawMsg :: initialMsg
val mode = NORMAL_MODE ""
in
NormalModeWith.bufferAndCursorIdx
( app
, buffer
, cursorIdx
, mode
, startLine
, searchList
2025-09-12 13:47:14 +01:00
, msgs
, time
, visualScrollColumn
)
end
fun helpDeleteToMatch (app: app_type, low, high, time) =
let
val {buffer, searchString, ...} = app
val buffer = LineGap.goToIdx (high, buffer)
val length = high - low
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToStart buffer
val initialMsg = SEARCH (buffer, searchString, time) :: initialMsg
val buffer = LineGap.goToIdx (low - 1111, buffer)
val searchList = SearchList.buildRange (buffer, searchString, low + 1111)
val buffer = LineGap.goToIdx (low, buffer)
in
NormalFinish.buildTextAndClear
(app, buffer, low, searchList, initialMsg, time)
end
fun deleteToNextMatch (app: app_type, count, time) =
let
val {cursorIdx, searchList, ...} = app
val newCursorIdx = SearchList.nextMatch (cursorIdx, searchList, count)
in
if newCursorIdx = ~1 orelse newCursorIdx <= cursorIdx then
NormalFinish.clearMode app
else
helpDeleteToMatch (app, cursorIdx, newCursorIdx, time)
end
fun deleteToPrevMatch (app: app_type, count, time) =
let
val {cursorIdx, searchList, ...} = app
val newCursorIdx = SearchList.prevMatch (cursorIdx, searchList, count)
in
if newCursorIdx = ~1 orelse newCursorIdx >= cursorIdx then
NormalFinish.clearMode app
else
helpDeleteToMatch (app, newCursorIdx, cursorIdx, time)
end
(* check if we are trying to delete from an empty buffer
* or a buffer which consists of only one character which is \n *)
fun canDeleteInsideOrAround (buffer, low, length) =
not (length = 1 andalso LineGap.substring (low, 1, buffer) = "\n")
fun deleteInsideWord (app: app_type, time) =
let
val {buffer, cursorIdx, searchString, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val low = Cursor.prevWordStrict (buffer, cursorIdx, 1)
val high = Cursor.endOfWordStrict (buffer, cursorIdx, 1) + 1
val buffer = LineGap.goToIdx (high, buffer)
val length = high - low
in
if canDeleteInsideOrAround (buffer, low, length) then
let
val buffer = LineGap.goToIdx (high, buffer)
val length = high - low
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToStart buffer
val initialMsg = SEARCH (buffer, searchString, time) :: initialMsg
val buffer = LineGap.goToIdx (low - 1111, buffer)
val searchList =
SearchList.buildRange (buffer, searchString, low + 1111)
val buffer = LineGap.goToIdx (low, buffer)
in
NormalFinish.buildTextAndClear
(app, buffer, low, searchList, initialMsg, time)
end
else
app
end
fun deleteInsideWORD (app: app_type, time) =
let
val {buffer, cursorIdx, searchString, ...} = app
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val low = Cursor.prevWORDStrict (buffer, cursorIdx, 1)
val high = Cursor.endOfWORDStrict (buffer, cursorIdx, 1) + 1
val buffer = LineGap.goToIdx (high, buffer)
val length = high - low
in
if canDeleteInsideOrAround (buffer, low, length) then
let
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToStart buffer
val initialMsg = SEARCH (buffer, searchString, time) :: initialMsg
val buffer = LineGap.goToIdx (low - 1111, buffer)
val searchList =
SearchList.buildRange (buffer, searchString, low + 1111)
val buffer = LineGap.goToIdx (low, buffer)
in
NormalFinish.buildTextAndClear
(app, buffer, low, searchList, initialMsg, time)
end
else
app
end
fun finishAfterDeleteInside (app: app_type, origLow, high, time) =
if origLow = high then
NormalFinish.clearMode app
else
let
val {cursorIdx, buffer, searchString, ...} = app
val low = origLow + 1
val length = high - low
val buffer = LineGap.goToIdx (high, buffer)
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToStart buffer
val initialMsg = SEARCH (buffer, searchString, time) :: initialMsg
val buffer = LineGap.goToIdx (low - 1111, buffer)
val searchList =
SearchList.buildRange (buffer, searchString, low + 1111)
val buffer = LineGap.goToIdx (origLow, buffer)
in
NormalFinish.buildTextAndClear
(app, buffer, origLow, searchList, initialMsg, time)
end
fun deleteInsideChrOpen (app: app_type, chr, time) =
let
val {cursorIdx, buffer, ...} = app
val start = cursorIdx + 1
val buffer = LineGap.goToIdx (start, buffer)
val origLow = Cursor.toPrevChr (buffer, start, chr)
val buffer = LineGap.goToIdx (origLow, buffer)
val high = Cursor.matchPair (buffer, origLow)
in
finishAfterDeleteInside (app, origLow, high, time)
end
fun deleteInsideChrClose (app: app_type, chr, time) =
let
val {cursorIdx, buffer, ...} = app
val start = Int.max (cursorIdx - 1, 0)
val buffer = LineGap.goToIdx (start, buffer)
val high = Cursor.toNextChr (buffer, start, chr)
val buffer = LineGap.goToIdx (high, buffer)
val origLow = Cursor.matchPair (buffer, high)
in
finishAfterDeleteInside (app, origLow, high, time)
end
fun finishDeleteAroundChr (app, low, high, buffer, time) =
let
val length = high - low + 1
val buffer = LineGap.goToIdx (high, buffer)
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToIdx (low, buffer)
val low =
if Cursor.isCursorAtStartOfLine (buffer, low) then Int.max (low - 1, 0)
else low
in
finishAfterDeletingBuffer (app, low, buffer, time, initialMsg)
end
fun deleteAroundChrOpen (app: app_type, chr, time) =
let
val {cursorIdx, buffer, ...} = app
val start = cursorIdx + 1
val buffer = LineGap.goToIdx (start, buffer)
val low = Cursor.toPrevChr (buffer, start, chr)
val buffer = LineGap.goToIdx (low, buffer)
val high = Cursor.matchPair (buffer, low)
in
if low = high then NormalFinish.clearMode app
else finishDeleteAroundChr (app, low, high, buffer, time)
end
fun deleteAroundChrClose (app: app_type, chr, time) =
let
val {cursorIdx, buffer, ...} = app
val start = Int.max (cursorIdx - 1, 0)
val buffer = LineGap.goToIdx (start, buffer)
val high = Cursor.toNextChr (buffer, start, chr)
val buffer = LineGap.goToIdx (high, buffer)
val low = Cursor.matchPair (buffer, high)
in
if low = high then NormalFinish.clearMode app
else finishDeleteAroundChr (app, low, high, buffer, time)
end
fun deletePair (app: app_type, time) =
let
val {cursorIdx, buffer, ...} = app
val otherIdx = Cursor.matchPair (buffer, cursorIdx)
in
if otherIdx = cursorIdx then
NormalFinish.clearMode app
else
let
val low = Int.min (cursorIdx, otherIdx)
val high = Int.max (cursorIdx, otherIdx)
val length = high - low + 1
val buffer = LineGap.goToIdx (high, buffer)
val initialMsg = Fn.initMsgs (low, length, buffer)
val buffer = LineGap.delete (low, length, buffer)
val buffer = LineGap.goToIdx (low, buffer)
val low =
if Cursor.isCursorAtStartOfLine (buffer, low) then
(* we may have deleted the last character of this current line,
* and if we did, we have to move the cursor back by 1 *)
Int.max (low - 1, 0)
else
low
val searchString = #searchString app
val buffer = LineGap.goToStart buffer
val initialMsg = SEARCH (buffer, searchString, time) :: initialMsg
val buffer = LineGap.goToIdx (low - 1111, buffer)
val searchList =
SearchList.buildRange (buffer, searchString, low + 1111)
val buffer = LineGap.goToIdx (low, buffer)
in
NormalFinish.buildTextAndClear
(app, buffer, low, searchList, initialMsg, time)
end
end
end