From 18d257960578ab0c1894cb8bc53a098f384ab16f Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Sat, 13 Sep 2025 18:15:30 +0100 Subject: [PATCH] remove todo-exception from 'G' (NormalMove.goToEnd) command. The todo was a reminder to implement a way to get the scroll line when moving to the end. We have that implemented now. --- fcore/normal-mode/normal-move.sml | 4 +- temp.txt | 492 ++++++++++++++++++++++++++++++ 2 files changed, 495 insertions(+), 1 deletion(-) diff --git a/fcore/normal-mode/normal-move.sml b/fcore/normal-mode/normal-move.sml index 0f51027..cbb6293 100644 --- a/fcore/normal-mode/normal-move.sml +++ b/fcore/normal-mode/normal-move.sml @@ -58,6 +58,7 @@ struct , searchString , bufferModifyTime , visualScrollColumn = prevScrollColumn + , startLine = prevLineNumber , ... } = app @@ -72,8 +73,9 @@ struct val visualScrollColumn = TextScroll.getScrollColumn (buffer, bufferIdx, windowWidth, prevScrollColumn) - val bufferLine = let in raise Fail "todo: fix" end + val bufferLine = + TextScroll.getStartLine (prevLineNumber, bufferLine, windowHeight) val buffer = LineGap.goToLine (bufferLine, buffer) val drawMsg = NormalModeTextBuilder.build diff --git a/temp.txt b/temp.txt index e92cac1..e9e1990 100644 --- a/temp.txt +++ b/temp.txt @@ -39,3 +39,495 @@ struct val vec = #[ left, top, r, g, b , right, top, r, g, b + , left, bottom, r, g, b + + , left, bottom, r, g, b + , right, bottom, r, g, b + , right, top, r, g, b + ] + in + [vec] + end + + (* builds text from a string with char-wrap. + * char-wrap is a similar concept to word-wrap, + * but it breaks on character in the middle of a word. + * + * Will likely want multiple versions of these two mutually recursive + * functions for each selection and cursor type: + * cursor over an individual character, + * range selection where multiple characters are selected, etc. + * + * Todo: + * - Possibly add visual horizontal indentation when char-wrap occurs + * on an indented line *) + fun buildTextStringAfterCursor + ( pos, str, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) = + if pos < String.size str then + case String.sub (str, pos) of + #" " => + (* if space, then proceed forward one char + * without adding to acc *) + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + | #"\t" => + (* if tab, proceed forward one char, + * and jump visually forwards three chars *) + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace3, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + | #"\n" => + (* if \n, move down vertically, and move to start horizontally + * assuming we have not exceeded the window's height. + * If we have exceeded the window's height, just return acc. *) + if posY + ySpace < windowHeight then + buildTextStringAfterCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + else + (* return if there is no more vertical space after line break *) + accToDrawMsg (acc, cursorAcc) + | #"\r" => + (* same as \n, except we also check if we are in a \r\n pair, + * and proceed two characters forward if so *) + if posY + ySpace < windowHeight then + if + pos < String.size str - 1 + andalso String.sub (str, pos + 1) = #"\n" + then + buildTextStringAfterCursor + ( pos + 2, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + else + buildTextStringAfterCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + else + (* return if there is no more vertical space after line break *) + accToDrawMsg (acc, cursorAcc) + | chr => + (* for any other character, add it to acc if there is space, + * and proceed forward one character in the string *) + let + val chrFun = Vector.sub (CozetteAscii.asciiTable, Char.ord chr) + in + if posX + xSpace < windowWidth then + (* if there is horizontal space, place char on the right *) + let + val chrVec = chrFun + (posX, posY, fontSize, fontSize, fWindowWidth, fWindowHeight, r, g, b) + val acc = chrVec :: acc + in + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else if posY + ySpace < windowHeight then + (* if there is vertical space, place char down below at startX *) + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringAfterCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else + (* return if no space horizontally or vertically *) + accToDrawMsg (acc, cursorAcc) + end + else + (* if we reached the end of the string, + * call function to build next string *) + continueBuildTextLineGapAfterCursor + ( tl, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, cursorAcc + ) + + and continueBuildTextLineGapAfterCursor + ( strList, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, cursorAcc + ) = + case strList of + hd :: tl => + buildTextStringAfterCursor + ( 0, hd, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + | [] => accToDrawMsg (acc, cursorAcc) + + (* same as buildTextStringAfterCursor, except this keeps track of absolute + * index and cursor pos too *) + fun buildTextStringBeforeCursor + ( pos, str, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, hr, hg, hb + ) = + if pos < String.size str then + case String.sub (str, pos) of + #" " => + buildTextStringBeforeCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + | #"\t" => + buildTextStringBeforeCursor + ( pos + 1, str, acc, posX + xSpace3, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + | #"\n" => + if posY + ySpace < windowHeight then + buildTextStringBeforeCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + else + accToDrawMsg (acc, []) + | #"\r" => + if posY + ySpace < windowHeight then + if + pos < String.size str - 1 + andalso String.sub (str, pos + 1) = #"\n" + then + buildTextStringBeforeCursor + ( pos + 2, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 2, cursorPos, hr, hg, hb + ) + else + buildTextStringBeforeCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + else + accToDrawMsg (acc, []) + | chr => + let + val chrFun = Vector.sub (CozetteAscii.asciiTable, Char.ord chr) + in + if posX + xSpace < windowWidth then + let + val chrVec = chrFun + (posX, posY, fontSize, fontSize, fWindowWidth, fWindowHeight, r, g, b) + val acc = chrVec :: acc + in + buildTextStringBeforeCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + end + else if posY + ySpace < windowHeight then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringBeforeCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, hr, hg, hb + ) + end + else + accToDrawMsg (acc, []) + end + else + continueBuildTextLineGapBeforeCursor + ( tl, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, absIdx, cursorPos, hr, hg, hb + ) + + and buildTextStringWithinCursor + ( pos, str, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, cursorAcc, hr, hg, hb + ) = + if pos < String.size str then + case String.sub (str, pos) of + #" " => + (* if inside cursor, then create cursorAcc; + * else, just skip as usual *) + if absIdx <> cursorPos then + (* not in cursur *) + buildTextStringWithinCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + else + (* in cursor *) + let + val cursorAcc = buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g ,b) + in + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + | #"\t" => + (* todo: draw cursor if cursor is on tab + * but this is not a priority right now *) + buildTextStringWithinCursor + ( pos + 1, str, acc, posX + xSpace3, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + | #"\n" => + if posY + ySpace < windowHeight then + if absIdx <> cursorPos then + (* not in cursor position, so iterate like normal *) + buildTextStringWithinCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + else + (* in cursor position, so build cursorAcc + * and call AfterCursor function *) + if pos = String.size str - 1 andalso tl = [] then + (* if we are at end of lineGap, we want to build cursorAcc + * at different coordinates than usual *) + let + val cursorAcc = + buildCursor (startX, posY + ySpace, fWindowWidth, fWindowHeight, r, g, b) + in + accToDrawMsg (acc, cursorAcc) + end + else + let + val cursorAcc = buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g ,b) + in + buildTextStringAfterCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else + accToDrawMsg (acc, cursorAcc) + | #"\r" => + if posY + ySpace < windowHeight then + if + pos < String.size str - 1 + andalso String.sub (str, pos + 1) = #"\n" + then + buildTextStringWithinCursor + ( pos + 2, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 2, cursorPos, cursorAcc, hr, hg, hb + ) + else + buildTextStringWithinCursor + ( pos + 1, str, acc, startX, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + else + accToDrawMsg (acc, cursorAcc) + | chr => + let + val chrFun = Vector.sub (CozetteAscii.asciiTable, Char.ord chr) + in + if absIdx <> cursorPos then + (* not equal to cursor *) + if posX + xSpace < windowWidth then + let + val chrVec = chrFun + (posX, posY, fontSize, fontSize, fWindowWidth, fWindowHeight, r, g, b) + val acc = chrVec :: acc + in + buildTextStringWithinCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + end + else if posY + ySpace < windowHeight then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringWithinCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx + 1, cursorPos, cursorAcc, hr, hg, hb + ) + end + else + accToDrawMsg (acc, cursorAcc) + else + (* equal to cursor *) + let + val cursorAcc = buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g ,b) + in + if posX + xSpace < windowWidth then + let + val chrVec = chrFun + ( posX, posY, fontSize, fontSize + , fWindowWidth, fWindowHeight + , hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextStringAfterCursor + ( pos + 1, str, acc, posX + xSpace, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else if posY + ySpace < windowHeight then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fWindowWidth, fWindowHeight + , hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextStringAfterCursor + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, cursorAcc + ) + end + else + accToDrawMsg (acc, cursorAcc) + end + end + + else + (* we have built cursor now, so can call after-cursor function + * to build rest of text *) + continueBuildTextLineGapAfterCursor + ( tl, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, cursorAcc + ) + + and continueBuildTextLineGapBeforeCursor + ( strList, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, absIdx, cursorPos, hr, hg, hb + ) = + case strList of + hd :: tl => + if cursorPos >= absIdx + cursorPos then + (* if end of string is before cursor *) + buildTextStringBeforeCursor + ( 0, hd, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, hr, hg, hb + ) + else + (* if within cursor *) + buildTextStringWithinCursor + ( 0, hd, acc, posX, posY, startX + , windowWidth, windowHeight, fWindowWidth, fWindowHeight + , r, g, b, tl, absIdx, cursorPos, [], hr, hg, hb + ) + | [] => accToDrawMsg (acc, []) + + fun build + (startLine, cursorPos, lineGap: LineGap.t, windowWidth, windowHeight) = + let + val lineGap = LineGap.goToLine (startLine, lineGap) + val {rightStrings, rightLines, line = curLine, idx = curIdx, ...} = lineGap + in + case (rightStrings, rightLines) of + (rStrHd :: rStrTl, rLnHd :: _) => + let + (* get index of line to start building from *) + val startIdx = + if startLine > curLine then + let + val lnPos = startLine - curLine - 1 + val startIdx = Vector.sub (rLnHd, lnPos) + in + if + String.sub (rStrHd, startIdx) = #"\r" + andalso startIdx < String.size rStrHd - 1 + andalso String.sub (rStrHd, startIdx + 1) = #"\n" + then + (* handle \r\n pair *) + startIdx + 2 + else startIdx + 1 + end + else + 0 + + val absIdx = curIdx + startIdx + + in + if cursorPos < curIdx + String.size rStrHd then + (* if cursor is within string *) + buildTextStringWithinCursor + ( startIdx, rStrHd, [] + , 5, 5, 5 + , windowWidth, windowHeight + , Real32.fromInt windowWidth, Real32.fromInt windowHeight + , 0.67, 0.51, 0.83 + , rStrTl, absIdx, cursorPos, [] + , 0.211, 0.219, 0.25 + ) + else + (* if cursor is after string *) + buildTextStringBeforeCursor + ( startIdx, rStrHd, [] + , 5, 5, 5 + , windowWidth, windowHeight + , Real32.fromInt windowWidth, Real32.fromInt windowHeight + , 0.67, 0.51, 0.83 + , rStrTl, absIdx, cursorPos + , 0.211, 0.219, 0.25 + ) + end + | (_, _) => + (* requested line goes beyond the buffer, + * so just return empty list as there is nothig + * else we can do. *) + [] + end +end