From 2b3f0405c60f46efe65971a32a2f0ca83e059634 Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Sat, 30 Aug 2025 23:31:55 +0100 Subject: [PATCH] reimplement search-range functionality to scan range from the string from left to right --- fcore/normal-mode/normal-delete.sml | 36 +- fcore/search-list.sml | 160 ++++---- shell/shell.sml | 2 +- temp.txt | 543 +++++++++++++++++++++++++++- 4 files changed, 627 insertions(+), 114 deletions(-) diff --git a/fcore/normal-mode/normal-delete.sml b/fcore/normal-mode/normal-delete.sml index 0e290d5..1467155 100644 --- a/fcore/normal-mode/normal-delete.sml +++ b/fcore/normal-mode/normal-delete.sml @@ -11,8 +11,8 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (low + 777, buffer) - val searchList = SearchList.buildRange (buffer, searchString, low - 777) + val buffer = LineGap.goToIdx (low - 777, buffer) + val searchList = SearchList.buildRange (buffer, searchString, low + 777) val buffer = LineGap.goToIdx (low, buffer) in @@ -27,9 +27,9 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (cursorIdx + 777, buffer) + val buffer = LineGap.goToIdx (cursorIdx - 777, buffer) val searchList = - SearchList.buildRange (buffer, searchString, cursorIdx - 777) + SearchList.buildRange (buffer, searchString, cursorIdx + 777) in Finish.buildTextAndClear (app, buffer, cursorIdx, searchList, initialMsg, time) @@ -109,9 +109,9 @@ struct val searchString = #searchString app val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (cursorIdx + 777, buffer) + val buffer = LineGap.goToIdx (cursorIdx - 777, buffer) val searchList = - SearchList.buildRange (buffer, searchString, cursorIdx - 777) + SearchList.buildRange (buffer, searchString, cursorIdx + 777) (* If we have deleted from the buffer so that cursorIdx * is no longer a valid idx, @@ -296,9 +296,9 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, #searchString app)] - val buffer = LineGap.goToIdx (cursorIdx + 777, buffer) + val buffer = LineGap.goToIdx (cursorIdx - 777, buffer) val searchList = - SearchList.buildRange (buffer, searchString, cursorIdx - 777) + SearchList.buildRange (buffer, searchString, cursorIdx + 777) val cursorIdx = 0 val startLine = 0 @@ -330,8 +330,8 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (low + 777, buffer) - val searchList = SearchList.buildRange (buffer, searchString, low - 777) + val buffer = LineGap.goToIdx (low - 777, buffer) + val searchList = SearchList.buildRange (buffer, searchString, low + 777) val buffer = LineGap.goToIdx (low, buffer) in @@ -377,9 +377,9 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (low + 777, buffer) + val buffer = LineGap.goToIdx (low - 777, buffer) val searchList = - SearchList.buildRange (buffer, searchString, low - 777) + SearchList.buildRange (buffer, searchString, low + 777) val buffer = LineGap.goToIdx (low, buffer) in @@ -405,9 +405,9 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (low + 777, buffer) + val buffer = LineGap.goToIdx (low - 777, buffer) val searchList = - SearchList.buildRange (buffer, searchString, low - 777) + SearchList.buildRange (buffer, searchString, low + 777) val buffer = LineGap.goToIdx (low, buffer) in @@ -429,8 +429,8 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (low + 777, buffer) - val searchList = SearchList.buildRange (buffer, searchString, low - 777) + val buffer = LineGap.goToIdx (low - 777, buffer) + val searchList = SearchList.buildRange (buffer, searchString, low + 777) val buffer = LineGap.goToIdx (origLow, buffer) in @@ -524,9 +524,9 @@ struct val buffer = LineGap.goToStart buffer val initialMsg = [SEARCH (buffer, searchString)] - val buffer = LineGap.goToIdx (low + 777, buffer) + val buffer = LineGap.goToIdx (low - 777, buffer) val searchList = - SearchList.buildRange (buffer, searchString, low - 777) + SearchList.buildRange (buffer, searchString, low + 777) val buffer = LineGap.goToIdx (low, buffer) in diff --git a/fcore/search-list.sml b/fcore/search-list.sml index 8658719..395e80c 100644 --- a/fcore/search-list.sml +++ b/fcore/search-list.sml @@ -8,7 +8,7 @@ struct if pos = String.size hd then case tl of hd :: tl => loopSearch (0, hd, absIdx, tl, acc, searchPos, searchString) - | [] => acc + | [] => PersistentVector.toVector acc else let val bufferChr = String.sub (hd, pos) @@ -26,28 +26,83 @@ struct else loopSearch (pos + 1, hd, absIdx + 1, tl, acc, searchPos + 1, searchString) - else if searchPos = 0 then - loopSearch (pos + 1, hd, absIdx + 1, tl, acc, 0, searchString) else - loopSearch (pos, hd, absIdx, tl, acc, 0, searchString) + (if searchPos = 0 then + loopSearch (pos + 1, hd, absIdx + 1, tl, acc, 0, searchString) + else + loopSearch (pos, hd, absIdx, tl, acc, 0, searchString)) end fun search ({rightStrings, leftStrings, ...}: LineGap.t, searchString) = case rightStrings of hd :: tl => - let - val result = loopSearch - (0, hd, 0, tl, PersistentVector.empty, 0, searchString) - in - PersistentVector.toVector result - end + loopSearch (0, hd, 0, tl, PersistentVector.empty, 0, searchString) | [] => empty - (* Prerequisite: move buffer/LineGap to end *) + (* Prerequisite: move buffer/LineGap to start *) fun build (buffer, searchString) = if String.size searchString > 0 then search (buffer, searchString) else empty + fun loopRange (pos, hd, absIdx, tl, acc, searchPos, searchString, finish) = + if pos = String.size hd then + case tl of + hd :: tl => + loopRange (0, hd, absIdx, tl, acc, searchPos, searchString, finish) + | [] => PersistentVector.toVector acc + else if absIdx = finish then + PersistentVector.toVector acc + else + let + val bufferChr = String.sub (hd, pos) + val searchChr = String.sub (searchString, searchPos) + in + if bufferChr = searchChr then + if searchPos + 1 = String.size searchString then + (* full match *) + let + val foundIdx = absIdx - String.size searchString + 1 + val acc = PersistentVector.append (foundIdx, acc) + in + loopRange + (pos + 1, hd, absIdx + 1, tl, acc, 0, searchString, finish) + end + else + loopRange + ( pos + 1 + , hd + , absIdx + 1 + , tl + , acc + , searchPos + 1 + , searchString + , finish + ) + else + ((if searchPos = 0 then + loopRange + (pos + 1, hd, absIdx + 1, tl, acc, 0, searchString, finish) + else + loopRange (pos, hd, absIdx, tl, acc, 0, searchString, finish))) + end + + fun searchRange (buffer: LineGap.t, searchString, finish) = + let + val {rightStrings, idx = absIdx, ...} = buffer + in + case rightStrings of + hd :: tl => + loopRange + (0, hd, absIdx, tl, PersistentVector.empty, 0, searchString, finish) + | [] => empty + end + + fun buildRange (buffer, searchString, finish) = + if String.size searchString > 0 then + searchRange (buffer, searchString, finish) + else + empty + fun loopNextMatch (pos, searchList, count) = if count = 0 then Vector.sub (searchList, pos) @@ -96,87 +151,4 @@ struct loopPrevMatch (pos, searchList, count) end - fun rangeSearchStep (pos, hd, absIdx, tl, acc, searchPos, searchString, low) = - if searchPos < 0 then - raise Fail "todo" - else if absIdx < low then - acc - else if pos < 0 then - case tl of - hd :: tl => - rangeSearchStep - ( String.size hd - 1 - , hd - , absIdx - , tl - , acc - , searchPos - , searchString - , low - ) - | [] => acc - else - let - val bufferChr = String.sub (hd, pos) - val searchChr = String.sub (searchString, searchPos) - in - if bufferChr = searchChr then - rangeSearchStep - (pos - 1, hd, absIdx - 1, tl, acc, searchPos - 1, searchString, low) - else - acc - end - - fun loopRange (pos, hd, absIdx, tl, acc, searchString, low) = - if absIdx < low then - Vector.fromList acc - else if pos < 0 then - case tl of - hd :: tl => - loopRange (String.size hd - 1, hd, absIdx, tl, acc, searchString, low) - | [] => Vector.fromList acc - else - let - val acc = rangeSearchStep - ( pos - , hd - , absIdx - , tl - , acc - , String.size searchString - 1 - , searchString - , low - ) - in - loopRange (pos - 1, hd, absIdx - 1, tl, acc, searchString, low) - end - - fun searchRange (buffer: LineGap.t, searchString, low) = - let - val low = Int.max (low, 0) - val {rightStrings, leftStrings, idx = absIdx, ...} = buffer - in - case rightStrings of - hd :: _ => - let - val pos = String.size hd - 1 - val absIdx = absIdx + String.size hd - 1 - in - loopRange (pos, hd, absIdx, leftStrings, [], searchString, low) - end - | [] => - (case leftStrings of - hd :: tl => - let - val pos = String.size hd - 1 - val absIdx = absIdx - 1 - in - loopRange (pos, hd, absIdx, tl, [], searchString, low) - end - | [] => empty) - end - - fun buildRange (buffer, searchString, low) = - if String.size searchString > 0 then searchRange (buffer, searchString, low) - else empty end diff --git a/shell/shell.sml b/shell/shell.sml index 4bc14dd..0d6d1d1 100644 --- a/shell/shell.sml +++ b/shell/shell.sml @@ -69,7 +69,7 @@ struct let val buffer = #buffer app val buffer = LineGap.goToStart buffer - val searchString = "abba" + val searchString = "val " val searchList = SearchList.build (buffer, searchString) val buffer = LineGap.goToStart buffer in diff --git a/temp.txt b/temp.txt index 35f2bf3..d6ea8a2 100644 --- a/temp.txt +++ b/temp.txt @@ -1 +1,542 @@ -abbabbabbabbabba +signature TEXT_BUILDER = +aaron baron carrot durian + (* Prerequisite: LineGap is moved to requested line first. *) + val build: int * int * LineGap.t * int * int + -> MailboxType.t list +end + + + +structure TextBuilder :> TEXT_BUILDER = +struct + val xSpace = 13 + val xSpace3 = xSpace * 3 + val ySpace = 25 + val fontSize = 30.0 + + fun accToDrawMsg (textAcc, cursorAcc) = + let + open MailboxType + open DrawMsg + + val textAcc = Vector.concat textAcc + val cursorAcc = Vector.concat cursorAcc + + val textMsg = REDRAW_TEXT textAcc + val cursorMsg = REDRAW_CURSOR cursorAcc + in + [DRAW textMsg, DRAW cursorMsg] + end + + fun buildCursor (posX, posY, fWindowWidth, fWindowHeight, r, g, b) = + let + val left = posX + 9 + val left = Real32.fromInt left + val right = left + 12.0 + + val top = Real32.fromInt posY + val bottom = top + fontSize + 2.0 + + val halfHeight = fWindowHeight / 2.0 + val top = (~(top - halfHeight)) / halfHeight + val bottom = (~(bottom - halfHeight)) / halfHeight + + val halfWidth = fWindowWidth / 2.0 + val left = (left - halfWidth) / halfWidth + val right = (right - halfWidth) / halfWidth + + 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