From 35de4582e98ccb63ceaf14b628ac82a6de18b7a4 Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Fri, 15 Nov 2024 08:13:32 +0000 Subject: [PATCH] refactor TextBuilder and inner functions to handle highlighting searched characters --- fcore/text-builder.sml | 567 +++++++++++++++++++++++++++++------------ 1 file changed, 400 insertions(+), 167 deletions(-) diff --git a/fcore/text-builder.sml b/fcore/text-builder.sml index 883b5d9..504fb33 100644 --- a/fcore/text-builder.sml +++ b/fcore/text-builder.sml @@ -1,7 +1,7 @@ signature TEXT_BUILDER = sig - (* Prerequisite: LineGap is moved to requested line first. *) - val build: int * int * LineGap.t * int * int + (* Prerequisites: LineGap is moved to requested line first. *) + val build: int * int * LineGap.t * int * int * SearchList.t * string -> MailboxType.t list end @@ -88,114 +88,72 @@ struct , tl, absIdx, cursorPos, cursorAcc , windowData: window_data, colourData: colour_data ) = - 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 *) - buildTextString - ( pos + 1, str, acc, posX + xSpace, posY, startX - , tl, absIdx + 1, cursorPos, cursorAcc - , windowData, colourData - ) - else - (* in cursor *) - let - val {fw, fh, ...} = windowData - val {r, g, b, ...} = colourData - - val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) - in - buildTextString - ( pos + 1, str, acc, posX + xSpace, posY, startX - , tl, absIdx + 1, cursorPos, cursorAcc - , windowData, colourData - ) - end - | #"\n" => - if posY + ySpace < #h windowData then + 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 cursor position, so iterate like normal *) + (* not in cursur *) buildTextString - ( pos + 1, str, acc, startX, posY + ySpace, startX - , tl, absIdx + 1, cursorPos, cursorAcc - , windowData, colourData - ) + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + ) else - (* in cursor position, so build cursorAcc *) - let - val {fw, fh, ...} = windowData - val {r, g, b, ...} = colourData - - val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) - in - buildTextString - ( pos + 1, str, acc, startX, posY + ySpace, startX - , tl, absIdx + 1, cursorPos, cursorAcc - , windowData, colourData - ) - end - 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 < #w windowData then - let - val {fw, fh, ...} = windowData - val {r, g, b, ...} = colourData - - val chrVec = chrFun - (posX, posY, fontSize, fontSize, fw, fh, r, g, b) - val acc = chrVec :: acc - in - buildTextString - ( pos + 1, str, acc, posX + xSpace, posY, startX - , tl, absIdx + 1, cursorPos, cursorAcc - , windowData, colourData - ) - end - else if posY + ySpace < #h windowData then - let - val {fw, fh, ...} = windowData - val {r, g, b, ...} = colourData - - val chrVec = chrFun - ( startX, posY + ySpace, fontSize, fontSize - , fw, fh, r, g, b - ) - val acc = chrVec :: acc - in - buildTextString - ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX - , tl, absIdx + 1, cursorPos, cursorAcc - , windowData, colourData - ) - end - else - accToDrawMsg (acc, cursorAcc) - else - (* equal to cursor *) + (* in cursor *) let val {fw, fh, ...} = windowData - val {r, g, b, hr, hg, hb} = colourData + val {r, g, b, ...} = colourData + val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) in + buildTextString + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + ) + end + | #"\n" => + if posY + ySpace < #h windowData then + if absIdx <> cursorPos then + (* not in cursor position, so iterate like normal *) + buildTextString + ( pos + 1, str, acc, startX, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + ) + else + (* in cursor position, so build cursorAcc *) + let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + + val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) + in + buildTextString + ( pos + 1, str, acc, startX, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + ) + end + 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 < #w windowData then let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + val chrVec = chrFun - ( posX, posY, fontSize, fontSize - , fw, fh , hr, hg, hb - ) + (posX, posY, fontSize, fontSize, fw, fh, r, g, b) val acc = chrVec :: acc in - (* can start building after cursor now, - * since cursor was built *) buildTextString ( pos + 1, str, acc, posX + xSpace, posY, startX , tl, absIdx + 1, cursorPos, cursorAcc @@ -204,14 +162,15 @@ struct end else if posY + ySpace < #h windowData then let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + val chrVec = chrFun ( startX, posY + ySpace, fontSize, fontSize - , fw, fh, hr, hg, hb + , fw, fh, r, g, b ) val acc = chrVec :: acc in - (* can start building after cursor now, - * since cursor was built *) buildTextString ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX , tl, absIdx + 1, cursorPos, cursorAcc @@ -220,75 +179,349 @@ struct end else accToDrawMsg (acc, cursorAcc) - end - end - else - (* we have built cursor now, so can call after-cursor function - * to build rest of text *) - case tl of - hd :: tl => - buildTextString - ( 0, hd, acc, posX, posY, startX - , tl, absIdx, cursorPos, cursorAcc - , windowData, colourData - ) - | [] => - accToDrawMsg (acc, cursorAcc) - - fun build - (startLine, cursorPos, lineGap: LineGap.t, windowWidth, windowHeight) = - let - 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 + (* equal to cursor *) + let + val {fw, fh, ...} = windowData + val {r, g, b, hr, hg, hb} = colourData + val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) + in + if posX + xSpace < #w windowData then + let + val chrVec = chrFun + ( posX, posY, fontSize, fontSize + , fw, fh , hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextString + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + ) + end + else if posY + ySpace < #h windowData then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fw, fh, hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextString + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + ) + end + else + accToDrawMsg (acc, cursorAcc) + end + end + else + (* change to searching in string's tl *) + case tl of + hd :: tl => + buildTextString + ( 0, hd, acc, posX, posY, startX + , tl, absIdx, cursorPos, cursorAcc + , windowData, colourData + ) + | [] => + accToDrawMsg (acc, cursorAcc) - val windowData = - { w = windowWidth - , h = windowHeight - , fw = Real32.fromInt windowWidth - , fh = Real32.fromInt windowHeight - } + fun isInSearchRange (absIdx, searchPos, searchHd, searchLen) = + let + val searchIdx = Vector.sub (searchHd, searchPos) + in + absIdx >= searchIdx andalso absIdx < searchIdx + searchLen + end - val colourData = - { r = 0.67 - , g = 0.51 - , b = 0.83 - , hr = 0.211 - , hg = 0.219 - , hb = 0.25 - } - in - buildTextString - ( startIdx, rStrHd, [], 5, 5, 5 - , rStrTl, absIdx, cursorPos, [] + fun isAfterSearchRange (absIdx, searchPos, searchHd, searchLen) = + let + val searchIdx = Vector.sub (searchHd, searchPos) + in + absIdx >= searchIdx + searchLen + end + + fun buildTextStringSearch + ( pos, str, acc, posX, posY, startX + , tl, absIdx, cursorPos, cursorAcc + , windowData: window_data, colourData: colour_data + , searchHd, searchTl, searchPos, searchLen + ) = + if searchPos = Vector.length searchHd then + case searchTl of + searchHd :: searchTl => + (* go to next search hd/tl *) + buildTextStringSearch + ( pos, str, acc, posX, posY, startX + , tl, absIdx, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, 0, searchLen + ) + | [] => + (* exhausted search hd/tl so calll normal build function *) + buildTextString + ( pos, str, acc, posX, posY, startX + , tl, absIdx, cursorPos, cursorAcc , windowData, colourData ) - end - | (_, _) => - (* requested line goes beyond the buffer, - * so just return empty list as there is nothig - * else we can do. *) - [] - end + else 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 *) + buildTextStringSearch + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + else + (* in cursor *) + let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + + val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) + in + buildTextStringSearch + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + | #"\n" => + if posY + ySpace < #h windowData then + if absIdx <> cursorPos then + (* not in cursor position, so iterate like normal *) + buildTextStringSearch + ( pos + 1, str, acc, startX, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + else + (* in cursor position, so build cursorAcc *) + let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + + val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) + in + buildTextStringSearch + ( pos + 1, str, acc, startX, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + 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 < #w windowData then + if isInSearchRange (absIdx, searchPos, searchHd, searchLen) then + let + val {fw, fh, ...} = windowData + + (* todo: temp colours *) + val r: Real32.real = 0.5 + val g: Real32.real = 0.5 + val b: Real32.real = 0.5 + + val chrVec = + chrFun (posX, posY, fontSize, fontSize, fw, fh, r, g, b) + val acc = chrVec :: acc + in + buildTextStringSearch + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + else + let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + + val chrVec = + chrFun (posX, posY, fontSize, fontSize, fw, fh, r, g, b) + val acc = chrVec :: acc + val searchPos = + if isAfterSearchRange (absIdx, searchPos, searchHd, searchLen) + then searchPos + 1 + else searchPos + in + buildTextStringSearch + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + else if posY + ySpace < #h windowData then + let + val {fw, fh, ...} = windowData + val {r, g, b, ...} = colourData + + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fw, fh, r, g, b + ) + val acc = chrVec :: acc + in + buildTextStringSearch + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + else + accToDrawMsg (acc, cursorAcc) + else + (* equal to cursor *) + let + val {fw, fh, ...} = windowData + val {r, g, b, hr, hg, hb} = colourData + val cursorAcc = buildCursor (posX, posY, fw, fh, r, g ,b) + in + if posX + xSpace < #w windowData then + let + val chrVec = chrFun + ( posX, posY, fontSize, fontSize + , fw, fh , hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextStringSearch + ( pos + 1, str, acc, posX + xSpace, posY, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + else if posY + ySpace < #h windowData then + let + val chrVec = chrFun + ( startX, posY + ySpace, fontSize, fontSize + , fw, fh, hr, hg, hb + ) + val acc = chrVec :: acc + in + (* can start building after cursor now, + * since cursor was built *) + buildTextStringSearch + ( pos + 1, str, acc, startX + xSpace, posY + ySpace, startX + , tl, absIdx + 1, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + end + else + accToDrawMsg (acc, cursorAcc) + end + end + else + (* change to searching in string's tl *) + case tl of + hd :: tl => + buildTextStringSearch + ( 0, hd, acc, posX, posY, startX + , tl, absIdx, cursorPos, cursorAcc + , windowData, colourData + , searchHd, searchTl, searchPos, searchLen + ) + | [] => + accToDrawMsg (acc, cursorAcc) + + fun build + ( startLine, cursorPos, lineGap: LineGap.t + , windowWidth, windowHeight + , searchList: SearchList.t, searchString + ) = + let + 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 + val searchList = SearchList.goToNum (absIdx, searchList) + + val windowData = + { w = windowWidth + , h = windowHeight + , fw = Real32.fromInt windowWidth + , fh = Real32.fromInt windowHeight + } + + val colourData = + { r = 0.67 + , g = 0.51 + , b = 0.83 + , hr = 0.211 + , hg = 0.219 + , hb = 0.25 + } + in + (case #right searchList of + searchHd :: searchTl => + let + val searchPos = BinSearch.equalOrMore (absIdx, searchHd) + in + buildTextStringSearch + ( startIdx, rStrHd, [], 5, 5, 5 + , rStrTl, absIdx, cursorPos, [] + , windowData, colourData + , searchHd, searchTl, searchPos, String.size searchString + ) + end + | [] => + buildTextString + ( startIdx, rStrHd, [], 5, 5, 5 + , rStrTl, absIdx, cursorPos, [] + , windowData, colourData + )) + end + | (_, _) => + (* requested line goes beyond the buffer, + * so just return empty list as there is nothig + * else we can do. *) + [] + end end