diff --git a/fcore/app-type.sml b/fcore/app-type.sml index ef957c0..c06b18a 100644 --- a/fcore/app-type.sml +++ b/fcore/app-type.sml @@ -8,9 +8,6 @@ struct , startLine: int (* absolute index of movable cursor *) , cursorIdx: int - (* when moving cursor up or down a line, - * move to this column if possible *) - , preferredColumn: int } fun init (buffer, windowWidth, windowHeight) : app_type = @@ -19,6 +16,5 @@ struct , windowHeight = windowHeight , startLine = 0 , cursorIdx = 0 - , preferredColumn = 0 } end diff --git a/fcore/app-update.sml b/fcore/app-update.sml index 155fcaa..6d87071 100644 --- a/fcore/app-update.sml +++ b/fcore/app-update.sml @@ -26,15 +26,13 @@ struct (* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *) val buffer = LineGap.goToIdx (cursorIdx, buffer) val cursorIdx = Cursor.viL (buffer, cursorIdx) - val preferredColumn = Cursor.getCursorColumn (buffer, cursorIdx) (* 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 newApp = - AppWith.bufferAndCursorIdx (app, buffer, cursorIdx, preferredColumn) + val newApp = AppWith.bufferAndCursorIdx (app, buffer, cursorIdx) in (newApp, drawMsg) end @@ -45,14 +43,28 @@ struct val buffer = LineGap.goToIdx (cursorIdx, buffer) val cursorIdx = Cursor.viH (buffer, cursorIdx) - val preferredColumn = Cursor.getCursorColumn (buffer, cursorIdx) val buffer = LineGap.goToLine (startLine, buffer) val drawMsg = TextBuilder.build (startLine, cursorIdx, buffer, windowWidth, windowHeight) - val newApp = - AppWith.bufferAndCursorIdx (app, buffer, cursorIdx, preferredColumn) + val newApp = AppWith.bufferAndCursorIdx (app, buffer, cursorIdx) + in + (newApp, drawMsg) + end + + fun moveDown (app: app_type) = + let + val {buffer, windowWidth, windowHeight, startLine, cursorIdx, ...} = app + + val buffer = LineGap.goToIdx (cursorIdx, buffer) + val cursorIdx = Cursor.viJ (buffer, cursorIdx) + + val buffer = LineGap.goToLine (startLine, buffer) + val drawMsg = TextBuilder.build + (startLine, cursorIdx, buffer, windowWidth, windowHeight) + + val newApp = AppWith.bufferAndCursorIdx (app, buffer, cursorIdx) in (newApp, drawMsg) end @@ -60,6 +72,7 @@ struct fun handleChr (app: app_type, chr) = case chr of #"h" => moveLeft app + | #"j" => moveDown app | #"l" => moveRight app | _ => (app, []) diff --git a/fcore/app-with.sml b/fcore/app-with.sml index 50b1ca2..25c6c51 100644 --- a/fcore/app-with.sml +++ b/fcore/app-with.sml @@ -4,38 +4,24 @@ struct fun bufferAndSize (app: app_type, newBuffer, newWidth, newHeight) = let - val - { buffer = _ - , windowWidth = _ - , windowHeight = _ - , startLine - , cursorIdx - , preferredColumn - } = app + val {buffer = _, windowWidth = _, windowHeight = _, startLine, cursorIdx} = + app in { buffer = newBuffer , windowWidth = newWidth , windowHeight = newHeight , startLine = startLine , cursorIdx = cursorIdx - , preferredColumn = preferredColumn } end - fun bufferAndCursorIdx (app: app_type, newBuffer, newCursorIdx, newColumn) = + fun bufferAndCursorIdx (app: app_type, newBuffer, newCursorIdx) = let - val - { buffer = _ - , cursorIdx = _ - , preferredColumn = _ - , windowWidth - , windowHeight - , startLine - } = app + val {buffer = _, cursorIdx = _, windowWidth, windowHeight, startLine} = + app in { buffer = newBuffer , cursorIdx = newCursorIdx - , preferredColumn = newColumn , windowWidth = windowWidth , windowHeight = windowHeight , startLine = startLine diff --git a/fcore/cursor.sml b/fcore/cursor.sml index a741d3f..ffc471d 100644 --- a/fcore/cursor.sml +++ b/fcore/cursor.sml @@ -291,4 +291,168 @@ struct end | (_, _) => helpGetCursorColumn (0, leftStrings, leftLines) end + + fun helpViJString + ( strPos, str, absIdx + , lineColumn, preferredColumn, hasPassedLine + , strTl, lineTl + ) = + if strPos = String.size str then + helpViJList + (absIdx, lineColumn, preferredColumn, hasPassedLine, strTl, lineTl) + else + case String.sub (str, strPos) of + #"\n" => + if + hasPassedLine + then + (* reached end of line twice, + * but line has fewer chars than preferredColumn + * note: if in double \n\n linebreak, + * then return absIdx of second linebreak. *) + if strPos = String.size str - 1 then + if String.sub (str, strPos + 1) = #"\n" then + (* we are in double linebreak *) + absIdx + 1 + else + (* not in double linebreak *) + absIdx - 1 + else + (* this is last chr of string; must check string's tl next *) + (case strTl of + hd :: tl => + if String.sub (hd, 0) = #"\n" then + (* in double linebreak *) + absIdx + 1 + else + (* not in double linebreak *) + absIdx - 1 + | [] => + (* no more strings so return last idx *) + absIdx - 1) + else + (* reached end of line once; + * have to check if this is a double linebreak, + * and return idx of second linebreak if so *) + if strPos < String.size str - 1 then + if String.sub (str, strPos + 1) = #"\n" then + absIdx + 1 + else + helpViJString + ( strPos + 1, str, absIdx + 1 + , 0, preferredColumn, true + , strTl, lineTl + ) + else + (* this is last chr of string; must check string's tl next *) + (case strTl of + hd :: tl => + if String.sub (hd, 0) = #"\n" then + (* in double linebreak *) + absIdx + 1 + else + (* not in double linebreak *) + helpViJString + ( strPos + 1, str, absIdx + 1 + , 0, preferredColumn, true + , strTl, lineTl + ) + | [] => + (* no more strings so return last idx *) + absIdx) + | _ => + if lineColumn <> preferredColumn orelse not hasPassedLine then + (* we're not in the preferred column, so keep iterating *) + helpViJString + ( strPos + 1, str, absIdx + 1 + , lineColumn + 1, preferredColumn, hasPassedLine + , strTl, lineTl + ) + else + (* we're at the preferredColumn so return absIdx *) + absIdx + + and helpViJList + (absIdx, lineColumn, preferredColumn, hasPassedLine, strings, lines) = + case (strings, lines) of + (strHd :: strTl, lineHd :: lineTl) => + (* todo: possibly check if we have passed line, + * and if so, if there are any line breaks in the lineHd + * which we could use to skip searching part of the string. + * However, this will likely have worse cache locality + * as we switch to searching in string to searcing in line vector + * so perhaps not. *) + helpViJString + ( 0, strHd, absIdx + , lineColumn, preferredColumn, hasPassedLine + , strTl, lineTl + ) + | (_, _) => + (* empty, so return end of previous string *) + absIdx - 1 + + fun viJ (lineGap: LineGap.t, cursorIdx) = + let + val + {rightStrings, idx = bufferIdx, rightLines, leftStrings, leftLines, ...} = + lineGap + in + case (rightStrings, rightLines) of + (strHd :: strTl, lnHd :: lnTl) => + let + (* convert absolute cursorIdx to idx relative to hd string *) + val strIdx = cursorIdx - bufferIdx + in + if strIdx < String.size strHd then + (* strIdx is in this string *) + if String.sub (strHd, strIdx) = #"\n" then + (* if we are at a newline + * note: + * don't need to check if we are a double linebreak, + * because cursor navigation functions already check + * for that condition at the end. + * So there is no way at the start of a navigation function + * that cursor is in a double linebreak incorrectly. *) + helpViJString + (strIdx + 1, strHd, cursorIdx + 1, 0, 0, true, strTl, lnTl) + else + (* not at newline + * so get column number and start iterating *) + let + val lineColumn = getCursorColumn (lineGap, cursorIdx) + in + helpViJString + ( strIdx + 1, strHd, cursorIdx + 1 + , lineColumn, lineColumn, false + , strTl, lnTl + ) + end + else + (* strIdx must be in the strTl *) + (case (strTl, lnTl) of + (nestStrHd :: nestStrTl, nestLnHd :: nestLnTl) => + let + val strIdx = strIdx - String.size strHd + in + if String.sub (nestStrHd, strIdx) = #"\n" then + helpViJString + (strIdx + 1, strHd, cursorIdx + 1, 0, 0, true, strTl, lnTl) + else + (* not in linebreak *) + let + val lineColumn = getCursorColumn (lineGap, cursorIdx) + in + helpViJString + ( strIdx + 1, strHd, cursorIdx + 1 + , lineColumn, lineColumn, false + , strTl, lnTl + ) + end + end + | (_, _) => cursorIdx) + end + | (_, _) => + (* nowhere to go rightward, so return cursorIdx *) + cursorIdx + end end diff --git a/shf b/shf index 0f3bf03..fc88bc5 100755 Binary files a/shf and b/shf differ