signature MAKE_NORMAL_DELETE = sig val initMsgs: int * int * LineGap.t -> MailboxType.t list end functor MakeNormalDelete(Fn: MAKE_NORMAL_DELETE) = struct open AppType 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 (* 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) 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 ) 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) val drawMsg = NormalModeTextBuilder.build ( startLine , cursorIdx , buffer , windowWidth , windowHeight , searchList , searchString , visualScrollColumn ) val drawMsg = Vector.concat drawMsg val drawMsg = DRAW_TEXT drawMsg val msgs = DRAW drawMsg :: initialMsg val mode = NORMAL_MODE "" in NormalModeWith.bufferAndCursorIdx ( app , buffer , cursorIdx , mode , startLine , searchList , 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