with cursor movements, instead of passing in functions as parameters (callbacks/higher order functions), functorise the cursor movement functions instead so we can take advantage of defunctorisation and avoid the runtime cost of closures/higher order functions/function pointers

This commit is contained in:
2025-01-09 22:30:51 +00:00
parent 7045b69ce8
commit deb24c2063
5 changed files with 152 additions and 111 deletions

View File

@@ -40,39 +40,6 @@ struct
(app, newBuffer, newWidth, newHeight, searchList, drawMsg) (app, newBuffer, newWidth, newHeight, searchList, drawMsg)
end end
fun buildTextAndClear (app: app_type, buffer, cursorIdx, searchList) =
let
val {windowWidth, windowHeight, startLine, searchString, ...} = app
(* move LineGap to first line displayed on screen
* and move searchList to line's start idx as well *)
val buffer = LineGap.goToLine (startLine, buffer)
(* get new startLine which may move screen depending on cursor movements *)
val startLine = TextWindow.getStartLine
(buffer, startLine, cursorIdx, windowWidth, windowHeight)
(* move buffer to new startLine as required by TextBuilder.build *)
val buffer = LineGap.goToLine (startLine, buffer)
val lineIdx = TextBuilder.getLineAbsIdx (startLine, buffer)
val searchList = SearchList.goToNum (lineIdx, searchList)
val drawMsg = TextBuilder.build
( startLine
, cursorIdx
, buffer
, windowWidth
, windowHeight
, searchList
, searchString
)
val mode = NORMAL_MODE ""
in
AppWith.bufferAndCursorIdx
(app, buffer, cursorIdx, mode, startLine, searchList, drawMsg)
end
(* Difference between this and buildTextAndClear is that (* Difference between this and buildTextAndClear is that
* this is meant to be called after a chr movement, * this is meant to be called after a chr movement,
* where the cursor may possibly jump off window by a wide marigin. * where the cursor may possibly jump off window by a wide marigin.
@@ -90,7 +57,7 @@ struct
(buffer, startLine, cursorIdx, windowWidth, windowHeight) (buffer, startLine, cursorIdx, windowWidth, windowHeight)
(* move buffer to new startLine as required by TextBuilder.build (* move buffer to new startLine as required by TextBuilder.build
* and move searchList to idx where line starts as well *) * and move searchList to idx where line starts as well *)
val buffer = LineGap.goToLine (startLine, buffer) val buffer = LineGap.goToLine (startLine, buffer)
val lineIdx = TextBuilder.getLineAbsIdx (startLine, buffer) val lineIdx = TextBuilder.getLineAbsIdx (startLine, buffer)
val searchList = SearchList.goToNum (lineIdx, searchList) val searchList = SearchList.goToNum (lineIdx, searchList)
@@ -258,32 +225,6 @@ struct
(app, buffer, cursorIdx, mode, startLine, searchList, drawMsg) (app, buffer, cursorIdx, mode, startLine, searchList, drawMsg)
end end
fun helpMove (app: app_type, buffer, cursorIdx, count, fMove) =
if count = 0 then
buildTextAndClear (app, buffer, cursorIdx, #searchList app)
else
(* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *)
let
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val newCursorIdx = fMove (buffer, cursorIdx)
val newCursorIdx = Cursor.clipIdx (buffer, newCursorIdx)
val newCount =
(* it's possible to loop a very high number like 5432131
* which will take a long time because of the high number of loops
* regardless of the data structure used.
* If this happens, and the newCursorIdx is the same as the old one,
* then skip to end of loop by going to base case. *)
if cursorIdx = newCursorIdx then 0
else count - 1
in
helpMove (app, buffer, newCursorIdx, newCount, fMove)
end
fun move (app: app_type, count, fMove) =
let val {cursorIdx, buffer, ...} = app
in helpMove (app, buffer, cursorIdx, count, fMove)
end
fun moveToMatchingPair (app: app_type) = fun moveToMatchingPair (app: app_type) =
let let
val val
@@ -376,7 +317,7 @@ struct
val buffer = LineGap.goToIdx (cursorIdx, buffer) val buffer = LineGap.goToIdx (cursorIdx, buffer)
val cursorIdx = Cursor.firstNonSpaceChr (buffer, cursorIdx) val cursorIdx = Cursor.firstNonSpaceChr (buffer, cursorIdx)
in in
buildTextAndClear (app, buffer, cursorIdx, #searchList app) Finish.buildTextAndClear (app, buffer, cursorIdx, #searchList app)
end end
fun helpMoveToChr (app: app_type, buffer, cursorIdx, count, fMove, chr) = fun helpMoveToChr (app: app_type, buffer, cursorIdx, count, fMove, chr) =
@@ -407,8 +348,8 @@ struct
case newCmd of case newCmd of
CHAR_EVENT chr => CHAR_EVENT chr =>
(case chr of (case chr of
#"e" => move (app, count, Cursor.endOfPrevWord) #"e" => MoveToEndOfPrevWord.move (app, count)
| #"E" => move (app, count, Cursor.endOfPrevWORD) | #"E" => MoveToEndOfPrevWORD.move (app, count)
| #"g" => moveToStart app | #"g" => moveToStart app
| _ => clearMode app) | _ => clearMode app)
| KEY_ESC => clearMode app | KEY_ESC => clearMode app
@@ -429,36 +370,36 @@ struct
fun helpRemoveChr (app: app_type, buffer, searchList, cursorIdx, count) = fun helpRemoveChr (app: app_type, buffer, searchList, cursorIdx, count) =
if count = 0 then if count = 0 then
buildTextAndClear (app, buffer, cursorIdx, searchList) Finish.buildTextAndClear (app, buffer, cursorIdx, searchList)
else else
let let
val buffer = LineGap.goToIdx (cursorIdx, buffer) val buffer = LineGap.goToIdx (cursorIdx, buffer)
(* Explanation of how Vi's 'x' command behaves: (* Explanation of how Vi's 'x' command behaves:
* If the cursor is at the end of the file, * If the cursor is at the end of the file,
* then it is decremented by 1. * then it is decremented by 1.
* If the character after the cursor is a line break, * If the character after the cursor is a line break,
* then it is also decremented by 1. * then it is also decremented by 1.
* If the character before the cursor is a linee break, the cursor stays * If the character before the cursor is a linee break, the cursor stays
* where it is. * where it is.
* If the chracter AT the cursor is a line break and the characater * 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. * AFTER the cursor is also a line break, then nothing is deleted.
* Otherwise, the same cursor is returned. * Otherwise, the same cursor is returned.
* All decrement cases do not decrement when the cursor is 0. *) * All decrement cases do not decrement when the cursor is 0. *)
val cursorIsStart = Cursor.isCursorAtStartOfLine (buffer, cursorIdx) val cursorIsStart = Cursor.isCursorAtStartOfLine (buffer, cursorIdx)
val nextIsEnd = Cursor.isNextChrEndOfLine (buffer, cursorIdx) val nextIsEnd = Cursor.isNextChrEndOfLine (buffer, cursorIdx)
in in
if nextIsEnd andalso cursorIsStart then if nextIsEnd andalso cursorIsStart then
(* vi simply doesn't do anything on 'x' command (* vi simply doesn't do anything on 'x' command
* when cursor is at start of line, and next chr is line break * when cursor is at start of line, and next chr is line break
* so skip to end of loop by passing count of 0 *) * so skip to end of loop by passing count of 0 *)
helpRemoveChr (app, buffer, searchList, cursorIdx, 0) helpRemoveChr (app, buffer, searchList, cursorIdx, 0)
else if cursorIsStart then else if cursorIsStart then
helpRemoveChr (app, buffer, searchList, cursorIdx, 0) helpRemoveChr (app, buffer, searchList, cursorIdx, 0)
else if nextIsEnd then else if nextIsEnd then
let let
(* delete char at cursor and then decrement cursorIdx by 1 (* delete char at cursor and then decrement cursorIdx by 1
* if cursorIdx is not 0 *) * if cursorIdx is not 0 *)
val {searchString, ...} = app val {searchString, ...} = app
val buffer = LineGap.delete (cursorIdx, 1, buffer) val buffer = LineGap.delete (cursorIdx, 1, buffer)
@@ -491,12 +432,12 @@ struct
fun helpDelete (app: app_type, buffer, cursorIdx, otherIdx, count, fMove) = fun helpDelete (app: app_type, buffer, cursorIdx, otherIdx, count, fMove) =
(* As a small optimisation to reduce allocations, (* As a small optimisation to reduce allocations,
* we accumulate otherIdx by calling fMove with it and the buffer * we accumulate otherIdx by calling fMove with it and the buffer
* on each loop. * on each loop.
* Then, at the end of the loop, we perform the actual deletion. * Then, at the end of the loop, we perform the actual deletion.
* This is faster than performing the actual deletion on every loop * This is faster than performing the actual deletion on every loop
* because we only delete once, and avoid allocating intermediary buffers. * because we only delete once, and avoid allocating intermediary buffers.
* The behaviour between the two is equivalent. *) * The behaviour between the two is equivalent. *)
if count = 0 then if count = 0 then
let let
val low = Int.min (cursorIdx, otherIdx) val low = Int.min (cursorIdx, otherIdx)
@@ -511,12 +452,12 @@ struct
(low, length, searchString, searchList, buffer) (low, length, searchString, searchList, buffer)
(* If we have deleted from the buffer so that cursorIdx (* If we have deleted from the buffer so that cursorIdx
* is no longer a valid idx, * is no longer a valid idx,
* clip cursorIdx to the end. *) * clip cursorIdx to the end. *)
val buffer = LineGap.goToIdx (low, buffer) val buffer = LineGap.goToIdx (low, buffer)
val cursorIdx = Cursor.clipIdx (buffer, low) val cursorIdx = Cursor.clipIdx (buffer, low)
in in
buildTextAndClear (app, buffer, cursorIdx, searchList) Finish.buildTextAndClear (app, buffer, cursorIdx, searchList)
end end
else else
let let
@@ -537,16 +478,16 @@ struct
in in
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
(* if we are on \n, we don't want to delete or do anything (* if we are on \n, we don't want to delete or do anything
* so reset the mode *) * so reset the mode *)
clearMode app clearMode app
else else
let let
(* viDlr takes us to the last chr in the line (* viDlr takes us to the last chr in the line
* but does not delete that last chr * but does not delete that last chr
* so we call helpRemoveChr to delete that last chr. * so we call helpRemoveChr to delete that last chr.
* We also rely on helpRemoveChr to handle backwards-movement logic: * We also rely on helpRemoveChr to handle backwards-movement logic:
* If cursorIdx is at \n after deletion, then stop. * If cursorIdx is at \n after deletion, then stop.
* Else, move back one chr. *) * Else, move back one chr. *)
val lastChr = Cursor.viDlr (buffer, cursorIdx) val lastChr = Cursor.viDlr (buffer, cursorIdx)
val length = lastChr - cursorIdx val length = lastChr - cursorIdx
val buffer = LineGap.delete (cursorIdx, length, buffer) val buffer = LineGap.delete (cursorIdx, length, buffer)
@@ -574,7 +515,7 @@ struct
val buffer = LineGap.goToIdx (cursorIdx, buffer) val buffer = LineGap.goToIdx (cursorIdx, buffer)
val cursorIdx = Cursor.clipIdx (buffer, cursorIdx) val cursorIdx = Cursor.clipIdx (buffer, cursorIdx)
in in
buildTextAndClear (app, buffer, cursorIdx, searchList) Finish.buildTextAndClear (app, buffer, cursorIdx, searchList)
end end
else else
let let
@@ -608,7 +549,7 @@ struct
val buffer = LineGap.goToIdx (low, buffer) val buffer = LineGap.goToIdx (low, buffer)
in in
buildTextAndClear (app, buffer, low, searchList) Finish.buildTextAndClear (app, buffer, low, searchList)
end end
else else
let let
@@ -659,7 +600,7 @@ struct
val (buffer, searchList) = deleteSearchList val (buffer, searchList) = deleteSearchList
(low, length, searchString, searchList, buffer) (low, length, searchString, searchList, buffer)
in in
buildTextAndClear (app, buffer, low, searchList) Finish.buildTextAndClear (app, buffer, low, searchList)
end end
fun helpDeleteToChr fun helpDeleteToChr
@@ -757,16 +698,16 @@ struct
fun handleChr (app: app_type, count, chr, str) = fun handleChr (app: app_type, count, chr, str) =
case chr of case chr of
#"h" => move (app, count, Cursor.viH) #"h" => MoveViH.move (app, count)
| #"j" => move (app, count, Cursor.viJ) | #"j" => MoveViJ.move (app, count)
| #"k" => move (app, count, Cursor.viK) | #"k" => MoveViK.move (app, count)
| #"l" => move (app, count, Cursor.viL) | #"l" => MoveViL.move (app, count)
| #"w" => move (app, count, Cursor.nextWord) | #"w" => MoveToNextWord.move (app, count)
| #"W" => move (app, count, Cursor.nextWORD) | #"W" => MoveToNextWORD.move (app, count)
| #"b" => move (app, count, Cursor.prevWord) | #"b" => MoveToPrevWord.move (app, count)
| #"B" => move (app, count, Cursor.prevWORD) | #"B" => MoveToPrevWORD.move (app, count)
| #"e" => move (app, count, Cursor.endOfWord) | #"e" => MoveToEndOfWord.move (app, count)
| #"E" => move (app, count, Cursor.endOfWORD) | #"E" => MoveToEndOfWORD.move (app, count)
| #"z" => centreToCursor app | #"z" => centreToCursor app
(* can only move to start or end of line once (* can only move to start or end of line once
* so hardcode count as 1 *) * so hardcode count as 1 *)
@@ -790,11 +731,11 @@ struct
AppWith.mode (app, mode, []) AppWith.mode (app, mode, [])
end end
else else
move (app, 1, Cursor.vi0) MoveToStartOfLine.move (app, 1)
end end
else else
move (app, 1, Cursor.vi0) MoveToStartOfLine.move (app, 1)
| #"$" => move (app, 1, Cursor.viDlr) | #"$" => MoveToEndOfLine.move (app, 1)
| #"^" => firstNonSpaceChr app | #"^" => firstNonSpaceChr app
| #"G" => | #"G" =>
(* if str has a size larger than 0, (* if str has a size larger than 0,

37
fcore/finish.sml Normal file
View File

@@ -0,0 +1,37 @@
structure Finish =
struct
open AppType
fun buildTextAndClear (app: app_type, buffer, cursorIdx, searchList) =
let
val {windowWidth, windowHeight, startLine, searchString, ...} = app
(* move LineGap to first line displayed on screen
* and move searchList to line's start idx as well *)
val buffer = LineGap.goToLine (startLine, buffer)
(* get new startLine which may move screen depending on cursor movements *)
val startLine = TextWindow.getStartLine
(buffer, startLine, cursorIdx, windowWidth, windowHeight)
(* move buffer to new startLine as required by TextBuilder.build *)
val buffer = LineGap.goToLine (startLine, buffer)
val lineIdx = TextBuilder.getLineAbsIdx (startLine, buffer)
val searchList = SearchList.goToNum (lineIdx, searchList)
val drawMsg = TextBuilder.build
( startLine
, cursorIdx
, buffer
, windowWidth
, windowHeight
, searchList
, searchString
)
val mode = NORMAL_MODE ""
in
AppWith.bufferAndCursorIdx
(app, buffer, cursorIdx, mode, startLine, searchList, drawMsg)
end
end

60
fcore/move.sml Normal file
View File

@@ -0,0 +1,60 @@
signature MOVE =
sig
val fMove: LineGap.t * int -> int
end
signature MAKE_MOVE =
sig
val move: AppType.app_type * int -> AppType.app_type
end
functor MakeMove(Fn: MOVE): MAKE_MOVE =
struct
fun helpMove (app: AppType.app_type, buffer, cursorIdx, count) =
if count = 0 then
Finish.buildTextAndClear (app, buffer, cursorIdx, #searchList app)
else
(* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *)
let
val buffer = LineGap.goToIdx (cursorIdx, buffer)
val newCursorIdx = Fn.fMove (buffer, cursorIdx)
val newCursorIdx = Cursor.clipIdx (buffer, newCursorIdx)
val newCount =
(* it's possible to loop a very high number like 5432131
* which will take a long time because of the high number of loops
* regardless of the data structure used.
* If this happens, and the newCursorIdx is the same as the old one,
* then skip to end of loop by going to base case. *)
if cursorIdx = newCursorIdx then 0
else count - 1
in
helpMove (app, buffer, newCursorIdx, newCount)
end
fun move (app: AppType.app_type, count) =
let val {cursorIdx, buffer, ...} = app
in helpMove (app, buffer, cursorIdx, count)
end
end
structure MoveViH = MakeMove (struct val fMove = Cursor.viH end)
structure MoveViJ = MakeMove (struct val fMove = Cursor.viJ end)
structure MoveViK = MakeMove (struct val fMove = Cursor.viK end)
structure MoveViL = MakeMove (struct val fMove = Cursor.viL end)
structure MoveToNextWord = MakeMove (struct val fMove = Cursor.nextWord end)
structure MoveToNextWORD = MakeMove (struct val fMove = Cursor.nextWORD end)
structure MoveToEndOfWord = MakeMove (struct val fMove = Cursor.endOfWord end)
structure MoveToEndOfWORD = MakeMove (struct val fMove = Cursor.endOfWORD end)
structure MoveToPrevWord = MakeMove (struct val fMove = Cursor.prevWord end)
structure MoveToPrevWORD = MakeMove (struct val fMove = Cursor.prevWORD end)
structure MoveToEndOfPrevWord =
MakeMove (struct val fMove = Cursor.endOfPrevWord end)
structure MoveToEndOfPrevWORD =
MakeMove (struct val fMove = Cursor.endOfPrevWORD end)
structure MoveToStartOfLine = MakeMove (struct val fMove = Cursor.vi0 end)
structure MoveToEndOfLine = MakeMove (struct val fMove = Cursor.viDlr end)

View File

@@ -1,10 +1,10 @@
$(SML_LIB)/basis/basis.mlb $(SML_LIB)/basis/basis.mlb
(* FUNCTIONAL CORE *) (* LIBRARIEES *)
lib/brolib-sml/src/line_gap.sml lib/brolib-sml/src/line_gap.sml
lib/cozette-sml/fonts/cozette-ascii.mlb lib/cozette-sml/fonts/cozette-ascii.mlb
(* FUNCTIONAL CORE *)
message-types/input-msg.sml message-types/input-msg.sml
message-types/draw-msg.sml message-types/draw-msg.sml
message-types/mailbox-type.sml message-types/mailbox-type.sml
@@ -26,8 +26,9 @@ end
fcore/cursor.sml fcore/cursor.sml
fcore/text-window.sml fcore/text-window.sml
fcore/finish.sml
fcore/move.sml
fcore/app-update.sml fcore/app-update.sml
(* TEST FILES *) (* TEST FILES *)
test/Railroad/src/railroad.mlb test/Railroad/src/railroad.mlb

View File

@@ -26,6 +26,8 @@ end
fcore/cursor.sml fcore/cursor.sml
fcore/text-window.sml fcore/text-window.sml
fcore/finish.sml
fcore/move.sml
fcore/app-update.sml fcore/app-update.sml
(* IMPERATIVE SHELL *) (* IMPERATIVE SHELL *)