diff --git a/fcore/app-update.sml b/fcore/app-update.sml index c1f70fb..c686eaf 100644 --- a/fcore/app-update.sml +++ b/fcore/app-update.sml @@ -6,6 +6,14 @@ struct open DrawMsg open InputMsg + fun clearMode app = + let + val mode = NORMAL_MODE "" + val newApp = AppWith.mode (app, mode) + in + (newApp, []) + end + fun resizeText (app: app_type, newWidth, newHeight) = let val {buffer, windowWidth, windowHeight, startLine, cursorIdx, ...} = app @@ -170,8 +178,6 @@ struct then (* if visible, just need to redraw; no need to get line *) let - val buffer = LineGap.goToLine (startLine, buffer) - val newApp = AppWith.bufferAndCursorIdx (app, buffer, cursorIdx, NORMAL_MODE "", startLine) @@ -226,6 +232,72 @@ struct (newApp, drawMsg) end + (* equivalent of vi's 'x' command *) + fun deleteChr (app: app_type, count) = + let + val {buffer, cursorIdx, startLine, windowWidth, windowHeight, ...} = app + 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 *) + clearMode app + else if cursorIsStart then + let val _ = print "260\n" in + clearMode app end + else if nextIsEnd then + let + (* delete char at cursor and then decrement cursorIdx by 1 + * if cursorIdx is not 0 *) + val newBuffer = LineGap.delete (cursorIdx, 1, buffer) + val cursorIdx = + if Cursor.isPrevChrStartOfLine (newBuffer, cursorIdx) + orelse cursorIdx = 0 then + cursorIdx + else cursorIdx - 1 + + val newBuffer = LineGap.goToLine (startLine, newBuffer) + val newApp = AppWith.bufferAndCursorIdx + (app, newBuffer, cursorIdx, NORMAL_MODE "", startLine) + + val drawMsg = + TextBuilder.build + (startLine, cursorIdx, newBuffer, windowWidth, windowHeight) + in + (newApp, drawMsg) + end + else if Cursor.isPrevChrStartOfLine (buffer, cursorIdx) then + clearMode app + else + let + val newBuffer = LineGap.delete (cursorIdx, 1, buffer) + + val newBuffer = LineGap.goToLine (startLine, newBuffer) + val newApp = AppWith.bufferAndCursorIdx + (app, newBuffer, cursorIdx, NORMAL_MODE "", startLine) + + val drawMsg = + TextBuilder.build + (startLine, cursorIdx, newBuffer, windowWidth, windowHeight) + in + (newApp, drawMsg) + end + end + (* number of characters which are integers *) fun getNumLength (pos, str) = if pos = String.size str then @@ -295,6 +367,7 @@ struct else moveToLine (app, count - 1) | #"%" => moveToMatchingPair app + | #"x" => deleteChr (app, count) (* multi-char commands which can be appended *) | #"t" => appendChr (app, chr, str) | #"T" => appendChr (app, chr, str) @@ -355,15 +428,6 @@ struct in helpMoveToChr (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 handleMoveToChr (count, app, fMove, newCmd) = case newCmd of CHAR_EVENT chr => moveToChr (app, count, fMove, chr) diff --git a/fcore/cursor.sml b/fcore/cursor.sml index 7334802..4b7eaeb 100644 --- a/fcore/cursor.sml +++ b/fcore/cursor.sml @@ -31,7 +31,8 @@ struct cursorIdx else (* not at newline so start iterating *) - helpVi0 (strIdx - 1, strHd, cursorIdx - 1, strTl, lnTl) + helpVi0 + (strIdx - 1, strHd, cursorIdx - 1, leftStrings, leftLines) else (* strIdx must be in the strTl *) (case (strTl, lnTl) of @@ -977,18 +978,18 @@ struct (case (stl, ltl) of (stlhd :: stltl, ltlhd :: ltltl) => let val strIdx = strIdx - String.size shd - in fNext (strIdx, shd, cursorIdx, stltl, ltltl) + in fNext (strIdx, stlhd, cursorIdx, stltl, ltltl) end | (_, _) => cursorIdx) end | (_, _) => cursorIdx end - (* equivalent 'f vi's 'w' command *) + (* equivalent of vi's 'w' command *) fun nextWord (lineGap, cursorIdx) = toNextWord (lineGap, cursorIdx, helpNextWord) - (* equivalent 'f vi's 'W' command *) + (* equivalent of vi's 'W' command *) fun nextWORD (lineGap, cursorIdx) = toNextWord (lineGap, cursorIdx, helpNextWORD) @@ -1742,4 +1743,97 @@ struct | (_, _) => 0) end end + + (* Prerequisite: lineGap is moved to cursorIdx *) + fun isCursorAtStartOfLine (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, ...} = lineGap + in + case rightStrings of + hd :: tl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size hd then + (* chr is in hd *) + String.sub (hd, strIdx) = #"\n" + else + (* chr is in tl *) + (case tl of + tlhd :: _ => + let + val strIdx = strIdx - String.size hd + in + String.sub (tlhd, strIdx) = #"\n" + end + | [] => true) + end + | [] => true + end + + (* Prerequisite: lineGap is moved to cursorIdx *) + fun isPrevChrStartOfLine (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, leftStrings, ...} = lineGap + in + case rightStrings of + hd :: tl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx > 0 then + (* prev chr is in hd *) + String.sub (hd, strIdx - 1) = #"\n" + else + (* prev chr if in leftStrings *) + (case leftStrings of + lhd :: _ => + String.sub (lhd, String.size lhd - 1) = #"\n" + | [] => + (* cursorIdx = 0 which means we are at start of file/line *) + true) + end + | [] => + true + end + + fun helpIsNextChrEndOfLine (strIdx, hd, tl) = + if strIdx + 1 < String.size hd then + (* next chr is in this string *) + String.sub (hd, strIdx + 1) = #"\n" + else + (* next chr, if it exists, is in tl *) + (case tl of + tlhd :: _ => + String.sub (tlhd, 0) = #"\n" + | [] => + true) + + (* Prerequisite: lineGap is moved to cursorIdx *) + fun isNextChrEndOfLine (lineGap: LineGap.t, cursorIdx) = + let + val {rightStrings, idx = bufferIdx, ...} = lineGap + in + case rightStrings of + hd :: tl => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size hd then + helpIsNextChrEndOfLine (strIdx, hd, tl) + else + (* strIdx is in tl *) + (case tl of + tlhd :: tltl => + helpIsNextChrEndOfLine (strIdx, tlhd, tltl) + | [] => + (* strIdx is at end of lineGap + * which also means at end of line *) + true) + end + | [] => true + end end diff --git a/shf b/shf index 018d78d..9e114c6 100755 Binary files a/shf and b/shf differ