diff --git a/fcore/app-update.sml b/fcore/app-update.sml index 0648010..4e87b60 100644 --- a/fcore/app-update.sml +++ b/fcore/app-update.sml @@ -112,10 +112,19 @@ struct let val chr = String.sub (str, pos) in - if chr >= #"0" andalso chr <= #"9" then getNumLength (pos + 1, str) + if Char.isDigit chr then getNumLength (pos + 1, str) else pos end + fun appendChr (app: app_type, chr, str) = + let + val str = str ^ Char.toString chr + val mode = NORMAL_MODE str + val newApp = AppWith.mode (app, mode) + in + (newApp, []) + end + fun handleChr (app: app_type, count, chr, str) = case chr of #"h" => moveBackward (app, count, Cursor.viH) @@ -132,13 +141,23 @@ struct | #"0" => moveBackward (app, 1, Cursor.vi0) | #"$" => moveForwards (app, 1, Cursor.viDlr) | #"^" => firstNonSpaceChr app + (* multi-char commands which can be appended *) + | #"t" => appendChr (app, chr, str) + | #"T" => appendChr (app, chr, str) + | #"y" => appendChr (app, chr, str) + | #"d" => appendChr (app, chr, str) + | #"f" => appendChr (app, chr, str) + | #"F" => appendChr (app, chr, str) + | #"g" => appendChr (app, chr, str) + | #"c" => appendChr (app, chr, str) | _ => (* user may be entering a cmd with more than one chr * such as "2dw" to delete two word * so add current chr to mode, and save it in the app state *) let val str = - if chr >= #"0" andalso chr <= #"9" then str ^ Char.toString chr + if Char.isDigit chr + then str ^ Char.toString chr else "" val mode = NORMAL_MODE str val newApp = AppWith.mode (app, mode) @@ -146,7 +165,52 @@ struct (newApp, []) end + fun helpMoveToChrNext (app: app_type, buffer, cursorIdx, count, fMove, chr) = + if count = 0 then + let + val {windowWidth, windowHeight, startLine, ...} = app + (* todo: get new startLine if cursor has moved out of screen *) + + (* move LineGap to first line displayed on screen, and build new text *) + val buffer = LineGap.goToLine (startLine, buffer) + val drawMsg = TextBuilder.build + (startLine, cursorIdx, buffer, windowWidth, windowHeight) + + val mode = NORMAL_MODE "" + val newApp = AppWith.bufferAndCursorIdx (app, buffer, cursorIdx, mode) + in + (newApp, drawMsg) + end + else + let + (* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *) + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = fMove (buffer, cursorIdx, chr) + in + helpMoveToChrNext (app, buffer, cursorIdx, count - 1, fMove, chr) + end + + fun moveToChrNext (app: app_type, count, fMove, chr) = + let val {cursorIdx, buffer, ...} = app + in helpMoveToChrNext (app, buffer, cursorIdx, count, fMove, chr) + end + + (* temp placeholder function *) + fun clearMode app = + let + val mode = NORMAL_MODE "" + val newApp = AppWith.mode (app, mode) + in + (newApp, []) + end + + fun handleNextChr (count, app, fMove, newCmd) = + case newCmd of + CHAR_EVENT chr => moveToChrNext (app, count, fMove, chr) + | RESIZE_EVENT (width, height) => resizeText (app, width, height) + (* useful reference as list of non-terminal commands *) + (* todo: actually parse, checking if there are further strings or input *) fun parseAfterCount (strPos, str, count, app, newCmd) = (* we are trying to parse multi-char but non-terminal strings here. * For example, we don't want to parse 3w which is a terminal commmand @@ -154,43 +218,51 @@ struct * but we do want to parse 3d which is a non-terminal command * which can be made terminal by adding "w" or "e" at the end. * *) - case String.sub (str, strPos + 1) of + case String.sub (str, strPos) of #"t" => - (* to just before char, forward *) - 0 - | #"T" => - (* to just before chr, backward *) - 0 - | #"y" => - (* yank *) - 0 - | #"d" => - (* delete *) - 0 + (* to just before char, forward + * tillNextChr with count of 1 has same effect + * as tillNextChr with any count above 1 + * so just hardcode 1 *) + handleNextChr (1, app, Cursor.tillNextChr, newCmd) + | #"T" => + (* to just before chr, backward *) + clearMode app + | #"y" => + (* yank *) + clearMode app + | #"d" => + (* delete *) + clearMode app | #"f" => (* to chr, forward *) - 0 - | #"F" => - (* to chr, backward *) - 0 - | #"g" => - (* go *) - 0 - | #"c" => - (* change *) - 0 - | #"/" => - (* search *) - 0 + handleNextChr (count, app, Cursor.toNextChr, newCmd) + | #"F" => + (* to chr, backward *) + clearMode app + | #"g" => + (* go *) + clearMode app + | #"c" => + (* change *) + clearMode app | _ => - (* isn't a non-terminal cmd *) - 0 + (* isn't a non-terminal cmd + * this case should never happen*) + clearMode app fun parseNormalModeCommand (app, str, newCmd) = if String.size str = 0 then case newCmd of RESIZE_EVENT (width, height) => resizeText (app, width, height) | CHAR_EVENT chr => handleChr (app, 1, chr, str) + else if String.size str = 1 then + case newCmd of + RESIZE_EVENT (width, height) => resizeText (app, width, height) + | CHAR_EVENT chr => + (case Int.fromString str of + SOME count => handleChr (app, count, chr, str) + | NONE => parseAfterCount (0, str, 1, app, newCmd)) else let val numLength = getNumLength (0, str) @@ -205,10 +277,12 @@ struct case newCmd of RESIZE_EVENT (width, height) => resizeText (app, width, height) | CHAR_EVENT chr => handleChr (app, count, chr, str) + else if numLength + 1 < String.size str then + (* continue parsing. *) + parseAfterCount (numLength + 1, str, count, app, newCmd) else - (* todo: continue parsing. *) - (* parseAfterCount (numLength, str, count, app, newCmd) *) - raise Match + (* continue parsing. *) + parseAfterCount (numLength, str, count, app, newCmd) end fun updateNormalMode (app, str, msg) = parseNormalModeCommand (app, str, msg) diff --git a/fcore/cursor.sml b/fcore/cursor.sml index 9e5ed86..1b24ce5 100644 --- a/fcore/cursor.sml +++ b/fcore/cursor.sml @@ -1262,4 +1262,72 @@ struct | (_, _) => cursorIdx end + + fun helpNextChr (strPos, str, absIdx, stl, ltl, origIdx, findChr, fResult) = + if strPos = String.size str then + case (stl, ltl) of + (shd :: stl, lhd :: ltl) => + helpNextChr + (0, shd, absIdx, stl, ltl, origIdx, findChr, fResult) + | (_, _) => + origIdx + else + if String.sub (str, strPos) = findChr then + fResult absIdx + else + helpNextChr + (strPos + 1, str, absIdx + 1, stl, ltl, origIdx, findChr, fResult) + + fun startNextChr (shd, strIdx, absIdx, stl, ltl, findChr, fResult) = + (* we want to start iterating from next char after strIdx *) + if strIdx - 1 < String.size shd then + helpNextChr + (strIdx + 1, shd, absIdx + 1, stl, ltl, absIdx, findChr, fResult) + else + case (stl, ltl) of + (stlhd :: stltl, ltlhd :: ltltl) => + helpNextChr + (0, stlhd, absIdx + 1, stltl, ltltl, absIdx, findChr, fResult) + | (_, _) => + (* tl is empty; just return absIdx *) + absIdx + + fun nextChr (lineGap: LineGap.t, cursorIdx, chr, fResult) = + let + val {rightStrings, rightLines, idx = bufferIdx, ...} = lineGap + in + case (rightStrings, rightLines) of + (shd :: stl, lhd :: ltl) => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size shd then + (* strIdx is in this string *) + startNextChr + (shd, strIdx, cursorIdx, stl, ltl, chr, fResult) + else + (* strIdx is in tl *) + (case (stl, ltl) of + (stlhd :: stltl, ltlhd :: ltltl) => + let + val strIdx = strIdx - String.size shd + in + startNextChr + (shd, strIdx, cursorIdx, stl, ltl, chr, fResult) + end + | (_, _) => cursorIdx) + end + | (_, _) => cursorIdx + end + + fun tillNextChrResult absIdx = absIdx - 1 + + fun tillNextChr (lineGap, cursorIdx, chr) = + nextChr (lineGap, cursorIdx, chr, tillNextChrResult) + + fun toNextChrResult absIdx = absIdx + + fun toNextChr (lineGap, cursorIdx, chr) = + nextChr (lineGap, cursorIdx, chr, toNextChrResult) end diff --git a/shf b/shf index e9e83af..a34505d 100755 Binary files a/shf and b/shf differ