746 lines
23 KiB
Standard ML
746 lines
23 KiB
Standard ML
structure Cursor =
|
|
struct
|
|
(* returns absolute index of previous line break in string *)
|
|
fun helpVi0 (absIdx, stl, ltl) =
|
|
case (stl, ltl) of
|
|
(shd :: stl, lhd :: ltl) =>
|
|
if Vector.length lhd > 0 then
|
|
let
|
|
val startAbsIdx = absIdx - String.size shd
|
|
val lineIdx = Vector.sub (lhd, Vector.length lhd - 1)
|
|
in
|
|
(* found lineIdx.
|
|
* Need to make sure we follow cursor-on-linebreak rule:
|
|
* If line break is preceded by non-line break char,
|
|
* then increment by 1.
|
|
* *)
|
|
startAbsIdx + lineIdx + 1
|
|
end
|
|
else
|
|
helpVi0 (absIdx - String.size shd, stl, ltl)
|
|
| (_, _) => 0
|
|
|
|
fun startVi0 (strPos, shd, lhd, absIdx, stl, ltl) =
|
|
if String.sub (shd, strPos) = #"\n" then
|
|
absIdx
|
|
else if Vector.length lhd > 0 then
|
|
if Vector.sub (lhd, 0) < strPos then
|
|
let
|
|
val linePos = BinSearch.equalOrLess (strPos - 1, lhd)
|
|
val lineIdx = Vector.sub (lhd, linePos)
|
|
in
|
|
if linePos = ~1 then
|
|
(* no previous line in lhd *)
|
|
helpVi0 (absIdx - strPos, stl, ltl)
|
|
else
|
|
let val lineIdx = Vector.sub (lhd, linePos)
|
|
in absIdx - strPos + lineIdx + 1
|
|
end
|
|
end
|
|
else
|
|
helpVi0 (absIdx - strPos, stl, ltl)
|
|
else
|
|
helpVi0 (absIdx - strPos, stl, ltl)
|
|
|
|
structure Vi0 =
|
|
MakeIfCharFolderPrev
|
|
(struct
|
|
type env = unit
|
|
val fStart = startVi0
|
|
fun fStart (strPos, shd, lhd, absIdx, stl, ltl, _) =
|
|
startVi0 (strPos, shd, lhd, absIdx, stl, ltl)
|
|
end)
|
|
|
|
fun vi0 (lineGap, cursorIdx) = Vi0.foldPrev (lineGap, cursorIdx, ())
|
|
|
|
val viDlr = ViDlrDfa.next
|
|
val viDlrForDelete = ViDlrDfa.nextForDelete
|
|
|
|
val viL = ViLDfa.next
|
|
val viH = ViHDfa.prev
|
|
|
|
fun helpGetCursorColumn (distanceFromLine, strList, lineList) =
|
|
case (strList, lineList) of
|
|
(strHd :: strTl, lnHd :: lnTl) =>
|
|
if Vector.length lnHd = 0 then
|
|
(* lnHd is empty, so line is not here *)
|
|
helpGetCursorColumn
|
|
(distanceFromLine + String.size strHd, strTl, lnTl)
|
|
else
|
|
(* lnHd is not empty, meaning last lineIdx is closest linebreak *)
|
|
let
|
|
val lineIdx = Vector.sub (lnHd, Vector.length lnHd - 1)
|
|
(* number of chars after the lineIdx *)
|
|
val idxAfterLn = String.size strHd - lineIdx
|
|
in
|
|
distanceFromLine + idxAfterLn - 1
|
|
end
|
|
| (_, _) => distanceFromLine
|
|
|
|
fun helpGetCursorColumnLeft (leftStrings, leftLines, cursorIdx) =
|
|
case (leftStrings, leftLines) of
|
|
(lshd :: lstl, llhd :: lltl) =>
|
|
let
|
|
val cursorIdx = cursorIdx - String.size lshd
|
|
in
|
|
if Vector.length llhd > 0 then
|
|
let val lnIdx = Vector.sub (llhd, Vector.length llhd - 1)
|
|
in lnIdx + cursorIdx
|
|
end
|
|
else
|
|
helpGetCursorColumnLeft (lstl, lltl, cursorIdx)
|
|
end
|
|
| (_, _) => Int.max (cursorIdx, 0)
|
|
|
|
fun getCursorColumn (strIdx, strHd, lnHd, leftStrings, leftLines, cursorIdx) =
|
|
if Vector.length lnHd > 0 then
|
|
let
|
|
val firstLn = Vector.sub (lnHd, 0)
|
|
in
|
|
if firstLn > strIdx then
|
|
(* search left strings/lines *)
|
|
let
|
|
val lineIdx =
|
|
helpGetCursorColumnLeft
|
|
(leftStrings, leftLines, cursorIdx - strIdx)
|
|
in
|
|
if lineIdx = 0 then cursorIdx else cursorIdx - lineIdx - 1
|
|
end
|
|
else if firstLn < strIdx then
|
|
(* binary search in here
|
|
* because we know lnHd definitely contains
|
|
* a lineIdx less or equal to strIdx *)
|
|
let
|
|
(* todo: what if BinSearch doesn't find anything? *)
|
|
val lnIdx = BinSearch.equalOrLess (strIdx, lnHd)
|
|
val lnIdx = Vector.sub (lnHd, lnIdx)
|
|
in
|
|
if lnIdx < strIdx then strIdx - lnIdx - 1
|
|
else (* firstLn = strIdx *) 0
|
|
end
|
|
else
|
|
(* firstLn = strIdx
|
|
* meaning that we are already at a line break
|
|
* and that the column is 0 *)
|
|
0
|
|
end
|
|
else
|
|
let
|
|
val lineIdx =
|
|
helpGetCursorColumnLeft (leftStrings, leftLines, cursorIdx - strIdx)
|
|
in
|
|
if lineIdx = 0 then cursorIdx else cursorIdx - lineIdx - 1
|
|
end
|
|
|
|
(* equivalent of vi's 'w' command *)
|
|
val nextWord = ViWordDfa.startOfNextWord
|
|
|
|
(* equivalent of vi's 'W' command *)
|
|
val nextWORD = ViCapsWordDfa.startOfNextWORD
|
|
|
|
(* equivalent of vi's 'b' command *)
|
|
val prevWord = ViWordDfa.startOfCurrentWord
|
|
val prevWordStrict = ViWordDfa.startOfCurrentWordStrict
|
|
|
|
(* equivalent of vi's 'B' command *)
|
|
val prevWORD = ViCapsWordDfa.startOfCurrentWORD
|
|
val prevWORDStrict = ViCapsWordDfa.startOfCurrentWORDStrict
|
|
|
|
(* equivalent of vi's 'ge' command *)
|
|
val endOfPrevWord = ViWordDfa.endOfPrevWord
|
|
|
|
(* equivalent of vi's 'gE' command *)
|
|
val endOfPrevWORD = ViCapsWordDfa.endOfPrevWORD
|
|
|
|
(* equivalent of vi's `e` command *)
|
|
val endOfWord = ViWordDfa.endOfCurrentWord
|
|
val endOfWordForDelete = ViWordDfa.endOfCurrentWordForDelete
|
|
val endOfWordStrict = ViWordDfa.endOfCurrentWordStrict
|
|
|
|
(* equivalent of vi's `E` command *)
|
|
val endOfWORD = ViCapsWordDfa.endOfCurrentWORD
|
|
val endOfWORDForDelete = ViCapsWordDfa.endOfCurrentWORDForDelete
|
|
val endOfWORDStrict = ViCapsWordDfa.endOfCurrentWORDStrict
|
|
|
|
(* Prerequisite:
|
|
* LineGap has been moved to start of line (provided with vi0). *)
|
|
structure FirstNonSpaceChr =
|
|
MakeIfCharFolderPrev
|
|
(struct
|
|
type env = unit
|
|
|
|
fun helpFirstNonSpaceChr (strPos, str, absIdx, stl) =
|
|
if strPos = String.size str then
|
|
case stl of
|
|
shd :: stl => helpFirstNonSpaceChr (0, shd, absIdx, stl)
|
|
| [] => absIdx - 1
|
|
else
|
|
let
|
|
val chr = String.sub (str, strPos)
|
|
in
|
|
if chr = #" " then
|
|
helpFirstNonSpaceChr (strPos + 1, str, absIdx + 1, stl)
|
|
else
|
|
absIdx
|
|
end
|
|
|
|
fun fStart (strIdx, shd, _, absIdx, stl, _, _) =
|
|
if strIdx < String.size shd then
|
|
helpFirstNonSpaceChr (strIdx, shd, absIdx, stl)
|
|
else
|
|
case stl of
|
|
stlhd :: stltl => helpFirstNonSpaceChr (0, stlhd, absIdx, stltl)
|
|
| [] => (* tl is empty; just return absIdx *) absIdx
|
|
end)
|
|
|
|
fun firstNonSpaceChr (lineGap, cursorIdx) =
|
|
FirstNonSpaceChr.foldPrev (lineGap, cursorIdx, ())
|
|
|
|
fun helpToNextChr (strPos, str, absIdx, stl, ltl, origIdx, findChr) =
|
|
if strPos = String.size str then
|
|
case (stl, ltl) of
|
|
(shd :: stl, lhd :: ltl) =>
|
|
helpToNextChr (0, shd, absIdx, stl, ltl, origIdx, findChr)
|
|
| (_, _) => origIdx
|
|
else if String.sub (str, strPos) = findChr then
|
|
absIdx
|
|
else
|
|
helpToNextChr (strPos + 1, str, absIdx + 1, stl, ltl, origIdx, findChr)
|
|
|
|
fun startToNextChr (shd, strIdx, absIdx, stl, ltl, findChr) =
|
|
(* we want to start iterating from next char after strIdx *)
|
|
if strIdx - 1 < String.size shd then
|
|
helpToNextChr (strIdx + 1, shd, absIdx + 1, stl, ltl, absIdx, findChr)
|
|
else
|
|
case (stl, ltl) of
|
|
(stlhd :: stltl, ltlhd :: ltltl) =>
|
|
helpToNextChr (0, stlhd, absIdx + 1, stltl, ltltl, absIdx, findChr)
|
|
| (_, _) => (* tl is empty; just return absIdx *) absIdx
|
|
|
|
fun helpTillNextChr
|
|
(strPos, str, absIdx, stl, ltl, origIdx, findChr, lastNonLine, lastLine) =
|
|
if strPos = String.size str then
|
|
case (stl, ltl) of
|
|
(shd :: stl, lhd :: ltl) =>
|
|
helpTillNextChr
|
|
(0, shd, absIdx, stl, ltl, origIdx, findChr, lastNonLine, lastLine)
|
|
| (_, _) => origIdx
|
|
else
|
|
let
|
|
val chr = String.sub (str, strPos)
|
|
in
|
|
if chr = findChr then
|
|
if lastLine = lastNonLine + 1 then
|
|
(* graphical-chr -> \n
|
|
* so return graphical-chr *)
|
|
lastNonLine
|
|
else
|
|
Int.max (lastLine, lastNonLine)
|
|
else
|
|
let
|
|
val lastLine = if chr = #"\n" then absIdx else lastLine
|
|
val lastNonLine = if chr = #"\n" then lastNonLine else absIdx
|
|
in
|
|
helpTillNextChr
|
|
( strPos + 1
|
|
, str
|
|
, absIdx + 1
|
|
, stl
|
|
, ltl
|
|
, origIdx
|
|
, findChr
|
|
, lastNonLine
|
|
, lastLine
|
|
)
|
|
end
|
|
end
|
|
|
|
fun startTillNextChr (shd, strIdx, absIdx, stl, ltl, findChr) =
|
|
(* we want to start iterating from next char after strIdx *)
|
|
if strIdx + 1 < String.size shd then
|
|
helpTillNextChr
|
|
(strIdx + 1, shd, absIdx + 1, stl, ltl, absIdx, findChr, absIdx, absIdx)
|
|
else
|
|
case (stl, ltl) of
|
|
(stlhd :: stltl, ltlhd :: ltltl) =>
|
|
helpTillNextChr
|
|
( 0
|
|
, stlhd
|
|
, absIdx + 1
|
|
, stltl
|
|
, ltltl
|
|
, absIdx
|
|
, findChr
|
|
, absIdx
|
|
, absIdx
|
|
)
|
|
| (_, _) => (* tl is empty; just return absIdx *) absIdx
|
|
|
|
fun nextChr (lineGap: LineGap.t, cursorIdx, chr, fStart) =
|
|
let
|
|
val {rightStrings, rightLines, idx = bufferIdx, ...} = lineGap
|
|
in
|
|
case (rightStrings, rightLines) of
|
|
(shd :: stl, lhd :: ltl) =>
|
|
let
|
|
(* convert absolute cursorIdx to idx relative to hd string *)
|
|
val strIdx = cursorIdx - bufferIdx
|
|
in
|
|
if strIdx < String.size shd then
|
|
(* strIdx is in this string *)
|
|
fStart (shd, strIdx, cursorIdx, stl, ltl, chr)
|
|
else
|
|
(* strIdx is in tl *)
|
|
(case (stl, ltl) of
|
|
(stlhd :: stltl, ltlhd :: ltltl) =>
|
|
let val strIdx = strIdx - String.size shd
|
|
in fStart (stlhd, strIdx, cursorIdx, stltl, ltltl, chr)
|
|
end
|
|
| (_, _) => cursorIdx)
|
|
end
|
|
| (_, _) => cursorIdx
|
|
end
|
|
|
|
fun tillNextChr (lineGap, cursorIdx, chr) =
|
|
nextChr (lineGap, cursorIdx, chr, startTillNextChr)
|
|
|
|
fun toNextChr (lineGap, cursorIdx, chr) =
|
|
nextChr (lineGap, cursorIdx, chr, startToNextChr)
|
|
|
|
structure ToPrevChr =
|
|
MakeIfCharFolderPrev
|
|
(struct
|
|
type env = char
|
|
|
|
fun helpToPrevChr (strPos, str, absIdx, stl, origIdx, findChr) =
|
|
if strPos < 0 then
|
|
case stl of
|
|
shd :: stl =>
|
|
helpToPrevChr
|
|
(String.size shd - 1, shd, absIdx, stl, origIdx, findChr)
|
|
| [] => origIdx
|
|
else if String.sub (str, strPos) = findChr then
|
|
absIdx
|
|
else
|
|
helpToPrevChr (strPos - 1, str, absIdx - 1, stl, origIdx, findChr)
|
|
|
|
fun fStart (strIdx, shd, _, absIdx, stl, _, findChr) =
|
|
(* we want to start iterating from Prev char after strIdx *)
|
|
if strIdx > 0 then
|
|
helpToPrevChr (strIdx - 1, shd, absIdx - 1, stl, absIdx, findChr)
|
|
else
|
|
case stl of
|
|
stlhd :: stltl =>
|
|
helpToPrevChr
|
|
( String.size stlhd - 1
|
|
, stlhd
|
|
, absIdx - 1
|
|
, stltl
|
|
, absIdx
|
|
, findChr
|
|
)
|
|
| [] => (* tl is empty; return 0 for lineGap start *) 0
|
|
end)
|
|
|
|
val toPrevChr = ToPrevChr.foldPrev
|
|
|
|
structure TillPrevChr =
|
|
MakeIfCharFolderPrev
|
|
(struct
|
|
type env = char
|
|
|
|
fun helpTillPrevChr
|
|
( strPos
|
|
, str
|
|
, absIdx
|
|
, stl
|
|
, origIdx
|
|
, findChr
|
|
, lastNonLine
|
|
, lastLine
|
|
, lastValid
|
|
) =
|
|
if strPos < 0 then
|
|
case stl of
|
|
shd :: stl =>
|
|
helpTillPrevChr
|
|
( String.size shd - 1
|
|
, shd
|
|
, absIdx
|
|
, stl
|
|
, origIdx
|
|
, findChr
|
|
, lastNonLine
|
|
, lastLine
|
|
, lastValid
|
|
)
|
|
| [] => origIdx
|
|
else
|
|
let
|
|
val chr = String.sub (str, strPos)
|
|
in
|
|
if chr = findChr then
|
|
if lastLine = lastNonLine then lastNonLine
|
|
else if absIdx + 1 = lastLine then lastValid + 1
|
|
else Int.min (lastLine, lastNonLine)
|
|
else
|
|
let
|
|
val lastLine = if chr = #"\n" then absIdx else lastLine
|
|
val lastNonLine = if chr = #"\n" then lastNonLine else absIdx
|
|
|
|
(* There is a slightly tricky edge case
|
|
* which is the reason the lastValid variable.
|
|
* Say we have a string "a\n\n\nbcd"
|
|
* and we type "Ta" with the cursor at the end.
|
|
* We want the cursor to go to the second line break
|
|
* because (graphical-chr -> \n) should not be selectable.
|
|
* However, with only lastLine and lastNonLine variables,
|
|
* we only have information about the most recent \n
|
|
* and the most recent graphical-chr.
|
|
* This means we don't have information about the case
|
|
* where a graphical-chr is followed by multiple '\n's.
|
|
* The lastValid variable keeps track of this information
|
|
* so we can use it to provide the expected behaviour.
|
|
* *)
|
|
val lastValid =
|
|
if lastLine = lastNonLine + 1 then lastLine + 1
|
|
else Int.min (lastLine, lastNonLine)
|
|
in
|
|
helpTillPrevChr
|
|
( strPos - 1
|
|
, str
|
|
, absIdx - 1
|
|
, stl
|
|
, origIdx
|
|
, findChr
|
|
, lastNonLine
|
|
, lastLine
|
|
, lastValid
|
|
)
|
|
end
|
|
end
|
|
|
|
fun fStart (strIdx, shd, _, absIdx, stl, ltl, findChr) =
|
|
(* we want to start iterating from Prev char after strIdx *)
|
|
if strIdx > 0 then
|
|
helpTillPrevChr
|
|
( strIdx - 1
|
|
, shd
|
|
, absIdx - 1
|
|
, stl
|
|
, absIdx
|
|
, findChr
|
|
, absIdx
|
|
, absIdx
|
|
, absIdx
|
|
)
|
|
else
|
|
case stl of
|
|
stlhd :: stltl =>
|
|
helpTillPrevChr
|
|
( String.size stlhd - 1
|
|
, stlhd
|
|
, absIdx - 1
|
|
, stltl
|
|
, absIdx
|
|
, findChr
|
|
, absIdx
|
|
, absIdx
|
|
, absIdx
|
|
)
|
|
| [] => (* tl is empty; return 0 for lineGap start *) 0
|
|
end)
|
|
|
|
val tillPrevChr = TillPrevChr.foldPrev
|
|
|
|
fun helpMatchPairNext
|
|
(strPos, str, absIdx, stl, origIdx, openChr, openNum, closeChr, closeNum) =
|
|
if strPos = String.size str then
|
|
case stl of
|
|
hd :: tl =>
|
|
helpMatchPairNext
|
|
(0, hd, absIdx, tl, origIdx, openChr, openNum, closeChr, closeNum)
|
|
| [] => origIdx
|
|
else
|
|
let
|
|
val chr = String.sub (str, strPos)
|
|
val openNum = if chr = openChr then openNum + 1 else openNum
|
|
val closeNum = if chr = closeChr then closeNum + 1 else closeNum
|
|
in
|
|
if openNum = closeNum then
|
|
absIdx
|
|
else
|
|
helpMatchPairNext
|
|
( strPos + 1
|
|
, str
|
|
, absIdx + 1
|
|
, stl
|
|
, origIdx
|
|
, openChr
|
|
, openNum
|
|
, closeChr
|
|
, closeNum
|
|
)
|
|
end
|
|
|
|
fun helpMatchPairPrev
|
|
(strPos, str, absIdx, stl, origIdx, openChr, openNum, closeChr, closeNum) =
|
|
if strPos < 0 then
|
|
case stl of
|
|
hd :: tl =>
|
|
helpMatchPairPrev
|
|
( String.size hd - 1
|
|
, hd
|
|
, absIdx
|
|
, tl
|
|
, origIdx
|
|
, openChr
|
|
, openNum
|
|
, closeChr
|
|
, closeNum
|
|
)
|
|
| [] => origIdx
|
|
else
|
|
let
|
|
val chr = String.sub (str, strPos)
|
|
val openNum = if chr = openChr then openNum + 1 else openNum
|
|
val closeNum = if chr = closeChr then closeNum + 1 else closeNum
|
|
in
|
|
if openNum = closeNum then
|
|
absIdx
|
|
else
|
|
helpMatchPairPrev
|
|
( strPos - 1
|
|
, str
|
|
, absIdx - 1
|
|
, stl
|
|
, origIdx
|
|
, openChr
|
|
, openNum
|
|
, closeChr
|
|
, closeNum
|
|
)
|
|
end
|
|
|
|
fun startMatchPair (strIdx, shd, leftStrings, rightStrings, cursorIdx) =
|
|
case String.sub (shd, strIdx) of
|
|
#"(" =>
|
|
helpMatchPairNext
|
|
( strIdx + 1
|
|
, shd
|
|
, cursorIdx + 1
|
|
, rightStrings
|
|
, cursorIdx
|
|
, #"("
|
|
, 1
|
|
, #")"
|
|
, 0
|
|
)
|
|
| #")" =>
|
|
helpMatchPairPrev
|
|
( strIdx - 1
|
|
, shd
|
|
, cursorIdx - 1
|
|
, leftStrings
|
|
, cursorIdx
|
|
, #"("
|
|
, 0
|
|
, #")"
|
|
, 1
|
|
)
|
|
| #"[" =>
|
|
helpMatchPairNext
|
|
( strIdx + 1
|
|
, shd
|
|
, cursorIdx + 1
|
|
, rightStrings
|
|
, cursorIdx
|
|
, #"["
|
|
, 1
|
|
, #"]"
|
|
, 0
|
|
)
|
|
| #"]" =>
|
|
helpMatchPairPrev
|
|
( strIdx - 1
|
|
, shd
|
|
, cursorIdx - 1
|
|
, leftStrings
|
|
, cursorIdx
|
|
, #"["
|
|
, 0
|
|
, #"]"
|
|
, 1
|
|
)
|
|
| #"{" =>
|
|
helpMatchPairNext
|
|
( strIdx + 1
|
|
, shd
|
|
, cursorIdx + 1
|
|
, rightStrings
|
|
, cursorIdx
|
|
, #"{"
|
|
, 1
|
|
, #"}"
|
|
, 0
|
|
)
|
|
| #"}" =>
|
|
helpMatchPairPrev
|
|
( strIdx - 1
|
|
, shd
|
|
, cursorIdx - 1
|
|
, leftStrings
|
|
, cursorIdx
|
|
, #"{"
|
|
, 0
|
|
, #"}"
|
|
, 1
|
|
)
|
|
| #"<" =>
|
|
helpMatchPairNext
|
|
( strIdx + 1
|
|
, shd
|
|
, cursorIdx + 1
|
|
, rightStrings
|
|
, cursorIdx
|
|
, #"<"
|
|
, 1
|
|
, #">"
|
|
, 0
|
|
)
|
|
| #">" =>
|
|
helpMatchPairPrev
|
|
( strIdx - 1
|
|
, shd
|
|
, cursorIdx - 1
|
|
, leftStrings
|
|
, cursorIdx
|
|
, #"<"
|
|
, 0
|
|
, #">"
|
|
, 1
|
|
)
|
|
| _ => cursorIdx
|
|
|
|
fun matchPair (lineGap: LineGap.t, cursorIdx) =
|
|
let
|
|
val {rightStrings, idx = bufferIdx, leftStrings, ...} = lineGap
|
|
in
|
|
case rightStrings of
|
|
shd :: stl =>
|
|
let
|
|
(* convert absolute cursorIdx to idx relative to hd string *)
|
|
val strIdx = cursorIdx - bufferIdx
|
|
in
|
|
if strIdx < String.size shd then
|
|
(* strIdx is in this string *)
|
|
startMatchPair (strIdx, shd, leftStrings, stl, cursorIdx)
|
|
else
|
|
(* strIdx is in tl *)
|
|
(case stl of
|
|
stlhd :: stltl =>
|
|
let
|
|
val strIdx = strIdx - String.size shd
|
|
val leftStrings = shd :: leftStrings
|
|
in
|
|
startMatchPair
|
|
(strIdx, stlhd, leftStrings, stltl, cursorIdx)
|
|
end
|
|
| [] => cursorIdx)
|
|
end
|
|
| [] => cursorIdx
|
|
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 - String.size hd, tlhd, tltl)
|
|
| [] =>
|
|
(* strIdx is at end of lineGap
|
|
* which also means at end of line *)
|
|
true)
|
|
end
|
|
| [] => true
|
|
end
|
|
|
|
(* Prerequisite: lineGap is moved to cursorIdx *)
|
|
fun isOnNewlineAfterChr (buffer, cursorIdx) =
|
|
cursorIdx > 0 andalso not (isPrevChrStartOfLine (buffer, cursorIdx))
|
|
andalso isCursorAtStartOfLine (buffer, cursorIdx)
|
|
end
|