Add 'shf/' from commit 'b6c5a95b664aeb861d7b33ffc9eefe447ba99dd7'
git-subtree-dir: shf git-subtree-mainline:401408448fgit-subtree-split:b6c5a95b66
This commit is contained in:
45
shf/fcore/app-type.sml
Normal file
45
shf/fcore/app-type.sml
Normal file
@@ -0,0 +1,45 @@
|
||||
structure AppType =
|
||||
struct
|
||||
datatype mode =
|
||||
NORMAL_MODE of string
|
||||
| NORMAL_SEARCH_MODE of
|
||||
{ searchString: string
|
||||
, tempSearchList: PersistentVector.t
|
||||
, searchCursorIdx: int
|
||||
, searchScrollColumn: int
|
||||
, caseSensitive: bool
|
||||
}
|
||||
|
||||
type app_type =
|
||||
{ mode: mode
|
||||
, buffer: LineGap.t
|
||||
, bufferModifyTime: Time.time
|
||||
, searchList: PersistentVector.t
|
||||
, windowWidth: int
|
||||
, windowHeight: int
|
||||
(* line to start drawing from *)
|
||||
, startLine: int
|
||||
(* absolute index of movable cursor *)
|
||||
, cursorIdx: int
|
||||
(* column to start drawing text at for horizontal scrolling. *)
|
||||
, visualScrollColumn: int
|
||||
, dfa: int vector vector
|
||||
(* msgs to send after an update.
|
||||
* The list of messages is reset on each invocation of AppUpdate.update. *)
|
||||
, msgs: MailboxType.t list
|
||||
}
|
||||
|
||||
fun init (buffer, windowWidth, windowHeight, time) : app_type =
|
||||
{ mode = NORMAL_MODE ""
|
||||
, buffer = buffer
|
||||
, bufferModifyTime = time
|
||||
, searchList = PersistentVector.empty
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, startLine = 0
|
||||
, cursorIdx = 0
|
||||
, visualScrollColumn = 0
|
||||
, dfa = Vector.fromList []
|
||||
, msgs = []
|
||||
}
|
||||
end
|
||||
10
shf/fcore/app-update.sml
Normal file
10
shf/fcore/app-update.sml
Normal file
@@ -0,0 +1,10 @@
|
||||
structure AppUpdate =
|
||||
struct
|
||||
open AppType
|
||||
|
||||
fun update (app: app_type, msg, time) =
|
||||
case #mode app of
|
||||
NORMAL_MODE modeData => NormalMode.update (app, modeData, msg, time)
|
||||
| NORMAL_SEARCH_MODE modeData =>
|
||||
NormalSearchMode.update (app, modeData, msg, time)
|
||||
end
|
||||
35
shf/fcore/app-with.sml
Normal file
35
shf/fcore/app-with.sml
Normal file
@@ -0,0 +1,35 @@
|
||||
structure AppWith =
|
||||
struct
|
||||
open AppType
|
||||
|
||||
(* this function exists only for testing *)
|
||||
fun idx (app, newIdx) =
|
||||
let
|
||||
val
|
||||
{ startLine
|
||||
, buffer
|
||||
, bufferModifyTime
|
||||
, searchList
|
||||
, dfa
|
||||
, mode
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, msgs
|
||||
, visualScrollColumn
|
||||
, cursorIdx = _
|
||||
} = app
|
||||
in
|
||||
{ startLine = startLine
|
||||
, buffer = buffer
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, searchList = searchList
|
||||
, dfa = dfa
|
||||
, mode = mode
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, msgs = msgs
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, cursorIdx = newIdx
|
||||
}
|
||||
end
|
||||
end
|
||||
120
shf/fcore/bin-search.sml
Normal file
120
shf/fcore/bin-search.sml
Normal file
@@ -0,0 +1,120 @@
|
||||
structure BinSearch =
|
||||
struct
|
||||
local
|
||||
fun reverseLinearSearch (findNum, idx, vec) =
|
||||
if idx < 0 then
|
||||
~1
|
||||
else
|
||||
let
|
||||
val curVal = Vector.sub (vec, idx)
|
||||
in
|
||||
if curVal < findNum then idx
|
||||
else reverseLinearSearch (findNum, idx - 1, vec)
|
||||
end
|
||||
|
||||
fun helpBinSearch (findNum, vec, low, high) =
|
||||
let
|
||||
val mid = low + ((high - low) div 2)
|
||||
in
|
||||
if high >= low then
|
||||
let
|
||||
val midVal = Vector.sub (vec, mid)
|
||||
in
|
||||
if midVal = findNum then
|
||||
mid
|
||||
else if midVal < findNum then
|
||||
helpBinSearch (findNum, vec, mid + 1, high)
|
||||
else
|
||||
helpBinSearch (findNum, vec, low, mid - 1)
|
||||
end
|
||||
else
|
||||
reverseLinearSearch (findNum, mid, vec)
|
||||
end
|
||||
in
|
||||
fun equalOrLess (findNum, vec) =
|
||||
helpBinSearch (findNum, vec, 0, Vector.length vec - 1)
|
||||
end
|
||||
|
||||
local
|
||||
fun forwardLinearSearch (findNum, idx, vec) =
|
||||
if idx = Vector.length vec then
|
||||
~1
|
||||
else
|
||||
let
|
||||
val curVal = Vector.sub (vec, idx)
|
||||
in
|
||||
if curVal > findNum then idx
|
||||
else forwardLinearSearch (findNum, idx + 1, vec)
|
||||
end
|
||||
|
||||
fun helpBinSearch (findNum, vec, low, high) =
|
||||
let
|
||||
val mid = low + ((high - low) div 2)
|
||||
in
|
||||
if high >= low then
|
||||
let
|
||||
val midVal = Vector.sub (vec, mid)
|
||||
in
|
||||
if midVal = findNum then
|
||||
mid
|
||||
else if midVal < findNum then
|
||||
helpBinSearch (findNum, vec, mid + 1, high)
|
||||
else
|
||||
helpBinSearch (findNum, vec, low, mid - 1)
|
||||
end
|
||||
else
|
||||
forwardLinearSearch (findNum, Int.max (mid, 0), vec)
|
||||
end
|
||||
in
|
||||
fun equalOrMore (findNum, vec) =
|
||||
helpBinSearch (findNum, vec, 0, Vector.length vec - 1)
|
||||
end
|
||||
|
||||
local
|
||||
fun helpExists (findNum, vec, low, high) =
|
||||
let
|
||||
val mid = low + ((high - low) div 2)
|
||||
in
|
||||
if high >= low then
|
||||
let
|
||||
val midVal = Vector.sub (vec, mid)
|
||||
in
|
||||
if midVal = findNum then
|
||||
true
|
||||
else if midVal < findNum then
|
||||
helpExists (findNum, vec, mid + 1, high)
|
||||
else
|
||||
helpExists (findNum, vec, low, mid - 1)
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
in
|
||||
fun exists (findNum, vec) =
|
||||
helpExists (findNum, vec, 0, Vector.length vec - 1)
|
||||
end
|
||||
|
||||
local
|
||||
fun helpEqualOrMinus1 (findNum, vec, low, high) =
|
||||
let
|
||||
val mid = low + ((high - low) div 2)
|
||||
in
|
||||
if high >= low then
|
||||
let
|
||||
val midVal = Vector.sub (vec, mid)
|
||||
in
|
||||
if midVal = findNum then
|
||||
mid
|
||||
else if midVal < findNum then
|
||||
helpEqualOrMinus1 (findNum, vec, mid + 1, high)
|
||||
else
|
||||
helpEqualOrMinus1 (findNum, vec, low, mid - 1)
|
||||
end
|
||||
else
|
||||
~1
|
||||
end
|
||||
in
|
||||
fun equalOrMinus1 (findNum, vec) =
|
||||
helpEqualOrMinus1 (findNum, vec, 0, Vector.length vec - 1)
|
||||
end
|
||||
end
|
||||
303
shf/fcore/cursor-dfa/make-dfa-loop.sml
Normal file
303
shf/fcore/cursor-dfa/make-dfa-loop.sml
Normal file
@@ -0,0 +1,303 @@
|
||||
signature MAKE_DFA_LOOP =
|
||||
sig
|
||||
val fStart: int * int * string * string list * Word8.word * int -> int
|
||||
val startState: Word8.word
|
||||
end
|
||||
|
||||
functor MakeNextDfaLoop(M: MAKE_DFA_LOOP) =
|
||||
struct
|
||||
fun next (lineGap: LineGap.t, cursorIdx, count) =
|
||||
let
|
||||
val {rightStrings, idx = bufferIdx, ...} = lineGap
|
||||
(* convert absolute cursorIdx to idx relative to hd string *)
|
||||
val strIdx = cursorIdx - bufferIdx
|
||||
in
|
||||
case rightStrings of
|
||||
shd :: stl =>
|
||||
if strIdx < String.size shd then
|
||||
(* strIdx is in this string *)
|
||||
M.fStart (strIdx, cursorIdx, shd, stl, M.startState, count)
|
||||
else
|
||||
(* strIdx is in tl *)
|
||||
(case stl of
|
||||
stlhd :: stltl =>
|
||||
let
|
||||
val strIdx = strIdx - String.size shd
|
||||
in
|
||||
M.fStart
|
||||
(strIdx, cursorIdx, stlhd, stltl, M.startState, count)
|
||||
end
|
||||
| _ => cursorIdx)
|
||||
| [] => cursorIdx
|
||||
end
|
||||
end
|
||||
|
||||
functor MakeNextDfaLoopPlus1(M: MAKE_DFA_LOOP) =
|
||||
struct
|
||||
fun next (lineGap: LineGap.t, cursorIdx, count) =
|
||||
let
|
||||
val {rightStrings, idx = bufferIdx, ...} = lineGap
|
||||
(* convert absolute cursorIdx to idx relative to hd string *)
|
||||
val strIdx = cursorIdx - bufferIdx + 1
|
||||
val absIdx = cursorIdx + 1
|
||||
in
|
||||
case rightStrings of
|
||||
shd :: stl =>
|
||||
if strIdx < String.size shd then
|
||||
(* strIdx is in this string *)
|
||||
M.fStart (strIdx, absIdx, shd, stl, M.startState, count)
|
||||
else
|
||||
(* strIdx is in tl *)
|
||||
(case stl of
|
||||
stlhd :: stltl =>
|
||||
let val strIdx = strIdx - String.size shd
|
||||
in M.fStart (strIdx, absIdx, stlhd, stltl, M.startState, count)
|
||||
end
|
||||
| _ => cursorIdx)
|
||||
| [] => cursorIdx
|
||||
end
|
||||
end
|
||||
|
||||
functor MakePrevDfaLoop(M: MAKE_DFA_LOOP) =
|
||||
struct
|
||||
fun startLeftStrings (leftStrings, absIdx, count) =
|
||||
case leftStrings of
|
||||
lhd :: ltl =>
|
||||
M.fStart (String.size lhd - 1, absIdx, lhd, ltl, M.startState, count)
|
||||
| [] => 0
|
||||
|
||||
fun prev (lineGap: LineGap.t, cursorIdx, count) =
|
||||
let
|
||||
val {rightStrings, leftStrings, idx = bufferIdx, ...} = lineGap
|
||||
(* convert absolute cursorIdx to idx relative to hd string *)
|
||||
val strIdx = cursorIdx - bufferIdx
|
||||
in
|
||||
case rightStrings of
|
||||
shd :: stl =>
|
||||
if strIdx < String.size shd then
|
||||
(* strIdx is in this string *)
|
||||
M.fStart (strIdx, cursorIdx, shd, leftStrings, M.startState, count)
|
||||
else
|
||||
(* strIdx is in tl *)
|
||||
(case stl of
|
||||
stlhd :: stltl =>
|
||||
let
|
||||
val strIdx = strIdx - String.size shd
|
||||
val leftStrings = shd :: leftStrings
|
||||
in
|
||||
M.fStart
|
||||
( strIdx
|
||||
, cursorIdx
|
||||
, stlhd
|
||||
, leftStrings
|
||||
, M.startState
|
||||
, count
|
||||
)
|
||||
end
|
||||
| [] => startLeftStrings (leftStrings, cursorIdx, count))
|
||||
| [] => startLeftStrings (leftStrings, cursorIdx, count)
|
||||
end
|
||||
end
|
||||
|
||||
functor MakePrevDfaLoopMinus1(M: MAKE_DFA_LOOP) =
|
||||
struct
|
||||
fun startLeftStrings (leftStrings, absIdx, count) =
|
||||
case leftStrings of
|
||||
lhd :: ltl =>
|
||||
M.fStart (String.size lhd - 1, absIdx, lhd, ltl, M.startState, count)
|
||||
| [] => 0
|
||||
|
||||
fun prev (lineGap: LineGap.t, cursorIdx, count) =
|
||||
let
|
||||
val {idx = bufferIdx, leftStrings, ...} = lineGap
|
||||
val strIdx = cursorIdx - bufferIdx - 1
|
||||
val absIdx = cursorIdx - 1
|
||||
in
|
||||
if strIdx < 0 then
|
||||
startLeftStrings (leftStrings, absIdx, count)
|
||||
else
|
||||
case #rightStrings lineGap of
|
||||
rhd :: _ =>
|
||||
M.fStart (strIdx, absIdx, rhd, leftStrings, M.startState, count)
|
||||
| [] => startLeftStrings (leftStrings, absIdx, count)
|
||||
end
|
||||
end
|
||||
|
||||
signature MAKE_CHAR_FOLDER =
|
||||
sig
|
||||
val startState: Word8.word
|
||||
val tables: Word8.word vector vector
|
||||
|
||||
val isFinal: Word8.word -> bool
|
||||
val finish: int -> int
|
||||
end
|
||||
|
||||
functor MakeCharFolderNext(Fn: MAKE_CHAR_FOLDER) =
|
||||
struct
|
||||
fun nextState (currentState, currentChar) =
|
||||
let
|
||||
val currentState = Word8.toInt currentState
|
||||
val currentTable = Vector.sub (Fn.tables, currentState)
|
||||
val charIdx = Char.ord currentChar
|
||||
in
|
||||
Vector.sub (currentTable, charIdx)
|
||||
end
|
||||
|
||||
fun foldNext (idx, absIdx, str, tl, currentState, counter) =
|
||||
if idx = String.size str then
|
||||
case tl of
|
||||
str :: tl => foldNext (0, absIdx, str, tl, currentState, counter)
|
||||
| [] => absIdx
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, idx)
|
||||
val newState = nextState (currentState, chr)
|
||||
in
|
||||
if Fn.isFinal newState then
|
||||
if counter - 1 = 0 then
|
||||
Fn.finish absIdx
|
||||
else
|
||||
(* new loop, so reset start state and proceed *)
|
||||
let val newState = nextState (Fn.startState, chr)
|
||||
in foldNext (idx + 1, absIdx + 1, str, tl, newState, counter - 1)
|
||||
end
|
||||
else
|
||||
foldNext (idx + 1, absIdx + 1, str, tl, newState, counter)
|
||||
end
|
||||
end
|
||||
|
||||
functor MakeCharFolderPrev(Fn: MAKE_CHAR_FOLDER) =
|
||||
struct
|
||||
fun nextState (currentState, currentChar) =
|
||||
let
|
||||
val currentState = Word8.toInt currentState
|
||||
val currentTable = Vector.sub (Fn.tables, currentState)
|
||||
val charIdx = Char.ord currentChar
|
||||
in
|
||||
Vector.sub (currentTable, charIdx)
|
||||
end
|
||||
|
||||
fun foldPrev (idx, absIdx, str, tl, currentState, counter) =
|
||||
if idx < 0 then
|
||||
case tl of
|
||||
str :: tl =>
|
||||
foldPrev (String.size str - 1, absIdx, str, tl, currentState, counter)
|
||||
| [] => 0
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, idx)
|
||||
val newState = nextState (currentState, chr)
|
||||
in
|
||||
if Fn.isFinal newState then
|
||||
if counter - 1 = 0 then
|
||||
Fn.finish absIdx
|
||||
else
|
||||
let val newState = nextState (Fn.startState, chr)
|
||||
in foldPrev (idx - 1, absIdx - 1, str, tl, newState, counter - 1)
|
||||
end
|
||||
else
|
||||
foldPrev (idx - 1, absIdx - 1, str, tl, newState, counter)
|
||||
end
|
||||
end
|
||||
|
||||
signature MAKE_IF_CHAR_FOLDER =
|
||||
sig
|
||||
type env
|
||||
|
||||
val fStart:
|
||||
int * string * int vector * int * string list * int vector list * env
|
||||
-> int
|
||||
end
|
||||
|
||||
functor MakeIfCharFolderPrev(Fn: MAKE_IF_CHAR_FOLDER) =
|
||||
struct
|
||||
fun foldPrev (lineGap: LineGap.t, cursorIdx, env: Fn.env) =
|
||||
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 either in this string or in leftStrings *)
|
||||
if strIdx < 0 then
|
||||
case (leftStrings, leftLines) of
|
||||
(lshd :: lstl, llhd :: lltl) =>
|
||||
Fn.fStart
|
||||
( String.size lshd - 1
|
||||
, lshd
|
||||
, llhd
|
||||
, cursorIdx
|
||||
, lstl
|
||||
, lltl
|
||||
, env
|
||||
)
|
||||
| (_, _) => 0
|
||||
else
|
||||
Fn.fStart
|
||||
(strIdx, strHd, lnHd, cursorIdx, leftStrings, leftLines, env)
|
||||
else
|
||||
(* strIdx must be in the strTl *)
|
||||
(case (strTl, lnTl) of
|
||||
(nestStrHd :: _, nestLnHd :: _) =>
|
||||
let
|
||||
val strIdx = strIdx - String.size strHd
|
||||
in
|
||||
Fn.fStart
|
||||
( strIdx
|
||||
, nestStrHd
|
||||
, nestLnHd
|
||||
, cursorIdx
|
||||
, strHd :: leftStrings
|
||||
, lnHd :: leftLines
|
||||
, env
|
||||
)
|
||||
end
|
||||
| (_, _) => cursorIdx)
|
||||
end
|
||||
| (_, _) => (* nowhere to go, so return cursorIdx *) cursorIdx
|
||||
end
|
||||
end
|
||||
|
||||
functor MakeIfCharFolderNext(Fn: MAKE_IF_CHAR_FOLDER) =
|
||||
struct
|
||||
fun foldNext (lineGap: LineGap.t, cursorIdx, env: Fn.env) =
|
||||
let
|
||||
val {rightStrings, idx = bufferIdx, rightLines, ...} = 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 *)
|
||||
Fn.fStart (strIdx, strHd, lnHd, cursorIdx, strTl, lnTl, env)
|
||||
else
|
||||
(* strIdx must be in the strTl *)
|
||||
(case (strTl, lnTl) of
|
||||
(nestStrHd :: nestStrTl, nestLnHd :: nestLnTl) =>
|
||||
let
|
||||
val strIdx = strIdx - String.size strHd
|
||||
in
|
||||
Fn.fStart
|
||||
( strIdx
|
||||
, nestStrHd
|
||||
, nestLnHd
|
||||
, cursorIdx
|
||||
, nestStrTl
|
||||
, nestLnTl
|
||||
, env
|
||||
)
|
||||
end
|
||||
| (_, _) => cursorIdx)
|
||||
end
|
||||
| (_, _) => (* nowhere to go, so return cursorIdx *) cursorIdx
|
||||
end
|
||||
end
|
||||
239
shf/fcore/cursor-dfa/vi-caps-word-dfa.sml
Normal file
239
shf/fcore/cursor-dfa/vi-caps-word-dfa.sml
Normal file
@@ -0,0 +1,239 @@
|
||||
structure ViCapsWordDfa =
|
||||
struct
|
||||
val startState: Word8.word = 0w0
|
||||
val startNonBlankState: Word8.word = 0w1
|
||||
val startSpaceState: Word8.word = 0w2
|
||||
val nonBlankAfterSpaceState: Word8.word = 0w3
|
||||
val spaceAfterNonBlankState = 0w4
|
||||
val startNewline: Word8.word = 0w5
|
||||
val newlineToNewline: Word8.word = 0w6
|
||||
val chrToNewline: Word8.word = 0w07
|
||||
|
||||
fun makeStart i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if chr = #"\n" then startNewline
|
||||
else if Char.isSpace chr then startSpaceState
|
||||
else startNonBlankState
|
||||
end
|
||||
|
||||
fun makeStartNonBlankState i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if chr = #"\n" then chrToNewline
|
||||
else if Char.isSpace chr then spaceAfterNonBlankState
|
||||
else startNonBlankState
|
||||
end
|
||||
|
||||
fun makeStartSpace i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if chr = #"\n" then chrToNewline
|
||||
else if Char.isSpace chr then startSpaceState
|
||||
else nonBlankAfterSpaceState
|
||||
end
|
||||
|
||||
fun makeNonBlankAfterSpace i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if chr = #"\n" then chrToNewline
|
||||
else if Char.isSpace chr then spaceAfterNonBlankState
|
||||
else nonBlankAfterSpaceState
|
||||
end
|
||||
|
||||
fun makeStartNewline i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if chr = #"\n" then newlineToNewline
|
||||
else if Char.isSpace chr then startSpaceState
|
||||
else nonBlankAfterSpaceState
|
||||
end
|
||||
|
||||
val startTable = Vector.tabulate (255, makeStart)
|
||||
val startNonBlankTable = Vector.tabulate (255, makeStartNonBlankState)
|
||||
val startSpaceTable = Vector.tabulate (255, makeStartSpace)
|
||||
val nonBlankAfterSpaceTable = Vector.tabulate (255, makeNonBlankAfterSpace)
|
||||
val spaceAfterNonBlankTable = nonBlankAfterSpaceTable
|
||||
val newlineTable = Vector.tabulate (255, makeStartNewline)
|
||||
|
||||
val tables =
|
||||
#[ startTable
|
||||
, startNonBlankTable
|
||||
, startSpaceTable
|
||||
, nonBlankAfterSpaceTable
|
||||
, spaceAfterNonBlankTable
|
||||
, newlineTable
|
||||
, newlineTable
|
||||
, newlineTable
|
||||
]
|
||||
|
||||
structure StartOfNextWORD =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun finish x = x
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = nonBlankAfterSpaceState
|
||||
orelse currentState = newlineToNewline
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
structure StartOfNextWORDForDelete =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun finish x = x
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = nonBlankAfterSpaceState
|
||||
orelse currentState = chrToNewline
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
structure EndOfPrevWORD =
|
||||
MakePrevDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderPrev
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun finish x = x
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = nonBlankAfterSpaceState
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldPrev
|
||||
end)
|
||||
|
||||
structure StartOfCurrentWORDFolder =
|
||||
MakeCharFolderPrev
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = spaceAfterNonBlankState
|
||||
orelse currentState = chrToNewline
|
||||
orelse currentState = newlineToNewline
|
||||
|
||||
fun finish idx = idx + 1
|
||||
end)
|
||||
|
||||
structure StartOfCurrentWORD =
|
||||
MakePrevDfaLoopMinus1
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = StartOfCurrentWORDFolder.foldPrev
|
||||
end)
|
||||
|
||||
structure StartOfNextWORDStrict =
|
||||
MakePrevDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = StartOfCurrentWORDFolder.foldPrev
|
||||
end)
|
||||
|
||||
fun endOfCurrentWORDFolderIsFinal currentState =
|
||||
currentState = spaceAfterNonBlankState orelse currentState = chrToNewline
|
||||
|
||||
structure EndOfCurrentWORDFolder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
val isFinal = endOfCurrentWORDFolderIsFinal
|
||||
|
||||
fun finish idx =
|
||||
Int.max (0, idx - 1)
|
||||
end)
|
||||
|
||||
structure EndOfCurrentWORD =
|
||||
MakeNextDfaLoopPlus1
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = EndOfCurrentWORDFolder.foldNext
|
||||
end)
|
||||
|
||||
structure EndOfCurrentWORDForDelete =
|
||||
MakeNextDfaLoopPlus1
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
val isFinal = endOfCurrentWORDFolderIsFinal
|
||||
|
||||
fun finish idx = idx
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
structure EndOfCurrentWORDStrict =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = EndOfCurrentWORDFolder.foldNext
|
||||
end)
|
||||
|
||||
(* W *)
|
||||
val startOfNextWORD = StartOfNextWORD.next
|
||||
val startOfNextWORDForDelete = StartOfNextWORDForDelete.next
|
||||
(* gE *)
|
||||
val endOfPrevWORD = EndOfPrevWORD.prev
|
||||
(* B *)
|
||||
val startOfCurrentWORD = StartOfCurrentWORD.prev
|
||||
(* E *)
|
||||
val endOfCurrentWORD = EndOfCurrentWORD.next
|
||||
val endOfCurrentWORDForDelete = EndOfCurrentWORDForDelete.next
|
||||
|
||||
(* functions to strictly get the start and end of the current word.
|
||||
* Problem: We want to support Vi motions like viW (selects a single word),
|
||||
* as well as ciW (change one WORD) and diW (delete one WORD).
|
||||
*
|
||||
* The 'startOfCurrentWORD' and 'endOfCurrentWORD' functions do this
|
||||
* (representing the vi 'B' and 'E' commands respectively),
|
||||
* except that 'B' goes to the previous WORD if the cursor is on the first
|
||||
* character of the current WORD, and 'E' goes to the next WORD if the cursor
|
||||
* is on the last character of the current WORD.
|
||||
*
|
||||
* What is meant by "strict" is that these below functions always stay
|
||||
* within the current WORD, not making the two exceptions mentioned above.
|
||||
*)
|
||||
val startOfCurrentWORDStrict = StartOfNextWORDStrict.prev
|
||||
val endOfCurrentWORDStrict = EndOfCurrentWORDStrict.next
|
||||
end
|
||||
64
shf/fcore/cursor-dfa/vi-dlr-dfa.sml
Normal file
64
shf/fcore/cursor-dfa/vi-dlr-dfa.sml
Normal file
@@ -0,0 +1,64 @@
|
||||
structure ViDlrDfa =
|
||||
struct
|
||||
val startState: Word8.word = 0w0
|
||||
val newlineState: Word8.word = 0w1
|
||||
val notNewlineState = 0w2
|
||||
|
||||
fun makeStart i =
|
||||
if Char.chr i = #"\n" then newlineState else notNewlineState
|
||||
|
||||
val startTable = Vector.tabulate (255, makeStart)
|
||||
val newlineTable = startTable
|
||||
val notNewlineTable = startTable
|
||||
|
||||
val tables = #[startTable, newlineTable, notNewlineTable]
|
||||
|
||||
fun isFinal currentState = currentState = newlineState
|
||||
|
||||
structure ViDlr =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun finish x = x - 1
|
||||
val isFinal = isFinal
|
||||
end)
|
||||
|
||||
fun fStart (idx, absIdx, str, tl, currentState, counter) =
|
||||
if String.sub (str, idx) = #"\n" then
|
||||
if counter = 1 then
|
||||
absIdx
|
||||
else
|
||||
Folder.foldNext
|
||||
(idx + 1, absIdx + 1, str, tl, currentState, counter - 1)
|
||||
else
|
||||
Folder.foldNext (idx, absIdx, str, tl, currentState, counter)
|
||||
end)
|
||||
|
||||
structure ViDlrForDelete =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun finish x = x + 1
|
||||
val isFinal = isFinal
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
val next = ViDlr.next
|
||||
val nextForDelete = ViDlrForDelete.next
|
||||
end
|
||||
76
shf/fcore/cursor-dfa/vi-h-dfa.sml
Normal file
76
shf/fcore/cursor-dfa/vi-h-dfa.sml
Normal file
@@ -0,0 +1,76 @@
|
||||
structure ViHDfa =
|
||||
struct
|
||||
val startState: Word8.word = 0w0
|
||||
val oneNewlineState: Word8.word = 0w1
|
||||
val twoNewlineState: Word8.word = 0w2
|
||||
val chrState: Word8.word = 0w3
|
||||
val chrBeforeNewlieState: Word8.word = 0w4
|
||||
|
||||
fun makeStart i =
|
||||
if Char.chr i = #"\n" then oneNewlineState else chrState
|
||||
|
||||
fun makeOneNewline i =
|
||||
if Char.chr i = #"\n" then twoNewlineState else chrBeforeNewlieState
|
||||
|
||||
val startTable = Vector.tabulate (255, makeStart)
|
||||
val oneNewlineTable = Vector.tabulate (255, makeOneNewline)
|
||||
val twoNewlineTable = oneNewlineTable
|
||||
val chrTable = startTable
|
||||
val chrBeforeNewlieTable = startTable
|
||||
|
||||
val tables =
|
||||
#[ startTable
|
||||
, oneNewlineTable
|
||||
, twoNewlineTable
|
||||
, chrTable
|
||||
, chrBeforeNewlieTable
|
||||
]
|
||||
|
||||
fun next (currentState, chr) =
|
||||
let val table = Vector.sub (tables, Word8.toInt currentState)
|
||||
in Vector.sub (table, Char.ord chr)
|
||||
end
|
||||
|
||||
structure ViH =
|
||||
MakePrevDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
fun loop (idx, absIdx, str, tl, currentState, counter) =
|
||||
if idx < 0 then
|
||||
case tl of
|
||||
str :: tl =>
|
||||
loop
|
||||
(String.size str - 1, absIdx, str, tl, currentState, counter)
|
||||
| [] => 0
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, idx)
|
||||
val newState = next (currentState, chr)
|
||||
in
|
||||
if newState = chrBeforeNewlieState orelse newState = chrState then
|
||||
if counter - 1 = ~1 then
|
||||
absIdx
|
||||
else
|
||||
loop (idx - 1, absIdx - 1, str, tl, startState, counter - 1)
|
||||
else if newState = twoNewlineState then
|
||||
if counter - 1 = ~1 then
|
||||
absIdx + 1
|
||||
else
|
||||
loop
|
||||
( idx - 1
|
||||
, absIdx - 1
|
||||
, str
|
||||
, tl
|
||||
, oneNewlineState
|
||||
, counter - 1
|
||||
)
|
||||
else
|
||||
loop (idx - 1, absIdx - 1, str, tl, newState, counter)
|
||||
end
|
||||
|
||||
val fStart = loop
|
||||
end)
|
||||
|
||||
val prev = ViH.prev
|
||||
end
|
||||
53
shf/fcore/cursor-dfa/vi-l-dfa.sml
Normal file
53
shf/fcore/cursor-dfa/vi-l-dfa.sml
Normal file
@@ -0,0 +1,53 @@
|
||||
structure ViLDfa =
|
||||
struct
|
||||
val startState: Word8.word = 0w0
|
||||
val newlineState: Word8.word = 0w1
|
||||
val chrState: Word8.word = 0w2
|
||||
val newlineAfterCHrState: Word8.word = 0w3
|
||||
|
||||
fun makeStart i =
|
||||
if Char.chr i = #"\n" then newlineState else chrState
|
||||
|
||||
fun makeChr i =
|
||||
if Char.chr i = #"\n" then newlineAfterCHrState else chrState
|
||||
|
||||
val startTable = Vector.tabulate (255, makeStart)
|
||||
val newlineTable = startTable
|
||||
val chrTable = Vector.tabulate (255, makeChr)
|
||||
val newlineAfterCHrTable = startTable
|
||||
|
||||
val tables = #[startTable, newlineTable, chrTable, newlineAfterCHrTable]
|
||||
|
||||
fun next (currentState, chr) =
|
||||
let val table = Vector.sub (tables, Word8.toInt currentState)
|
||||
in Vector.sub (table, Char.ord chr)
|
||||
end
|
||||
|
||||
structure ViL =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
fun loop (idx, absIdx, str, tl, currentState, counter) =
|
||||
if idx = String.size str then
|
||||
case tl of
|
||||
str :: tl => loop (0, absIdx, str, tl, currentState, counter)
|
||||
| [] => absIdx
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, idx)
|
||||
val newState = next (currentState, chr)
|
||||
in
|
||||
if newState = newlineAfterCHrState then
|
||||
loop (idx + 1, absIdx + 1, str, tl, newState, counter)
|
||||
else if counter - 1 = ~1 then
|
||||
absIdx
|
||||
else
|
||||
loop (idx + 1, absIdx + 1, str, tl, newState, counter - 1)
|
||||
end
|
||||
|
||||
val fStart = loop
|
||||
end)
|
||||
|
||||
val next = ViL.next
|
||||
end
|
||||
285
shf/fcore/cursor-dfa/vi-word-dfa.sml
Normal file
285
shf/fcore/cursor-dfa/vi-word-dfa.sml
Normal file
@@ -0,0 +1,285 @@
|
||||
structure ViWordDfa =
|
||||
struct
|
||||
val startState: Word8.word = 0w0
|
||||
|
||||
val startAlpha: Word8.word = 0w1
|
||||
val startSpace: Word8.word = 0w2
|
||||
val startPunct: Word8.word = 0w3
|
||||
|
||||
val alphaToSpace: Word8.word = 0w4
|
||||
val punctToSpace: Word8.word = 0w5
|
||||
|
||||
val spaceToAlpha: Word8.word = 0w6
|
||||
val spaceToPunct: Word8.word = 0w7
|
||||
|
||||
val startNewline: Word8.word = 0w8
|
||||
val newlineToNewline: Word8.word = 0w9
|
||||
val chrToNewline: Word8.word = 0w10
|
||||
|
||||
val newlineToAlpha: Word8.word = 0w11
|
||||
val newlineToPunct: Word8.word = 0w12
|
||||
|
||||
val alphaToPunct: Word8.word = 0w13
|
||||
val punctToAlpha: Word8.word = 0w14
|
||||
|
||||
fun makeStart i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if Char.isAlphaNum chr orelse chr = #"_" then startAlpha
|
||||
else if chr = #"\n" then startNewline
|
||||
else if Char.isSpace chr then startSpace
|
||||
else startPunct
|
||||
end
|
||||
|
||||
fun makeStartAlpha i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if Char.isAlphaNum chr orelse chr = #"_" then startAlpha
|
||||
else if chr = #"\n" then chrToNewline
|
||||
else if Char.isSpace chr then alphaToSpace
|
||||
else alphaToPunct
|
||||
end
|
||||
|
||||
fun makeStartSpace i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if Char.isAlphaNum chr orelse chr = #"_" then spaceToAlpha
|
||||
else if chr = #"\n" then chrToNewline
|
||||
else if Char.isSpace chr then startSpace
|
||||
else spaceToPunct
|
||||
end
|
||||
|
||||
fun makeStartPunct i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if Char.isAlphaNum chr orelse chr = #"_" then punctToAlpha
|
||||
else if chr = #"\n" then chrToNewline
|
||||
else if Char.isSpace chr then punctToSpace
|
||||
else startPunct
|
||||
end
|
||||
|
||||
fun makeStartNewline i =
|
||||
let
|
||||
val chr = Char.chr i
|
||||
in
|
||||
if Char.isAlphaNum chr orelse chr = #"_" then newlineToAlpha
|
||||
else if chr = #"\n" then newlineToNewline
|
||||
else if Char.isSpace chr then startSpace
|
||||
else newlineToPunct
|
||||
end
|
||||
|
||||
val startTable = Vector.tabulate (255, makeStart)
|
||||
|
||||
val startAlphaTable = Vector.tabulate (255, makeStartAlpha)
|
||||
val startSpaceTable = Vector.tabulate (255, makeStartSpace)
|
||||
val startPunctTable = Vector.tabulate (255, makeStartPunct)
|
||||
|
||||
val alphaToSpaceTable = startSpaceTable
|
||||
val punctToSpaceTable = startSpaceTable
|
||||
|
||||
val spaceToAlphaTable = startAlphaTable
|
||||
val spaceToPunctTable = startPunctTable
|
||||
|
||||
val newlineTable = Vector.tabulate (255, makeStartNewline)
|
||||
|
||||
val tables =
|
||||
#[ startTable
|
||||
|
||||
, startAlphaTable
|
||||
, startSpaceTable
|
||||
, startPunctTable
|
||||
|
||||
, alphaToSpaceTable
|
||||
, punctToSpaceTable
|
||||
|
||||
, spaceToAlphaTable
|
||||
, spaceToPunctTable
|
||||
|
||||
, newlineTable
|
||||
, newlineTable
|
||||
, newlineTable
|
||||
|
||||
, startAlphaTable
|
||||
, startPunctTable
|
||||
]
|
||||
|
||||
structure StartOfNextWord =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = alphaToPunct orelse currentState = punctToAlpha
|
||||
orelse currentState = spaceToAlpha
|
||||
orelse currentState = spaceToPunct
|
||||
orelse currentState = newlineToNewline
|
||||
orelse currentState = newlineToAlpha
|
||||
orelse currentState = newlineToPunct
|
||||
|
||||
fun finish x = x
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
(* This is the same as StartOfNextWord, except for the `isFinal` function.
|
||||
* The difference is that the `isFinal` function here considers
|
||||
* the state where any character goes to a newline,
|
||||
* to be a final state.
|
||||
* This is because, in Vim, the 'w' motion will move past a newline
|
||||
* when that newline is preceded by a non-newline character.
|
||||
* However, the 'dw' motion deletes until that newline
|
||||
* (not including the newline itself).
|
||||
* It is easier, less fragile, and perhaps clearer,
|
||||
* to implement the difference using a transition table like this
|
||||
* than convoluted if-statements. *)
|
||||
structure StartOfNextWordForDelete =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = alphaToPunct orelse currentState = punctToAlpha
|
||||
orelse currentState = spaceToAlpha
|
||||
orelse currentState = spaceToPunct
|
||||
orelse currentState = chrToNewline
|
||||
|
||||
fun finish x = x
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
structure EndOfPrevWord =
|
||||
MakePrevDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderPrev
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = alphaToPunct orelse currentState = punctToAlpha
|
||||
orelse currentState = spaceToAlpha
|
||||
orelse currentState = spaceToPunct
|
||||
orelse currentState = newlineToNewline
|
||||
orelse currentState = newlineToAlpha
|
||||
orelse currentState = newlineToPunct
|
||||
|
||||
fun finish x = x
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldPrev
|
||||
end)
|
||||
|
||||
structure StartOfCurrentWordFolder =
|
||||
MakeCharFolderPrev
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
fun isFinal currentState =
|
||||
currentState = alphaToSpace orelse currentState = punctToSpace
|
||||
orelse currentState = alphaToPunct orelse currentState = punctToAlpha
|
||||
orelse currentState = chrToNewline
|
||||
orelse currentState = newlineToNewline
|
||||
|
||||
fun finish idx = idx + 1
|
||||
end)
|
||||
|
||||
structure StartOfCurrentWord =
|
||||
MakePrevDfaLoopMinus1
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = StartOfCurrentWordFolder.foldPrev
|
||||
end)
|
||||
|
||||
structure StartOfCurrentWordStrict =
|
||||
MakePrevDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = StartOfCurrentWordFolder.foldPrev
|
||||
end)
|
||||
|
||||
fun isFinalForEndOfCurrentWord currentState =
|
||||
currentState = alphaToSpace orelse currentState = punctToSpace
|
||||
orelse currentState = alphaToPunct orelse currentState = punctToAlpha
|
||||
orelse currentState = chrToNewline
|
||||
|
||||
structure EndOfCurrentWordFolder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
val isFinal = isFinalForEndOfCurrentWord
|
||||
fun finish x = x - 1
|
||||
end)
|
||||
|
||||
structure EndOfCurrentWord =
|
||||
MakeNextDfaLoopPlus1
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = EndOfCurrentWordFolder.foldNext
|
||||
end)
|
||||
|
||||
structure EndOfCurrentWordStrict =
|
||||
MakeNextDfaLoop
|
||||
(struct
|
||||
val startState = startState
|
||||
val fStart = EndOfCurrentWordFolder.foldNext
|
||||
end)
|
||||
|
||||
structure EndOfCurrentWordForDelete =
|
||||
MakeNextDfaLoopPlus1
|
||||
(struct
|
||||
val startState = startState
|
||||
|
||||
structure Folder =
|
||||
MakeCharFolderNext
|
||||
(struct
|
||||
val startState = startState
|
||||
val tables = tables
|
||||
|
||||
val isFinal = isFinalForEndOfCurrentWord
|
||||
fun finish x = x
|
||||
end)
|
||||
|
||||
val fStart = Folder.foldNext
|
||||
end)
|
||||
|
||||
(* w *)
|
||||
val startOfNextWord = StartOfNextWord.next
|
||||
val startOfNextWordForDelete = StartOfNextWordForDelete.next
|
||||
(* ge *)
|
||||
val endOfPrevWord = EndOfPrevWord.prev
|
||||
(* b *)
|
||||
val startOfCurrentWord = StartOfCurrentWord.prev
|
||||
(* e *)
|
||||
val endOfCurrentWord = EndOfCurrentWord.next
|
||||
val endOfCurrentWordForDelete = EndOfCurrentWordForDelete.next
|
||||
|
||||
(* the meaning of "Strict" and the utility of these two functions
|
||||
* is described in vi-WORD-dfa.sml *)
|
||||
val startOfCurrentWordStrict = StartOfCurrentWordStrict.prev
|
||||
val endOfCurrentWordStrict = EndOfCurrentWordStrict.next
|
||||
end
|
||||
1028
shf/fcore/cursor.sml
Normal file
1028
shf/fcore/cursor.sml
Normal file
File diff suppressed because it is too large
Load Diff
103
shf/fcore/move.sml
Normal file
103
shf/fcore/move.sml
Normal file
@@ -0,0 +1,103 @@
|
||||
signature MOVE =
|
||||
sig
|
||||
val fMove: LineGap.t * int -> int
|
||||
end
|
||||
|
||||
signature MAKE_MOVE =
|
||||
sig
|
||||
val move: AppType.app_type * int -> AppType.app_type
|
||||
end
|
||||
|
||||
functor MakeMove(Fn: MOVE): MAKE_MOVE =
|
||||
struct
|
||||
fun finish (app: AppType.app_type, buffer, cursorIdx) =
|
||||
let
|
||||
val {searchList, bufferModifyTime, ...} = app
|
||||
in
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun helpMove (app: AppType.app_type, buffer, cursorIdx, count) =
|
||||
if count = 0 then
|
||||
finish (app, buffer, cursorIdx)
|
||||
else
|
||||
(* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *)
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val textLength = #textLength buffer
|
||||
val newCursorIdx = Fn.fMove (buffer, cursorIdx)
|
||||
in
|
||||
if newCursorIdx >= textLength - 1 then
|
||||
let val newCursorIdx = Int.max (textLength - 1, 0)
|
||||
in finish (app, buffer, newCursorIdx)
|
||||
end
|
||||
else
|
||||
helpMove (app, buffer, newCursorIdx, count - 1)
|
||||
end
|
||||
|
||||
fun move (app: AppType.app_type, count) =
|
||||
let val {cursorIdx, buffer, ...} = app
|
||||
in helpMove (app, buffer, cursorIdx, count)
|
||||
end
|
||||
end
|
||||
|
||||
structure MoveToStartOfLine = MakeMove (struct val fMove = Cursor.vi0 end)
|
||||
|
||||
signature DFA_MOVE =
|
||||
sig
|
||||
val fMove: LineGap.t * int * int -> int
|
||||
end
|
||||
|
||||
signature MAKE_DFA_MOVE =
|
||||
sig
|
||||
val move: AppType.app_type * int -> AppType.app_type
|
||||
end
|
||||
|
||||
functor MakeDfaMove(Fn: DFA_MOVE): MAKE_DFA_MOVE =
|
||||
struct
|
||||
fun move (app: AppType.app_type, count) : AppType.app_type =
|
||||
let
|
||||
val {buffer, cursorIdx, searchList, bufferModifyTime, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorIdx = Fn.fMove (buffer, cursorIdx, count)
|
||||
|
||||
val textLength = #textLength buffer
|
||||
in
|
||||
if cursorIdx >= textLength - 1 then
|
||||
let
|
||||
val cursorIdx = Int.max (textLength - 1, 0)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorIdx =
|
||||
if Cursor.isOnNewlineAfterChr (buffer, cursorIdx) then cursorIdx - 1
|
||||
else cursorIdx
|
||||
in
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
end
|
||||
|
||||
structure MoveViH = MakeDfaMove (struct val fMove = Cursor.viH end)
|
||||
structure MoveViL = MakeDfaMove (struct val fMove = Cursor.viL end)
|
||||
|
||||
structure MoveToNextWord = MakeDfaMove (struct val fMove = Cursor.nextWord end)
|
||||
structure MoveToNextWORD = MakeDfaMove (struct val fMove = Cursor.nextWORD end)
|
||||
|
||||
structure MoveToEndOfWord =
|
||||
MakeDfaMove (struct val fMove = Cursor.endOfWord end)
|
||||
structure MoveToEndOfWORD =
|
||||
MakeDfaMove (struct val fMove = Cursor.endOfWORD end)
|
||||
|
||||
structure MoveToPrevWord = MakeDfaMove (struct val fMove = Cursor.prevWord end)
|
||||
structure MoveToPrevWORD = MakeDfaMove (struct val fMove = Cursor.prevWORD end)
|
||||
|
||||
structure MoveToEndOfPrevWord =
|
||||
MakeDfaMove (struct val fMove = Cursor.endOfPrevWord end)
|
||||
structure MoveToEndOfPrevWORD =
|
||||
MakeDfaMove (struct val fMove = Cursor.endOfPrevWORD end)
|
||||
|
||||
structure MoveToEndOfLine = MakeDfaMove (struct val fMove = Cursor.viDlr end)
|
||||
1298
shf/fcore/normal-mode/make-normal-delete.sml
Normal file
1298
shf/fcore/normal-mode/make-normal-delete.sml
Normal file
File diff suppressed because it is too large
Load Diff
5
shf/fcore/normal-mode/normal-delete.sml
Normal file
5
shf/fcore/normal-mode/normal-delete.sml
Normal file
@@ -0,0 +1,5 @@
|
||||
structure NormalDelete =
|
||||
MakeNormalDelete
|
||||
(struct
|
||||
fun initMsgs _ = []
|
||||
end)
|
||||
155
shf/fcore/normal-mode/normal-finish.sml
Normal file
155
shf/fcore/normal-mode/normal-finish.sml
Normal file
@@ -0,0 +1,155 @@
|
||||
structure NormalFinish =
|
||||
struct
|
||||
open AppType
|
||||
|
||||
open MailboxType
|
||||
open DrawMsg
|
||||
open InputMsg
|
||||
|
||||
fun clearMode app =
|
||||
NormalModeWith.mode (app, NORMAL_MODE "", [])
|
||||
|
||||
fun buildTextAndClear
|
||||
(app: app_type, buffer, cursorIdx, searchList, msgs, bufferModifyTime) =
|
||||
let
|
||||
val
|
||||
{ windowWidth
|
||||
, windowHeight
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, startLine = prevLineNumber
|
||||
, ...
|
||||
} = app
|
||||
|
||||
(* calculate new scroll column and start line, if there are any changes *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val visualScrollColumn =
|
||||
TextScroll.getScrollColumn
|
||||
(buffer, cursorIdx, windowWidth, prevScrollColumn)
|
||||
|
||||
val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val startLine =
|
||||
TextScroll.getStartLine
|
||||
(prevLineNumber, cursorLine, windowHeight, #lineLength buffer)
|
||||
|
||||
(* move buffer to new startLine as required by TextBuilder.build *)
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DRAW_TEXT drawMsg
|
||||
val msgs = DRAW drawMsg :: msgs
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, cursorIdx
|
||||
, mode
|
||||
, startLine
|
||||
, searchList
|
||||
, msgs
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
)
|
||||
end
|
||||
|
||||
fun resizeText (app: app_type, newWidth, newHeight) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, cursorIdx
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val newBuffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val visualScrollColumn =
|
||||
TextScroll.getScrollColumn
|
||||
(newBuffer, cursorIdx, newWidth, prevScrollColumn)
|
||||
val newBuffer = LineGap.goToLine (startLine, newBuffer)
|
||||
val lineIdx = TextBuilderUtils.getLineAbsIdxFromBuffer (startLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, newWidth
|
||||
, newHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DRAW_TEXT drawMsg
|
||||
val msgs = [DRAW drawMsg]
|
||||
in
|
||||
NormalModeWith.bufferAndSize
|
||||
( app
|
||||
, newBuffer
|
||||
, newWidth
|
||||
, newHeight
|
||||
, searchList
|
||||
, msgs
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
)
|
||||
end
|
||||
|
||||
fun centreToCursor (app: app_type) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine = prevLineNumber
|
||||
, cursorIdx
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val startLine = TextScroll.getLineCentre (cursorLine, windowHeight)
|
||||
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DRAW_TEXT drawMsg
|
||||
val drawMsg = [DRAW drawMsg]
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, cursorIdx
|
||||
, NORMAL_MODE ""
|
||||
, startLine
|
||||
, searchList
|
||||
, drawMsg
|
||||
, bufferModifyTime
|
||||
, #visualScrollColumn app
|
||||
)
|
||||
end
|
||||
end
|
||||
203
shf/fcore/normal-mode/normal-mode-with.sml
Normal file
203
shf/fcore/normal-mode/normal-mode-with.sml
Normal file
@@ -0,0 +1,203 @@
|
||||
structure NormalModeWith =
|
||||
struct
|
||||
open AppType
|
||||
|
||||
fun bufferMsgsAndMode (app: app_type, newBuffer, newMsgs, newMode) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, buffer = _
|
||||
, msgs = _
|
||||
, bufferModifyTime
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, startLine
|
||||
, cursorIdx
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, buffer = newBuffer
|
||||
, msgs = newMsgs
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, searchList = searchList
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, startLine = startLine
|
||||
, cursorIdx = cursorIdx
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun bufferAndSize
|
||||
( app: app_type
|
||||
, newBuffer
|
||||
, newWidth
|
||||
, newHeight
|
||||
, newSearchList
|
||||
, newMsgs
|
||||
, newBufferModifyTime
|
||||
, newVisualScrollColumn
|
||||
) =
|
||||
let
|
||||
val
|
||||
{ mode
|
||||
, buffer = _
|
||||
, bufferModifyTime = _
|
||||
, windowWidth = _
|
||||
, windowHeight = _
|
||||
, searchList = _
|
||||
, visualScrollColumn = _
|
||||
, msgs = _
|
||||
, startLine
|
||||
, cursorIdx
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = mode
|
||||
, buffer = newBuffer
|
||||
, bufferModifyTime = newBufferModifyTime
|
||||
, windowWidth = newWidth
|
||||
, windowHeight = newHeight
|
||||
, searchList = newSearchList
|
||||
, visualScrollColumn = newVisualScrollColumn
|
||||
, msgs = newMsgs
|
||||
, startLine = startLine
|
||||
, cursorIdx = cursorIdx
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun bufferAndCursorIdx
|
||||
( app: app_type
|
||||
, newBuffer
|
||||
, newCursorIdx
|
||||
, newMode
|
||||
, newStartLine
|
||||
, newSearchList
|
||||
, newMsgs
|
||||
, newBufferModifyTime
|
||||
, newVisualScrollColumn
|
||||
) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, buffer = _
|
||||
, bufferModifyTime = _
|
||||
, cursorIdx = _
|
||||
, startLine = _
|
||||
, searchList = _
|
||||
, visualScrollColumn = _
|
||||
, msgs = _
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, buffer = newBuffer
|
||||
, bufferModifyTime = newBufferModifyTime
|
||||
, cursorIdx = newCursorIdx
|
||||
, startLine = newStartLine
|
||||
, searchList = newSearchList
|
||||
, visualScrollColumn = newVisualScrollColumn
|
||||
, msgs = newMsgs
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun mode (app: app_type, newMode, newMsgs) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, msgs = _
|
||||
, buffer
|
||||
, bufferModifyTime
|
||||
, searchList
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, visualScrollColumn
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, msgs = newMsgs
|
||||
, buffer = buffer
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, searchList = searchList
|
||||
, cursorIdx = cursorIdx
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, startLine = startLine
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun modeAndBuffer (app: app_type, newBuffer, newMode, newMsgs) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, msgs = _
|
||||
, buffer = _
|
||||
, bufferModifyTime
|
||||
, searchList
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, visualScrollColumn
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, msgs = newMsgs
|
||||
, buffer = newBuffer
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, searchList = searchList
|
||||
, cursorIdx = cursorIdx
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, startLine = startLine
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun searchList (app: app_type, newSearchList, newBuffer, newBufferModifyTime) =
|
||||
let
|
||||
val
|
||||
{ searchList = _
|
||||
, buffer = _
|
||||
, bufferModifyTime
|
||||
, msgs
|
||||
, mode
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, visualScrollColumn
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ searchList = newSearchList
|
||||
, buffer = newBuffer
|
||||
, bufferModifyTime = newBufferModifyTime
|
||||
, msgs = msgs
|
||||
, mode = mode
|
||||
, cursorIdx = cursorIdx
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, startLine = startLine
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
end
|
||||
693
shf/fcore/normal-mode/normal-mode.sml
Normal file
693
shf/fcore/normal-mode/normal-mode.sml
Normal file
@@ -0,0 +1,693 @@
|
||||
structure NormalMode =
|
||||
struct
|
||||
(* parsing functions, deciding what to do while we are in normal mode *)
|
||||
|
||||
open AppType
|
||||
open InputMsg
|
||||
|
||||
fun switchToNormalSearchMode (app: app_type, caseSensitive) =
|
||||
NormalSearchFinish.onSearchChanged
|
||||
(app, "", PersistentVector.empty, 0, 0, caseSensitive, #buffer app)
|
||||
|
||||
fun getNumLength (pos, str) =
|
||||
if pos = String.size str then
|
||||
pos
|
||||
else
|
||||
let val chr = String.sub (str, pos)
|
||||
in if Char.isDigit chr then getNumLength (pos + 1, str) else pos
|
||||
end
|
||||
|
||||
fun appendChr (app: app_type, chr, str) =
|
||||
let
|
||||
val str = str ^ Char.toString chr
|
||||
val mode = NORMAL_MODE str
|
||||
in
|
||||
NormalModeWith.mode (app, mode, [])
|
||||
end
|
||||
|
||||
local
|
||||
fun loop (app: app_type, cursorIdx, buffer, searchList, count, time) =
|
||||
if count = 0 then
|
||||
NormalDelete.finishAfterDeletingBuffer
|
||||
(app, #cursorIdx app, buffer, searchList, time, [])
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val lineStart = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
val (buffer, searchList) = SearchList.insert
|
||||
(lineStart, " ", buffer, searchList, #dfa app)
|
||||
|
||||
val buffer = LineGap.goToIdx (lineStart, buffer)
|
||||
val lineEnd = Cursor.viDlr (buffer, lineStart, 1)
|
||||
val buffer = LineGap.goToIdx (lineEnd, buffer)
|
||||
val nextLine = Cursor.viL (buffer, lineEnd, 1)
|
||||
|
||||
val count = if lineEnd = nextLine then 0 else count - 1
|
||||
in
|
||||
loop (app, nextLine, buffer, searchList, count, time)
|
||||
end
|
||||
in
|
||||
fun indnetLine (app: app_type, count, time) =
|
||||
let val {buffer, searchList, cursorIdx, ...} = app
|
||||
in loop (app, cursorIdx, buffer, searchList, count, time)
|
||||
end
|
||||
end
|
||||
|
||||
local
|
||||
fun loop (cursorIdx, buffer, searchList, dfa, count) =
|
||||
if count = 0 then
|
||||
(buffer, searchList)
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val lineStart = Cursor.vi0 (buffer, cursorIdx)
|
||||
val firstNonSpaceChr = Cursor.firstNonSpaceChr (buffer, lineStart)
|
||||
|
||||
(* delete from buffer *)
|
||||
val difference = firstNonSpaceChr - lineStart
|
||||
val deleteLength = Int.min (difference, 2)
|
||||
val (buffer, searchList) =
|
||||
if difference = 0 then
|
||||
(* can't dedent as there is no leading space *)
|
||||
(buffer, searchList)
|
||||
else
|
||||
SearchList.deleteBufferAndSearchList
|
||||
(lineStart, deleteLength, buffer, searchList, dfa)
|
||||
|
||||
(* get next line to dedent *)
|
||||
val buffer = LineGap.goToIdx (lineStart, buffer)
|
||||
val lineEnd = Cursor.viDlr (buffer, lineStart, 1)
|
||||
val buffer = LineGap.goToIdx (lineEnd, buffer)
|
||||
val nextLine = Cursor.viL (buffer, lineEnd, 1)
|
||||
|
||||
val count = if lineEnd = nextLine then 0 else count - 1
|
||||
in
|
||||
loop (nextLine, buffer, searchList, dfa, count)
|
||||
end
|
||||
in
|
||||
fun dedentLine (app: app_type, count, time) =
|
||||
let
|
||||
open MailboxType
|
||||
|
||||
val {cursorIdx, buffer, searchList, dfa, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val lineStart = Cursor.vi0 (buffer, cursorIdx)
|
||||
val firstNonSpaceChr = Cursor.firstNonSpaceChr (buffer, lineStart)
|
||||
|
||||
(* calculate length to delete *)
|
||||
val difference = firstNonSpaceChr - lineStart
|
||||
val deleteLength = Int.min (difference, 2)
|
||||
|
||||
(* delete once *)
|
||||
val (buffer, searchList) =
|
||||
if deleteLength = 0 then
|
||||
(buffer, searchList)
|
||||
else
|
||||
SearchList.deleteBufferAndSearchList
|
||||
(lineStart, deleteLength, buffer, searchList, dfa)
|
||||
|
||||
(* Calculate nextLine and newCursorIdx.
|
||||
* The cursorIdx might be past the current line after we dedent.
|
||||
* If it is, we put the cursorIdx at the last char of the line. *)
|
||||
val buffer = LineGap.goToIdx (lineStart, buffer)
|
||||
val lineEnd = Cursor.viDlr (buffer, lineStart, 1)
|
||||
val buffer = LineGap.goToIdx (lineEnd, buffer)
|
||||
val nextLine = Cursor.viL (buffer, lineEnd, 1)
|
||||
val newCursorIdx = Int.min (lineEnd, cursorIdx)
|
||||
|
||||
val (buffer, searchList) =
|
||||
if lineEnd = nextLine then
|
||||
(* at end of file, so we cannot dedent anymore *)
|
||||
(buffer, searchList)
|
||||
else
|
||||
(* dedent remaining lines specified by count *)
|
||||
loop (nextLine, buffer, searchList, dfa, count - 1)
|
||||
|
||||
val buffer = LineGap.goToStart buffer
|
||||
in
|
||||
NormalDelete.finishAfterDeletingBuffer
|
||||
(app, newCursorIdx, buffer, searchList, time, [])
|
||||
end
|
||||
end
|
||||
|
||||
fun parseGo (count, app, chrCmd) =
|
||||
case chrCmd of
|
||||
#"e" => MoveToEndOfPrevWord.move (app, count)
|
||||
| #"E" => MoveToEndOfPrevWORD.move (app, count)
|
||||
| #"g" => NormalMove.moveToStart app
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseChr (app: app_type, count, chr, str, time) =
|
||||
case chr of
|
||||
#"h" => MoveViH.move (app, count)
|
||||
| #"j" => NormalMove.moveCursorDown (app, count)
|
||||
| #"k" => NormalMove.moveCursorUp (app, count)
|
||||
| #"l" => MoveViL.move (app, count)
|
||||
| #"w" => MoveToNextWord.move (app, count)
|
||||
| #"W" => MoveToNextWORD.move (app, count)
|
||||
| #"b" => MoveToPrevWord.move (app, count)
|
||||
| #"B" => MoveToPrevWORD.move (app, count)
|
||||
| #"e" => MoveToEndOfWord.move (app, count)
|
||||
| #"E" => MoveToEndOfWORD.move (app, count)
|
||||
| #"n" => NormalMove.moveToNextMatch (app, count)
|
||||
| #"N" => NormalMove.moveToPrevMatch (app, count)
|
||||
| #"z" => NormalFinish.centreToCursor app
|
||||
(* can only move to start or end of line once
|
||||
* so hardcode count as 1 *)
|
||||
| #"0" =>
|
||||
(* 0 is a bit of a special case.
|
||||
* If 0 is pressed without any preceding characters,
|
||||
* then it should move cursor to the start of the line.
|
||||
* However, if a number was pressed previously before 0 was,
|
||||
* then this means user is entering a count.
|
||||
* In that case, we append 0 to the string. *)
|
||||
if String.size str > 0 then
|
||||
let
|
||||
val lastChr = String.sub (str, String.size str - 1)
|
||||
in
|
||||
if Char.isDigit lastChr then
|
||||
let
|
||||
val chr = Char.toString chr
|
||||
val str = str ^ chr
|
||||
val mode = NORMAL_MODE str
|
||||
in
|
||||
NormalModeWith.mode (app, mode, [])
|
||||
end
|
||||
else
|
||||
MoveToStartOfLine.move (app, 1)
|
||||
end
|
||||
else
|
||||
MoveToStartOfLine.move (app, 1)
|
||||
| #"$" => MoveToEndOfLine.move (app, 1)
|
||||
| #"^" => NormalMove.firstNonSpaceChr app
|
||||
| #"G" =>
|
||||
(* if str has a size larger than 0,
|
||||
* interpret as "go to line" command;
|
||||
* else, interpret as a command to move to end *)
|
||||
if String.size str = 0 then NormalMove.moveToEnd app
|
||||
else NormalMove.moveToLine (app, count)
|
||||
| #"%" => NormalMove.moveToMatchingPair app
|
||||
| #"D" => NormalDelete.deleteToEndOfLine (app, time)
|
||||
| #"x" => NormalDelete.removeChr (app, count, time)
|
||||
| #"J" => NormalDelete.removeLineBreaks (app, count, time)
|
||||
| #"/" => switchToNormalSearchMode (app, false)
|
||||
| #"?" => switchToNormalSearchMode (app, true)
|
||||
| #">" => indnetLine (app, count, time)
|
||||
| #"<" => dedentLine (app, count, time)
|
||||
(* multi-char commands which can be appended *)
|
||||
| #"t" => appendChr (app, chr, str)
|
||||
| #"T" => appendChr (app, chr, str)
|
||||
| #"y" => appendChr (app, chr, str)
|
||||
| #"d" => appendChr (app, chr, str)
|
||||
| #"f" => appendChr (app, chr, str)
|
||||
| #"F" => appendChr (app, chr, str)
|
||||
| #"g" => appendChr (app, chr, str)
|
||||
| #"c" => appendChr (app, chr, str)
|
||||
| _ =>
|
||||
(* user may be entering a cmd with more than one chr
|
||||
* such as "2dw" to delete two word
|
||||
* so add current chr to mode, and save it in the app state *)
|
||||
let
|
||||
val str = if Char.isDigit chr then str ^ Char.toString chr else ""
|
||||
val mode = NORMAL_MODE str
|
||||
in
|
||||
NormalModeWith.mode (app, mode, [])
|
||||
end
|
||||
|
||||
structure ParseDelete =
|
||||
struct
|
||||
fun parseDeleteInside (app, chr, time) =
|
||||
case chr of
|
||||
#"w" => NormalDelete.deleteInsideWord (app, time)
|
||||
| #"W" => NormalDelete.deleteInsideWORD (app, time)
|
||||
|
||||
| #"(" => NormalDelete.deleteInsidePair (app, #"(", #")", time)
|
||||
| #")" => NormalDelete.deleteInsidePair (app, #"(", #")", time)
|
||||
|
||||
| #"[" => NormalDelete.deleteInsidePair (app, #"[", #"]", time)
|
||||
| #"]" => NormalDelete.deleteInsidePair (app, #"[", #"]", time)
|
||||
|
||||
| #"{" => NormalDelete.deleteInsidePair (app, #"{", #"}", time)
|
||||
| #"}" => NormalDelete.deleteInsidePair (app, #"{", #"}", time)
|
||||
|
||||
| #"<" => NormalDelete.deleteInsidePair (app, #"<", #">", time)
|
||||
| #">" => NormalDelete.deleteInsidePair (app, #"<", #">", time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDeleteAround (app, chr, time) =
|
||||
case chr of
|
||||
#"w" => NormalDelete.deleteAroundWord (app, time)
|
||||
| #"W" => NormalDelete.deleteAroundWORD (app, time)
|
||||
|
||||
|
||||
| #"(" => NormalDelete.deleteAroundPair (app, #"(", #")", time)
|
||||
| #")" => NormalDelete.deleteAroundPair (app, #"(", #")", time)
|
||||
|
||||
| #"[" => NormalDelete.deleteAroundPair (app, #"[", #"]", time)
|
||||
| #"]" => NormalDelete.deleteAroundPair (app, #"[", #"]", time)
|
||||
|
||||
| #"{" => NormalDelete.deleteAroundPair (app, #"{", #"}", time)
|
||||
| #"}" => NormalDelete.deleteAroundPair (app, #"{", #"}", time)
|
||||
|
||||
| #"<" => NormalDelete.deleteAroundPair (app, #"<", #">", time)
|
||||
| #">" => NormalDelete.deleteAroundPair (app, #"<", #">", time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDeleteTerminal (str, count, app, chrCmd, time) =
|
||||
case chrCmd of
|
||||
(* terminal commands: require no input after *)
|
||||
#"h" => NormalDelete.deleteCharsLeft (app, count, time)
|
||||
| #"l" => NormalDelete.removeChr (app, count, time)
|
||||
(* vi's 'j' and 'k' commands move up or down a column
|
||||
* but 'dj' or 'dk' delete whole lines
|
||||
* so their implementation differs from
|
||||
* other cursor motions *)
|
||||
| #"j" => NormalDelete.deleteLineDown (app, count, time)
|
||||
| #"k" => NormalDelete.deleteLineUp (app, count, time)
|
||||
| #"w" => NormalDelete.deleteWord (app, count, time)
|
||||
| #"W" => NormalDelete.deleteWORD (app, count, time)
|
||||
| #"b" => NormalDelete.deleteByDfa (app, count, Cursor.prevWord, time)
|
||||
| #"B" => NormalDelete.deleteByDfa (app, count, Cursor.prevWORD, time)
|
||||
| #"e" =>
|
||||
NormalDelete.deleteByDfa (app, count, Cursor.endOfWordForDelete, time)
|
||||
| #"E" =>
|
||||
NormalDelete.deleteByDfa (app, count, Cursor.endOfWORDForDelete, time)
|
||||
| #"0" => NormalDelete.delete (app, 1, Cursor.vi0, time)
|
||||
| #"$" => NormalDelete.deleteToEndOfLine (app, time)
|
||||
| #"^" => NormalDelete.deleteToFirstNonSpaceChr (app, time)
|
||||
| #"d" => NormalDelete.deleteLine (app, count, time)
|
||||
| #"n" => NormalDelete.deleteToNextMatch (app, count, time)
|
||||
| #"N" => NormalDelete.deleteToPrevMatch (app, count, time)
|
||||
| #"%" => NormalDelete.deletePair (app, time)
|
||||
| #"G" => NormalDelete.deleteToEnd (app, time)
|
||||
(* non-terminal commands which require appending chr *)
|
||||
| #"t" => appendChr (app, chrCmd, str)
|
||||
| #"T" => appendChr (app, chrCmd, str)
|
||||
| #"f" => appendChr (app, chrCmd, str)
|
||||
| #"F" => appendChr (app, chrCmd, str)
|
||||
| #"g" => appendChr (app, chrCmd, str)
|
||||
| #"i" => appendChr (app, chrCmd, str)
|
||||
| #"a" => appendChr (app, chrCmd, str)
|
||||
(* invalid command: reset mode *)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDeleteGo (app, count, chrCmd, time) =
|
||||
case chrCmd of
|
||||
#"e" => NormalDelete.deleteToEndOfPrevWord (app, count, time)
|
||||
| #"E" => NormalDelete.deleteToEndOfPrevWORD (app, count, time)
|
||||
| #"g" => NormalDelete.deleteToStart (app, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDelete (strPos, str, count, app, chrCmd, time) =
|
||||
if strPos = String.size str - 1 then
|
||||
parseDeleteTerminal (str, count, app, chrCmd, time)
|
||||
else
|
||||
(* have to continue parsing string *)
|
||||
case String.sub (str, strPos + 1) of
|
||||
#"t" => NormalDelete.deleteTillNextChr (app, count, chrCmd, time)
|
||||
| #"T" => NormalDelete.deleteTillPrevChr (app, count, chrCmd, time)
|
||||
| #"f" => NormalDelete.deleteToNextChr (app, count, chrCmd, time)
|
||||
| #"F" => NormalDelete.deleteToPrevChr (app, count, chrCmd, time)
|
||||
| #"g" => parseDeleteGo (app, count, chrCmd, time)
|
||||
| #"i" => parseDeleteInside (app, chrCmd, time)
|
||||
| #"a" => parseDeleteAround (app, chrCmd, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
end
|
||||
|
||||
structure ParseYankDelete =
|
||||
struct
|
||||
fun parseDeleteInside (app, chr, time) =
|
||||
case chr of
|
||||
#"w" => NormalYankDelete.deleteInsideWord (app, time)
|
||||
| #"W" => NormalYankDelete.deleteInsideWORD (app, time)
|
||||
|
||||
| #"(" => NormalYankDelete.deleteInsidePair (app, #"(", #")", time)
|
||||
| #")" => NormalYankDelete.deleteInsidePair (app, #"(", #")", time)
|
||||
|
||||
| #"[" => NormalYankDelete.deleteInsidePair (app, #"[", #"]", time)
|
||||
| #"]" => NormalYankDelete.deleteInsidePair (app, #"[", #"]", time)
|
||||
|
||||
| #"{" => NormalYankDelete.deleteInsidePair (app, #"{", #"}", time)
|
||||
| #"}" => NormalYankDelete.deleteInsidePair (app, #"{", #"}", time)
|
||||
|
||||
| #"<" => NormalYankDelete.deleteInsidePair (app, #"<", #">", time)
|
||||
| #">" => NormalYankDelete.deleteInsidePair (app, #"<", #">", time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDeleteAround (app, chr, time) =
|
||||
case chr of
|
||||
#"w" => NormalYankDelete.deleteAroundWord (app, time)
|
||||
| #"W" => NormalYankDelete.deleteAroundWORD (app, time)
|
||||
|
||||
|
||||
| #"(" => NormalYankDelete.deleteAroundPair (app, #"(", #")", time)
|
||||
| #")" => NormalYankDelete.deleteAroundPair (app, #"(", #")", time)
|
||||
|
||||
| #"[" => NormalYankDelete.deleteAroundPair (app, #"[", #"]", time)
|
||||
| #"]" => NormalYankDelete.deleteAroundPair (app, #"[", #"]", time)
|
||||
|
||||
| #"{" => NormalYankDelete.deleteAroundPair (app, #"{", #"}", time)
|
||||
| #"}" => NormalYankDelete.deleteAroundPair (app, #"{", #"}", time)
|
||||
|
||||
| #"<" => NormalYankDelete.deleteAroundPair (app, #"<", #">", time)
|
||||
| #">" => NormalYankDelete.deleteAroundPair (app, #"<", #">", time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDeleteTerminal (str, count, app, chrCmd, time) =
|
||||
case chrCmd of
|
||||
(* terminal commands: require no input after *)
|
||||
#"h" => NormalYankDelete.deleteCharsLeft (app, count, time)
|
||||
| #"l" => NormalYankDelete.removeChr (app, count, time)
|
||||
(* vi's 'j' and 'k' commands move up or down a column
|
||||
* but 'dj' or 'dk' delete whole lines
|
||||
* so their implementation differs from
|
||||
* other cursor motions *)
|
||||
| #"j" => NormalYankDelete.deleteLineDown (app, count, time)
|
||||
| #"k" => NormalYankDelete.deleteLineUp (app, count, time)
|
||||
| #"w" => NormalYankDelete.deleteWord (app, count, time)
|
||||
| #"W" => NormalYankDelete.deleteWORD (app, count, time)
|
||||
| #"b" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWord, time)
|
||||
| #"B" => NormalYankDelete.deleteByDfa (app, count, Cursor.prevWORD, time)
|
||||
| #"e" =>
|
||||
NormalYankDelete.deleteByDfa
|
||||
(app, count, Cursor.endOfWordForDelete, time)
|
||||
| #"E" =>
|
||||
NormalYankDelete.deleteByDfa
|
||||
(app, count, Cursor.endOfWORDForDelete, time)
|
||||
| #"0" => NormalYankDelete.delete (app, 1, Cursor.vi0, time)
|
||||
| #"$" => NormalYankDelete.deleteToEndOfLine (app, time)
|
||||
| #"^" => NormalYankDelete.deleteToFirstNonSpaceChr (app, time)
|
||||
| #"d" => NormalYankDelete.deleteLine (app, count, time)
|
||||
| #"n" => NormalYankDelete.deleteToNextMatch (app, count, time)
|
||||
| #"N" => NormalYankDelete.deleteToPrevMatch (app, count, time)
|
||||
| #"%" => NormalYankDelete.deletePair (app, time)
|
||||
| #"G" => NormalYankDelete.deleteToEnd (app, time)
|
||||
(* non-terminal commands which require appending chr *)
|
||||
| #"t" => appendChr (app, chrCmd, str)
|
||||
| #"T" => appendChr (app, chrCmd, str)
|
||||
| #"f" => appendChr (app, chrCmd, str)
|
||||
| #"F" => appendChr (app, chrCmd, str)
|
||||
| #"g" => appendChr (app, chrCmd, str)
|
||||
| #"i" => appendChr (app, chrCmd, str)
|
||||
| #"a" => appendChr (app, chrCmd, str)
|
||||
(* invalid command: reset mode *)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDeleteGo (app, count, chrCmd, time) =
|
||||
case chrCmd of
|
||||
#"e" => NormalYankDelete.deleteToEndOfPrevWord (app, count, time)
|
||||
| #"E" => NormalYankDelete.deleteToEndOfPrevWORD (app, count, time)
|
||||
| #"g" => NormalYankDelete.deleteToStart (app, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseDelete (strPos, str, count, app, chrCmd, time) =
|
||||
if strPos = String.size str - 1 then
|
||||
parseDeleteTerminal (str, count, app, chrCmd, time)
|
||||
else
|
||||
(* have to continue parsing string *)
|
||||
case String.sub (str, strPos + 1) of
|
||||
#"t" => NormalYankDelete.deleteTillNextChr (app, count, chrCmd, time)
|
||||
| #"T" => NormalYankDelete.deleteTillPrevChr (app, count, chrCmd, time)
|
||||
| #"f" => NormalYankDelete.deleteToNextChr (app, count, chrCmd, time)
|
||||
| #"F" => NormalYankDelete.deleteToPrevChr (app, count, chrCmd, time)
|
||||
| #"g" => parseDeleteGo (app, count, chrCmd, time)
|
||||
| #"i" => parseDeleteInside (app, chrCmd, time)
|
||||
| #"a" => parseDeleteAround (app, chrCmd, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
end
|
||||
|
||||
structure ParseYank =
|
||||
struct
|
||||
fun yankWhenMovingBack (app: app_type, fMove, count) =
|
||||
let
|
||||
open DrawMsg
|
||||
open MailboxType
|
||||
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = fMove (buffer, cursorIdx, count)
|
||||
|
||||
val length = cursorIdx - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
|
||||
val msg = YANK str
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.modeAndBuffer (app, buffer, mode, [DRAW msg])
|
||||
end
|
||||
|
||||
fun yankWhenMovingForward (app: app_type, fMove, count) =
|
||||
let
|
||||
open DrawMsg
|
||||
open MailboxType
|
||||
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val high = fMove (buffer, cursorIdx, count)
|
||||
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val length = high - cursorIdx
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
|
||||
val msg = YANK str
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.modeAndBuffer (app, buffer, mode, [DRAW msg])
|
||||
end
|
||||
|
||||
fun parseYankTerminal (str, count, app, chrCmd, time) =
|
||||
case chrCmd of
|
||||
#"h" => NormalYank.yankLeft (app, count)
|
||||
| #"k" => NormalYank.yankLineUp (app, count)
|
||||
| #"j" => NormalYank.yankLineDown (app, count)
|
||||
| #"l" => NormalYank.yankRight (app, count)
|
||||
| #"y" => NormalYank.yankLine (app, count)
|
||||
| #"0" => NormalYank.yankToStartOfLine app
|
||||
| #"w" => NormalYank.yankWhenMovingForward (app, Cursor.nextWord, count)
|
||||
| #"W" => NormalYank.yankWhenMovingForward (app, Cursor.nextWORD, count)
|
||||
| #"b" => NormalYank.yankWhenMovingBack (app, Cursor.prevWord, count)
|
||||
| #"B" => NormalYank.yankWhenMovingBack (app, Cursor.prevWORD, count)
|
||||
| #"e" =>
|
||||
NormalYank.yankWhenMovingForward
|
||||
(app, Cursor.endOfWordForDelete, count)
|
||||
| #"E" =>
|
||||
NormalYank.yankWhenMovingForward
|
||||
(app, Cursor.endOfWORDForDelete, count)
|
||||
| #"$" => NormalYank.yankWhenMovingForward (app, Cursor.viDlr, 1)
|
||||
| #"^" => NormalYank.yankToFirstNonSpaceChr app
|
||||
| #"G" => NormalYank.yankToEndOfText app
|
||||
| #"%" => NormalYank.yankToMatchingPair app
|
||||
| #"n" => NormalYank.yankToNextMatch (app, count)
|
||||
| #"N" => NormalYank.yankToPrevMatch (app, count)
|
||||
| #"x" => NormalYankDelete.removeChr (app, count, time)
|
||||
(* append non-terminal characters to string *)
|
||||
| #"d" =>
|
||||
let (* 'yd' motion, like 'ydw'; meant to be 'yank then delete' *)
|
||||
in appendChr (app, chrCmd, str)
|
||||
end
|
||||
| #"t" => appendChr (app, chrCmd, str)
|
||||
| #"T" => appendChr (app, chrCmd, str)
|
||||
| #"f" => appendChr (app, chrCmd, str)
|
||||
| #"F" => appendChr (app, chrCmd, str)
|
||||
| #"g" => appendChr (app, chrCmd, str)
|
||||
| #"i" => appendChr (app, chrCmd, str)
|
||||
| #"a" => appendChr (app, chrCmd, str)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseYankGo (count, app, chrCmd) =
|
||||
case chrCmd of
|
||||
#"e" =>
|
||||
NormalYank.yankWhenMovingBackPlusOne
|
||||
(app, Cursor.endOfPrevWord, count)
|
||||
| #"E" =>
|
||||
NormalYank.yankWhenMovingBackPlusOne
|
||||
(app, Cursor.endOfPrevWORD, count)
|
||||
| #"g" => NormalYank.yankToStart app
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseYankInside (app, chr) =
|
||||
case chr of
|
||||
#"w" => NormalYank.yankInsideWord app
|
||||
| #"W" => NormalYank.yankInsideWORD app
|
||||
| #"(" => NormalYank.yankInsideChrOpen (app, chr)
|
||||
| #"[" => NormalYank.yankInsideChrOpen (app, chr)
|
||||
| #"{" => NormalYank.yankInsideChrOpen (app, chr)
|
||||
| #"<" => NormalYank.yankInsideChrOpen (app, chr)
|
||||
| #")" => NormalYank.yankInsideChrClose (app, chr)
|
||||
| #"]" => NormalYank.yankInsideChrClose (app, chr)
|
||||
| #"}" => NormalYank.yankInsideChrClose (app, chr)
|
||||
| #">" => NormalYank.yankInsideChrClose (app, chr)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseYankAround (app, chr) =
|
||||
case chr of
|
||||
#"(" => NormalYank.yankAroundChrOpen (app, chr)
|
||||
| #"[" => NormalYank.yankAroundChrOpen (app, chr)
|
||||
| #"{" => NormalYank.yankAroundChrOpen (app, chr)
|
||||
| #"<" => NormalYank.yankAroundChrOpen (app, chr)
|
||||
| #")" => NormalYank.yankAroundChrClose (app, chr)
|
||||
| #"]" => NormalYank.yankAroundChrClose (app, chr)
|
||||
| #"}" => NormalYank.yankAroundChrClose (app, chr)
|
||||
| #">" => NormalYank.yankAroundChrClose (app, chr)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parseYank (strPos, str, count, app, chrCmd, time) =
|
||||
if strPos = String.size str - 1 then
|
||||
parseYankTerminal (str, count, app, chrCmd, time)
|
||||
else
|
||||
case String.sub (str, strPos + 1) of
|
||||
#"t" => NormalYank.yankTillNextChr (app, count, chrCmd)
|
||||
| #"T" => NormalYank.yankTillPrevChr (app, count, chrCmd)
|
||||
| #"f" => NormalYank.yankToNextChr (app, count, chrCmd)
|
||||
| #"F" => NormalYank.yankToPrevChr (app, count, chrCmd)
|
||||
| #"g" => parseYankGo (count, app, chrCmd)
|
||||
| #"i" => parseYankInside (app, chrCmd)
|
||||
| #"a" => parseYankAround (app, chrCmd)
|
||||
| #"d" =>
|
||||
ParseYankDelete.parseDelete
|
||||
(strPos + 1, str, count, app, chrCmd, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
end
|
||||
|
||||
(* useful reference as list of non-terminal commands *)
|
||||
fun parseAfterCount (strPos, str, count, app, chrCmd, time) =
|
||||
(* we are trying to parse multi-char but non-terminal strings here.
|
||||
* For example, we don't want to parse 3w which is a terminal commmand
|
||||
* to go 3 words forwards
|
||||
* but we do want to parse 3d which is a non-terminal command
|
||||
* which can be made terminal by adding "w" or "e" at the end.
|
||||
* *)
|
||||
case String.sub (str, strPos) of
|
||||
#"t" => NormalMove.tillNextChr (app, count, chrCmd)
|
||||
| #"T" => NormalMove.tillPrevChr (app, count, chrCmd)
|
||||
| #"y" => ParseYank.parseYank (strPos, str, count, app, chrCmd, time)
|
||||
| #"d" => ParseDelete.parseDelete (strPos, str, count, app, chrCmd, time)
|
||||
| #"f" => NormalMove.toNextChr (app, count, chrCmd)
|
||||
| #"F" => NormalMove.toPrevChr (app, count, chrCmd)
|
||||
| #"g" => (* go *) parseGo (count, app, chrCmd)
|
||||
| #"c" => (* change *) NormalFinish.clearMode app
|
||||
| _ =>
|
||||
(* isn't a non-terminal cmd
|
||||
* this case should never happen*)
|
||||
NormalFinish.clearMode app
|
||||
|
||||
fun parseNormalModeCommand (app, str, chrCmd, time) =
|
||||
if String.size str = 0 then
|
||||
parseChr (app, 1, chrCmd, str, time)
|
||||
else if String.size str = 1 then
|
||||
case Int.fromString str of
|
||||
SOME count => parseChr (app, count, chrCmd, str, time)
|
||||
| NONE => parseAfterCount (0, str, 1, app, chrCmd, time)
|
||||
else
|
||||
let
|
||||
val numLength = getNumLength (0, str)
|
||||
val count = String.substring (str, 0, numLength)
|
||||
val count =
|
||||
case Int.fromString count of
|
||||
SOME x => x
|
||||
| NONE => 1
|
||||
in
|
||||
if numLength = String.size str then
|
||||
(* reached end of str; str only contained numbers *)
|
||||
parseChr (app, count, chrCmd, str, time)
|
||||
else
|
||||
(* continue parsing. *)
|
||||
parseAfterCount (numLength, str, count, app, chrCmd, time)
|
||||
end
|
||||
|
||||
structure LeftArrow =
|
||||
struct
|
||||
fun parseLeftArrowCommand (strPos, str, count, app, time) =
|
||||
case String.sub (str, strPos) of
|
||||
#"y" =>
|
||||
if strPos + 1 = String.size str then
|
||||
(* terminal command, so simple yank *)
|
||||
raise Fail "left-arrow-yank unimplemnted"
|
||||
else
|
||||
(case String.sub (str, strPos + 1) of
|
||||
#"d" => NormalYankDelete.deleteCharsLeft (app, count, time)
|
||||
| _ => NormalFinish.clearMode app)
|
||||
| #"d" => NormalDelete.deleteCharsLeft (app, count, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parse (app, str, time) =
|
||||
if String.size str = 0 then
|
||||
MoveViH.move (app, 1)
|
||||
else if String.size str = 1 then
|
||||
case Int.fromString str of
|
||||
SOME count => MoveViH.move (app, count)
|
||||
| NONE => parseLeftArrowCommand (0, str, 1, app, time)
|
||||
else
|
||||
let
|
||||
val numLength = getNumLength (0, str)
|
||||
val count = String.substring (str, 0, numLength)
|
||||
val count =
|
||||
case Int.fromString count of
|
||||
SOME x => x
|
||||
| NONE => 1
|
||||
in
|
||||
if numLength = String.size str then
|
||||
(* reached end of string; string only contained numbers *)
|
||||
MoveViH.move (app, count)
|
||||
else
|
||||
parseLeftArrowCommand (numLength, str, count, app, time)
|
||||
end
|
||||
end
|
||||
|
||||
structure RightArrow =
|
||||
struct
|
||||
fun parseRightArrowCommand (strPos, str, count, app, time) =
|
||||
case String.sub (str, strPos) of
|
||||
#"y" =>
|
||||
if strPos + 1 = String.size str then
|
||||
raise Fail "right-arrow-yank unimplemnted"
|
||||
else
|
||||
(case String.sub (str, strPos + 1) of
|
||||
#"d" => NormalYankDelete.removeChr (app, count, time)
|
||||
| _ => NormalFinish.clearMode app)
|
||||
| #"d" => NormalDelete.removeChr (app, count, time)
|
||||
| _ => NormalFinish.clearMode app
|
||||
|
||||
fun parse (app, str, time) =
|
||||
if String.size str = 0 then
|
||||
MoveViL.move (app, 1)
|
||||
else if String.size str = 1 then
|
||||
case Int.fromString str of
|
||||
SOME count => MoveViL.move (app, count)
|
||||
| NONE => parseRightArrowCommand (0, str, 1, app, time)
|
||||
else
|
||||
let
|
||||
val numLength = getNumLength (0, str)
|
||||
val count = String.substring (str, 0, numLength)
|
||||
val count =
|
||||
case Int.fromString count of
|
||||
SOME x => x
|
||||
| NONE => 1
|
||||
in
|
||||
if numLength = String.size str then
|
||||
(* reached end of string; string only contained numbers *)
|
||||
MoveViH.move (app, count)
|
||||
else
|
||||
parseRightArrowCommand (numLength, str, count, app, time)
|
||||
end
|
||||
end
|
||||
|
||||
fun update (app, str, msg, time) =
|
||||
case msg of
|
||||
CHAR_EVENT chrCmd => parseNormalModeCommand (app, str, chrCmd, time)
|
||||
| KEY_ESC => NormalFinish.clearMode app
|
||||
| RESIZE_EVENT (width, height) =>
|
||||
NormalFinish.resizeText (app, width, height)
|
||||
|
||||
| ARROW_RIGHT => RightArrow.parse (app, str, time)
|
||||
| ARROW_LEFT => LeftArrow.parse (app, str, time)
|
||||
| ARROW_UP => NormalFinish.clearMode app
|
||||
| ARROW_DOWN => NormalFinish.clearMode app
|
||||
|
||||
| KEY_ENTER => NormalFinish.clearMode app
|
||||
| KEY_BACKSPACE => NormalFinish.clearMode app
|
||||
end
|
||||
586
shf/fcore/normal-mode/normal-move.sml
Normal file
586
shf/fcore/normal-mode/normal-move.sml
Normal file
@@ -0,0 +1,586 @@
|
||||
structure NormalMove =
|
||||
struct
|
||||
open AppType
|
||||
|
||||
fun moveToStart (app: app_type) : AppType.app_type =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val cursorIdx = 0
|
||||
val startLine = 0
|
||||
val buffer = LineGap.goToStart buffer
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val drawMsg = [MailboxType.DRAW drawMsg]
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, cursorIdx
|
||||
, mode
|
||||
, startLine
|
||||
, searchList
|
||||
, drawMsg
|
||||
, bufferModifyTime
|
||||
, 0
|
||||
)
|
||||
end
|
||||
|
||||
fun moveToEnd (app: app_type) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, startLine = prevLineNumber
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val {line = bufferLine, textLength, ...} = buffer
|
||||
|
||||
val bufferIdx = Int.max (0, textLength - 1)
|
||||
val bufferLine = bufferLine - 1
|
||||
|
||||
val buffer = LineGap.goToIdx (bufferIdx, buffer)
|
||||
val bufferIdx =
|
||||
if Cursor.isOnNewlineAfterChr (buffer, bufferIdx) then
|
||||
Int.max (0, bufferIdx - 1)
|
||||
else
|
||||
bufferIdx
|
||||
|
||||
val buffer = LineGap.goToIdx (bufferIdx, buffer)
|
||||
val visualScrollColumn =
|
||||
TextScroll.getScrollColumn
|
||||
(buffer, bufferIdx, windowWidth, prevScrollColumn)
|
||||
|
||||
val bufferLine =
|
||||
TextScroll.getStartLine
|
||||
(prevLineNumber, bufferLine, windowHeight, #lineLength buffer)
|
||||
val buffer = LineGap.goToLine (bufferLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( bufferLine
|
||||
, bufferIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val drawMsg = [MailboxType.DRAW drawMsg]
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, bufferIdx
|
||||
, mode
|
||||
, bufferLine
|
||||
, searchList
|
||||
, drawMsg
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
)
|
||||
end
|
||||
|
||||
fun finishMoveCursorUpDown
|
||||
(app: app_type, newCursorLineNumber, buffer, column, lineIdx) =
|
||||
let
|
||||
val
|
||||
{ windowWidth
|
||||
, windowHeight
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, startLine = prevLineNumber
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (lineIdx, buffer)
|
||||
val endOfLineIdx = Cursor.viDlr (buffer, lineIdx, 1)
|
||||
val endOfLineIdx =
|
||||
if endOfLineIdx >= #textLength buffer - 1 then
|
||||
Int.max (0, #textLength buffer - 1)
|
||||
else
|
||||
endOfLineIdx
|
||||
|
||||
val cursorIdx = Int.min (endOfLineIdx, lineIdx + column)
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
(* create draw message *)
|
||||
val visualScrollColumn =
|
||||
TextScroll.getScrollColumn
|
||||
(buffer, cursorIdx, windowWidth, prevScrollColumn)
|
||||
|
||||
val startLine =
|
||||
TextScroll.getStartLine
|
||||
( prevLineNumber
|
||||
, newCursorLineNumber
|
||||
, windowHeight
|
||||
, #lineLength buffer
|
||||
)
|
||||
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val drawMsg = [MailboxType.DRAW drawMsg]
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, cursorIdx
|
||||
, mode
|
||||
, startLine
|
||||
, searchList
|
||||
, drawMsg
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
)
|
||||
end
|
||||
|
||||
fun moveCursorUp (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val startOfLine = Cursor.vi0 (buffer, cursorIdx)
|
||||
in
|
||||
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
|
||||
let
|
||||
val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx + 1, buffer)
|
||||
val newCursorLineNumber = Int.max (0, cursorLineNumber - count)
|
||||
val buffer = LineGap.goToLine (newCursorLineNumber, buffer)
|
||||
val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer)
|
||||
|
||||
val lineIdx =
|
||||
if Cursor.isPrevChrStartOfLine (buffer, lineIdx) then lineIdx
|
||||
else lineIdx - 1
|
||||
|
||||
val buffer = LineGap.goToIdx (lineIdx, buffer)
|
||||
val lineIdx = Cursor.vi0 (buffer, lineIdx)
|
||||
|
||||
val lineIdx = Int.max (0, lineIdx)
|
||||
in
|
||||
finishMoveCursorUpDown (app, newCursorLineNumber, buffer, 0, lineIdx)
|
||||
end
|
||||
else
|
||||
let
|
||||
val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val newCursorLineNumber = Int.max (cursorLineNumber - count, 0)
|
||||
|
||||
val buffer = LineGap.goToLine (newCursorLineNumber, buffer)
|
||||
val lineIdx =
|
||||
LineGap.lineNumberToIdx (newCursorLineNumber, buffer) + 1
|
||||
|
||||
val column = cursorIdx - startOfLine
|
||||
val column = if newCursorLineNumber = 0 then column - 1 else column
|
||||
in
|
||||
finishMoveCursorUpDown
|
||||
(app, newCursorLineNumber, buffer, column, lineIdx)
|
||||
end
|
||||
end
|
||||
|
||||
fun moveCursorDown (app: app_type, count) =
|
||||
let
|
||||
val
|
||||
{ windowWidth
|
||||
, windowHeight
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, startLine = prevLineNumber
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
(* calculate new idx to move to *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val startOfLine = Cursor.vi0 (buffer, cursorIdx)
|
||||
val column = cursorIdx - startOfLine
|
||||
in
|
||||
if Cursor.isCursorAtStartOfLine (buffer, cursorIdx) then
|
||||
let
|
||||
val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx + 1, buffer)
|
||||
val newCursorLineNumber = cursorLineNumber + count
|
||||
|
||||
val buffer = LineGap.goToLine (newCursorLineNumber, buffer)
|
||||
val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer)
|
||||
|
||||
val lineIdx =
|
||||
if Cursor.isPrevChrStartOfLine (buffer, lineIdx) then lineIdx
|
||||
else lineIdx - 1
|
||||
val buffer = LineGap.goToIdx (lineIdx, buffer)
|
||||
val lineIdx = Cursor.vi0 (buffer, lineIdx)
|
||||
|
||||
val lineIdx =
|
||||
if lineIdx >= #textLength buffer - 1 then
|
||||
Int.max (0, #textLength buffer - 1)
|
||||
else
|
||||
lineIdx
|
||||
in
|
||||
finishMoveCursorUpDown (app, newCursorLineNumber, buffer, 0, lineIdx)
|
||||
end
|
||||
else
|
||||
let
|
||||
val cursorLineNumber = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val newCursorLineNumber = cursorLineNumber + count
|
||||
|
||||
val buffer = LineGap.goToLine (newCursorLineNumber, buffer)
|
||||
val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer)
|
||||
val buffer = LineGap.goToIdx (lineIdx, buffer)
|
||||
in
|
||||
if lineIdx >= #textLength buffer - 1 then
|
||||
(* we reached last line *)
|
||||
let
|
||||
val lineIdx = Int.max (#textLength buffer - 1, 0)
|
||||
val buffer = LineGap.goToIdx (lineIdx, buffer)
|
||||
|
||||
val lineIdx =
|
||||
if Cursor.isOnNewlineAfterChr (buffer, lineIdx) then lineIdx - 1
|
||||
else lineIdx
|
||||
|
||||
val startOfLine = Cursor.vi0 (buffer, lineIdx)
|
||||
in
|
||||
if cursorIdx >= startOfLine then
|
||||
(* we are already on last line so don't move *)
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, [], bufferModifyTime)
|
||||
else
|
||||
finishMoveCursorUpDown
|
||||
(app, newCursorLineNumber, buffer, column, startOfLine)
|
||||
end
|
||||
else
|
||||
let
|
||||
val lineIdx =
|
||||
if lineIdx >= #textLength buffer - 2 then
|
||||
Int.max (0, #textLength buffer - 2)
|
||||
else
|
||||
lineIdx
|
||||
|
||||
val buffer = LineGap.goToIdx (lineIdx, buffer)
|
||||
val lineIdx =
|
||||
if Cursor.isOnNewlineAfterChr (buffer, lineIdx) then lineIdx + 1
|
||||
else lineIdx
|
||||
in
|
||||
finishMoveCursorUpDown
|
||||
(app, newCursorLineNumber, buffer, column, lineIdx)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
fun moveToLine (app: app_type, reqLine) =
|
||||
let
|
||||
val reqLine = reqLine - 1
|
||||
in
|
||||
if reqLine = 0 then
|
||||
moveToStart app
|
||||
else
|
||||
let
|
||||
val
|
||||
{ windowWidth
|
||||
, windowHeight
|
||||
, buffer
|
||||
, startLine = prevLineNumber
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
val buffer = LineGap.goToLine (reqLine, buffer)
|
||||
|
||||
(* get idx of first chr after linebreak *)
|
||||
val cursorIdx = LineGap.lineNumberToIdx (reqLine, buffer)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
(* we got the line start idx, but we want to move to the index
|
||||
* after it, where the first character of the line is.
|
||||
* Unless the next character is a line break,
|
||||
* in which case we want to stay at the current idx. *)
|
||||
val cursorIdx =
|
||||
if Cursor.isNextChrEndOfLine (buffer, cursorIdx) then cursorIdx
|
||||
else cursorIdx + 1
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val visualScrollColumn =
|
||||
TextScroll.getScrollColumn
|
||||
(buffer, cursorIdx, windowWidth, prevScrollColumn)
|
||||
|
||||
val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val startLine =
|
||||
TextScroll.getStartLine
|
||||
(prevLineNumber, cursorLine, windowHeight, #lineLength buffer)
|
||||
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val drawMsg = [MailboxType.DRAW drawMsg]
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, cursorIdx
|
||||
, mode
|
||||
, startLine
|
||||
, searchList
|
||||
, drawMsg
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
fun moveToMatchingPair (app: app_type) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine = prevLineNumber
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn = prevScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
(* move LineGap and buffer to start of line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorIdx = Cursor.nextPairChr (buffer, cursorIdx)
|
||||
in
|
||||
if cursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorIdx = Cursor.matchPair (buffer, cursorIdx)
|
||||
in
|
||||
if cursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val visualScrollColumn =
|
||||
TextScroll.getScrollColumn
|
||||
(buffer, cursorIdx, windowWidth, prevScrollColumn)
|
||||
|
||||
val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val startLine =
|
||||
TextScroll.getStartLine
|
||||
(prevLineNumber, cursorLine, windowHeight, #lineLength buffer)
|
||||
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val drawMsg = [MailboxType.DRAW drawMsg]
|
||||
in
|
||||
NormalModeWith.bufferAndCursorIdx
|
||||
( app
|
||||
, buffer
|
||||
, cursorIdx
|
||||
, NORMAL_MODE ""
|
||||
, startLine
|
||||
, searchList
|
||||
, drawMsg
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
fun firstNonSpaceChr (app: app_type) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, searchList
|
||||
, bufferModifyTime
|
||||
, ...
|
||||
} = app
|
||||
|
||||
(* move LineGap and buffer to start of line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
(* move cursorIdx to first character on line *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorIdx = Cursor.firstNonSpaceChr (buffer, cursorIdx)
|
||||
in
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun helpMoveToChr (app: app_type, buffer, cursorIdx, count, fMove, chr) =
|
||||
if count = 0 then
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, #searchList app, [], #bufferModifyTime app)
|
||||
else
|
||||
let
|
||||
(* move LineGap to cursorIdx, which is necessary for finding newCursorIdx *)
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx = fMove (buffer, cursorIdx, chr)
|
||||
val newCount = if cursorIdx = newCursorIdx then 0 else count - 1
|
||||
in
|
||||
helpMoveToChr (app, buffer, newCursorIdx, newCount, fMove, chr)
|
||||
end
|
||||
|
||||
fun moveToChr (app: app_type, count, fMove, chr) =
|
||||
let val {cursorIdx, buffer, ...} = app
|
||||
in helpMoveToChr (app, buffer, cursorIdx, count, fMove, chr)
|
||||
end
|
||||
|
||||
fun moveToNextMatch (app: app_type, count) =
|
||||
let
|
||||
val
|
||||
{ cursorIdx
|
||||
, searchList
|
||||
, buffer
|
||||
, bufferModifyTime
|
||||
, visualScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
val newCursorIdx =
|
||||
PersistentVector.nextMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, newCursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun moveToPrevMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, buffer, bufferModifyTime, ...} = app
|
||||
val newCursorIdx =
|
||||
PersistentVector.prevMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, newCursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun toNextChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, newCursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun tillNextChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, newCursorIdx - 1, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun toPrevChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, newCursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
fun tillPrevChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, searchList, bufferModifyTime, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, newCursorIdx + 1, searchList, [], bufferModifyTime)
|
||||
end
|
||||
end
|
||||
156
shf/fcore/normal-mode/normal-search-finish.sml
Normal file
156
shf/fcore/normal-mode/normal-search-finish.sml
Normal file
@@ -0,0 +1,156 @@
|
||||
structure NormalSearchFinish =
|
||||
struct
|
||||
open AppType
|
||||
open DrawMsg
|
||||
|
||||
fun onSearchChanged
|
||||
( app: app_type
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, buffer
|
||||
) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, cursorIdx
|
||||
, startLine = prevLineNumber
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, visualScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val searchScrollColumn =
|
||||
TextScroll.getScrollColumnFromString
|
||||
(searchCursorIdx, windowWidth, searchScrollColumn)
|
||||
|
||||
val mode = NORMAL_SEARCH_MODE
|
||||
{ searchString = searchString
|
||||
, tempSearchList = tempSearchList
|
||||
, searchCursorIdx = searchCursorIdx
|
||||
, searchScrollColumn = searchScrollColumn
|
||||
, caseSensitive = caseSensitive
|
||||
}
|
||||
|
||||
val floatWindowWidth = Real32.fromInt windowWidth
|
||||
val floatWindowHeight = Real32.fromInt windowHeight
|
||||
|
||||
val searchStringPosY = windowHeight - TextConstants.ySpace - 5
|
||||
|
||||
val initialTextAcc = SearchBar.build
|
||||
( searchString
|
||||
, 5
|
||||
, searchStringPosY
|
||||
, windowWidth
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
)
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val startLine =
|
||||
TextScroll.getStartLine
|
||||
(prevLineNumber, cursorLine, windowHeight, #lineLength buffer)
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val remainingWindowHeight = windowHeight - (TextConstants.ySpace * 2)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.startBuild
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, remainingWindowHeight
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, tempSearchList
|
||||
, visualScrollColumn
|
||||
, initialTextAcc
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val msgs = [MailboxType.DRAW drawMsg]
|
||||
in
|
||||
NormalSearchModeWith.changeTempSearchString
|
||||
(app, buffer, startLine, mode, msgs)
|
||||
end
|
||||
|
||||
fun resize
|
||||
( app: app_type
|
||||
, newWindowWidth
|
||||
, newWindowHeight
|
||||
, searchString
|
||||
, searchCursorIdx
|
||||
, tempSearchList
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
) =
|
||||
let
|
||||
val
|
||||
{buffer, cursorIdx, startLine = prevLineNumber, visualScrollColumn, ...} =
|
||||
app
|
||||
|
||||
val floatWindowWidth = Real32.fromInt newWindowWidth
|
||||
val floatWindowHeight = Real32.fromInt newWindowHeight
|
||||
|
||||
val searchScrollColumn =
|
||||
TextScroll.getScrollColumnFromString
|
||||
(searchCursorIdx, newWindowWidth, searchScrollColumn)
|
||||
|
||||
val mode = NORMAL_SEARCH_MODE
|
||||
{ searchString = searchString
|
||||
, tempSearchList = tempSearchList
|
||||
, searchCursorIdx = searchCursorIdx
|
||||
, searchScrollColumn = searchScrollColumn
|
||||
, caseSensitive = caseSensitive
|
||||
}
|
||||
|
||||
val searchStringPosY = newWindowHeight - TextConstants.ySpace - 5
|
||||
|
||||
val initialTextAcc = SearchBar.build
|
||||
( searchString
|
||||
, 5
|
||||
, searchStringPosY
|
||||
, newWindowWidth
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
)
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val cursorLine = LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val startLine =
|
||||
TextScroll.getStartLine
|
||||
(prevLineNumber, cursorLine, newWindowHeight, #lineLength buffer)
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val remainingWindowHeight = newWindowHeight - (TextConstants.ySpace * 2)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.startBuild
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, newWindowWidth
|
||||
, remainingWindowHeight
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, tempSearchList
|
||||
, visualScrollColumn
|
||||
, initialTextAcc
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val msgs = [MailboxType.DRAW drawMsg]
|
||||
in
|
||||
NormalSearchModeWith.bufferAndSize
|
||||
(app, mode, buffer, newWindowWidth, newWindowHeight, msgs)
|
||||
end
|
||||
end
|
||||
140
shf/fcore/normal-mode/normal-search-mode-with.sml
Normal file
140
shf/fcore/normal-mode/normal-search-mode-with.sml
Normal file
@@ -0,0 +1,140 @@
|
||||
structure NormalSearchModeWith =
|
||||
struct
|
||||
open AppType
|
||||
|
||||
fun returnToNormalMode
|
||||
( app: app_type
|
||||
, newBuffer
|
||||
, newSearchList
|
||||
, newStartLine
|
||||
, newMode
|
||||
, newDfa
|
||||
, newMsgs
|
||||
) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, buffer = _
|
||||
, searchList = _
|
||||
, startLine = _
|
||||
, msgs = _
|
||||
, dfa = _
|
||||
, bufferModifyTime
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, cursorIdx
|
||||
, visualScrollColumn
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, buffer = newBuffer
|
||||
, searchList = newSearchList
|
||||
, startLine = newStartLine
|
||||
, dfa = newDfa
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, msgs = newMsgs
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, cursorIdx = cursorIdx
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
}
|
||||
end
|
||||
|
||||
fun changeTempSearchString
|
||||
(app: app_type, newBuffer, newStartLine, newMode, newMsgs) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, buffer = _
|
||||
, searchList
|
||||
, startLine = _
|
||||
, msgs = _
|
||||
, bufferModifyTime
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, cursorIdx
|
||||
, visualScrollColumn
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, buffer = newBuffer
|
||||
, startLine = newStartLine
|
||||
, msgs = newMsgs
|
||||
, searchList = searchList
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, cursorIdx = cursorIdx
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun searchList (app: app_type, newSearchList) =
|
||||
let
|
||||
val
|
||||
{ mode
|
||||
, buffer
|
||||
, searchList = _
|
||||
, startLine
|
||||
, msgs
|
||||
, bufferModifyTime
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, cursorIdx
|
||||
, visualScrollColumn
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = mode
|
||||
, searchList = newSearchList
|
||||
, buffer = buffer
|
||||
, startLine = startLine
|
||||
, msgs = msgs
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, windowWidth = windowWidth
|
||||
, windowHeight = windowHeight
|
||||
, cursorIdx = cursorIdx
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
|
||||
fun bufferAndSize
|
||||
( app: app_type
|
||||
, newMode
|
||||
, newBuffer
|
||||
, newWindowWidth
|
||||
, newWindowHeight
|
||||
, newMsgs
|
||||
) =
|
||||
let
|
||||
val
|
||||
{ mode = _
|
||||
, windowWidth = _
|
||||
, windowHeight = _
|
||||
, msgs = _
|
||||
, buffer = _
|
||||
, searchList
|
||||
, startLine
|
||||
, bufferModifyTime
|
||||
, cursorIdx
|
||||
, visualScrollColumn
|
||||
, dfa
|
||||
} = app
|
||||
in
|
||||
{ mode = newMode
|
||||
, buffer = newBuffer
|
||||
, windowWidth = newWindowWidth
|
||||
, windowHeight = newWindowHeight
|
||||
, msgs = newMsgs
|
||||
, searchList = searchList
|
||||
, startLine = startLine
|
||||
, bufferModifyTime = bufferModifyTime
|
||||
, cursorIdx = cursorIdx
|
||||
, visualScrollColumn = visualScrollColumn
|
||||
, dfa = dfa
|
||||
}
|
||||
end
|
||||
end
|
||||
274
shf/fcore/normal-mode/normal-search-mode.sml
Normal file
274
shf/fcore/normal-mode/normal-search-mode.sml
Normal file
@@ -0,0 +1,274 @@
|
||||
structure NormalSearchMode =
|
||||
struct
|
||||
open AppType
|
||||
open InputMsg
|
||||
open MailboxType
|
||||
|
||||
fun buildTempSearchList (searchString, buffer, cursorIdx, caseSensitive) =
|
||||
let
|
||||
val dfa =
|
||||
if caseSensitive then CaseSensitiveDfa.fromString searchString
|
||||
else CaseInsensitiveDfa.fromString searchString
|
||||
in
|
||||
SearchList.buildRange (buffer, cursorIdx + 1111, dfa)
|
||||
end
|
||||
|
||||
fun addChr
|
||||
( app: app_type
|
||||
, searchString
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, chr
|
||||
) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
|
||||
val c = String.implode [chr]
|
||||
val searchString =
|
||||
if searchCursorIdx = String.size searchString then
|
||||
searchString ^ c
|
||||
else
|
||||
let
|
||||
val sub1 = Substring.extract (searchString, 0, SOME searchCursorIdx)
|
||||
val sub2 = Substring.full c
|
||||
val sub3 = Substring.extract (searchString, searchCursorIdx, NONE)
|
||||
in
|
||||
Substring.concat [sub1, sub2, sub3]
|
||||
end
|
||||
val searchCursorIdx = searchCursorIdx + 1
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer)
|
||||
val (buffer, tempSearchList) =
|
||||
buildTempSearchList (searchString, buffer, cursorIdx, caseSensitive)
|
||||
in
|
||||
NormalSearchFinish.onSearchChanged
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, buffer
|
||||
)
|
||||
end
|
||||
|
||||
(* return to normal mode, keeping the same searchString and searchList
|
||||
* from before entering this mode. *)
|
||||
fun exitToNormalMode (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, searchList, bufferModifyTime, ...} = app
|
||||
in
|
||||
NormalFinish.buildTextAndClear
|
||||
(app, buffer, cursorIdx, searchList, [], bufferModifyTime)
|
||||
end
|
||||
|
||||
(* save search string and searchList and return to normal mode *)
|
||||
fun saveSearch (app: app_type, searchString, caseSensitive, time) =
|
||||
let
|
||||
val
|
||||
{ buffer
|
||||
, cursorIdx
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, startLine
|
||||
, visualScrollColumn
|
||||
, ...
|
||||
} = app
|
||||
|
||||
val dfa =
|
||||
if caseSensitive then CaseSensitiveDfa.fromString searchString
|
||||
else CaseInsensitiveDfa.fromString searchString
|
||||
|
||||
val buffer = LineGap.goToStart buffer
|
||||
val (buffer, searchList) = SearchList.build (buffer, dfa)
|
||||
|
||||
(* move LineGap to first line displayed on screen *)
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
(* move buffer to new startLine as required by TextBuilder.build *)
|
||||
val buffer = LineGap.goToLine (startLine, buffer)
|
||||
|
||||
val drawMsg = NormalModeTextBuilder.build
|
||||
( startLine
|
||||
, cursorIdx
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
)
|
||||
val drawMsg = Vector.concat drawMsg
|
||||
val drawMsg = DrawMsg.DRAW_TEXT drawMsg
|
||||
val msgs = [DRAW drawMsg]
|
||||
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalSearchModeWith.returnToNormalMode
|
||||
(app, buffer, searchList, startLine, mode, dfa, msgs)
|
||||
end
|
||||
|
||||
fun backspace
|
||||
( app: app_type
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchScrollColumn
|
||||
, searchCursorIdx
|
||||
, caseSensitive
|
||||
) =
|
||||
if searchCursorIdx = 0 then
|
||||
app
|
||||
else
|
||||
let
|
||||
val searchString =
|
||||
if searchCursorIdx = String.size searchString then
|
||||
String.substring (searchString, 0, String.size searchString - 1)
|
||||
else
|
||||
let
|
||||
val sub1 = Substring.extract
|
||||
(searchString, 0, SOME (searchCursorIdx - 1))
|
||||
val sub2 = Substring.extract (searchString, searchCursorIdx, SOME
|
||||
(String.size searchString - searchCursorIdx))
|
||||
in
|
||||
Substring.concat [sub1, sub2]
|
||||
end
|
||||
val searchCursorIdx = searchCursorIdx - 1
|
||||
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx - 1111, buffer)
|
||||
val (buffer, tempSearchList) =
|
||||
buildTempSearchList (searchString, buffer, cursorIdx, caseSensitive)
|
||||
in
|
||||
NormalSearchFinish.onSearchChanged
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, buffer
|
||||
)
|
||||
end
|
||||
|
||||
fun moveLeft
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
) =
|
||||
if searchCursorIdx = 0 then
|
||||
app
|
||||
else
|
||||
let
|
||||
val searchCursorIdx = Int.max (0, searchCursorIdx - 1)
|
||||
in
|
||||
NormalSearchFinish.onSearchChanged
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, #buffer app
|
||||
)
|
||||
end
|
||||
|
||||
fun moveRight
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
) =
|
||||
if searchCursorIdx = String.size searchString then
|
||||
app
|
||||
else
|
||||
let
|
||||
val searchCursorIdx =
|
||||
Int.min (searchCursorIdx + 1, String.size searchString)
|
||||
in
|
||||
NormalSearchFinish.onSearchChanged
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, #buffer app
|
||||
)
|
||||
end
|
||||
|
||||
fun update
|
||||
( app
|
||||
, { searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
}
|
||||
, msg
|
||||
, time
|
||||
) =
|
||||
case msg of
|
||||
CHAR_EVENT chr =>
|
||||
addChr
|
||||
( app
|
||||
, searchString
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
, chr
|
||||
)
|
||||
| KEY_BACKSPACE =>
|
||||
backspace
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchScrollColumn
|
||||
, searchCursorIdx
|
||||
, caseSensitive
|
||||
)
|
||||
| KEY_ESC => exitToNormalMode app
|
||||
| KEY_ENTER => saveSearch (app, searchString, caseSensitive, time)
|
||||
| ARROW_LEFT =>
|
||||
moveLeft
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
)
|
||||
| ARROW_RIGHT =>
|
||||
moveRight
|
||||
( app
|
||||
, searchString
|
||||
, tempSearchList
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
)
|
||||
| RESIZE_EVENT (width, height) =>
|
||||
NormalSearchFinish.resize
|
||||
( app
|
||||
, width
|
||||
, height
|
||||
, searchString
|
||||
, searchCursorIdx
|
||||
, tempSearchList
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
)
|
||||
|
||||
(* In Vim's search mode, the up and down arrows can be used
|
||||
* to scroll through the search history.
|
||||
* I don't find this feature too useful as it is often easier to type
|
||||
* the whole search string again, so I'm leaving it unimplemented
|
||||
* until/unless I find that I wish this functionality was there
|
||||
* while using the program. *)
|
||||
| ARROW_UP => app
|
||||
| ARROW_DOWN => app
|
||||
end
|
||||
14
shf/fcore/normal-mode/normal-yank-delete.sml
Normal file
14
shf/fcore/normal-mode/normal-yank-delete.sml
Normal file
@@ -0,0 +1,14 @@
|
||||
structure NormalYankDelete =
|
||||
MakeNormalDelete
|
||||
(struct
|
||||
open DrawMsg
|
||||
open MailboxType
|
||||
|
||||
fun initMsgs (low, length, buffer) =
|
||||
let
|
||||
val str = LineGap.substring (low, length + 1, buffer)
|
||||
val msg = YANK str
|
||||
in
|
||||
[DRAW msg]
|
||||
end
|
||||
end)
|
||||
534
shf/fcore/normal-mode/normal-yank.sml
Normal file
534
shf/fcore/normal-mode/normal-yank.sml
Normal file
@@ -0,0 +1,534 @@
|
||||
structure NormalYank =
|
||||
struct
|
||||
open AppType
|
||||
open DrawMsg
|
||||
open MailboxType
|
||||
|
||||
fun finish (app, buffer, yankedString) =
|
||||
let
|
||||
val msgs = [DRAW (YANK yankedString)]
|
||||
val mode = NORMAL_MODE ""
|
||||
in
|
||||
NormalModeWith.modeAndBuffer (app, buffer, mode, msgs)
|
||||
end
|
||||
|
||||
fun yankLeft (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val min = Cursor.vi0 (buffer, cursorIdx)
|
||||
val low = Cursor.viH (buffer, cursorIdx, count)
|
||||
|
||||
val low = Int.max (min, low)
|
||||
val length = cursorIdx - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankRight (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val endOfLineIdx = Cursor.viDlr (buffer, cursorIdx, 1) + 1
|
||||
val high = Cursor.viL (buffer, cursorIdx, count)
|
||||
val high = Int.min (high, endOfLineIdx)
|
||||
val length = high - cursorIdx
|
||||
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankLineUp (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val cursorLineNumber =
|
||||
if Cursor.isNextChrEndOfLine (buffer, cursorIdx) then
|
||||
LineGap.idxToLineNumber (cursorIdx + 1, buffer)
|
||||
else
|
||||
LineGap.idxToLineNumber (cursorIdx, buffer)
|
||||
val newCursorLineNumber = Int.max (cursorLineNumber - count, 0)
|
||||
in
|
||||
if cursorLineNumber = 0 then
|
||||
NormalFinish.clearMode app
|
||||
else if newCursorLineNumber = 0 then
|
||||
let
|
||||
val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1)
|
||||
val buffer = LineGap.goToIdx (endOfLine, buffer)
|
||||
|
||||
val endOfLine =
|
||||
if Cursor.isCursorAtStartOfLine (buffer, endOfLine) then
|
||||
endOfLine + 1
|
||||
else
|
||||
endOfLine + 2
|
||||
|
||||
val buffer = LineGap.goToIdx (endOfLine, buffer)
|
||||
val str = LineGap.substring (0, endOfLine, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
else
|
||||
let
|
||||
val endOfLine = Cursor.viDlr (buffer, cursorIdx, 1)
|
||||
val buffer = LineGap.goToIdx (endOfLine, buffer)
|
||||
val endsOnNewline = Cursor.isCursorAtStartOfLine (buffer, endOfLine)
|
||||
|
||||
val endOfLine = if endsOnNewline then endOfLine else endOfLine + 1
|
||||
|
||||
val newCursorLineNumber =
|
||||
if endsOnNewline andalso endOfLine = #textLength buffer - 1 then
|
||||
newCursorLineNumber - 1
|
||||
else
|
||||
newCursorLineNumber
|
||||
val buffer = LineGap.goToLine (newCursorLineNumber, buffer)
|
||||
|
||||
val lineIdx = LineGap.lineNumberToIdx (newCursorLineNumber, buffer)
|
||||
val length = endOfLine - lineIdx
|
||||
|
||||
val buffer = LineGap.goToIdx (endOfLine, buffer)
|
||||
val str = LineGap.substring (lineIdx + 1, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankLineDown (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val startIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
val buffer = LineGap.goToIdx (startIdx, buffer)
|
||||
|
||||
val startLine =
|
||||
if Cursor.isCursorAtStartOfLine (buffer, startIdx) then
|
||||
LineGap.idxToLineNumber (startIdx, buffer)
|
||||
else
|
||||
LineGap.idxToLineNumber (startIdx + 1, buffer)
|
||||
val endLine = startLine + count + 1
|
||||
|
||||
val buffer = LineGap.goToLine (endLine, buffer)
|
||||
val endLineIdx = LineGap.lineNumberToIdx (endLine, buffer)
|
||||
val buffer = LineGap.goToIdx (endLineIdx - 1, buffer)
|
||||
|
||||
(* get "real" endLine by not considering newline after non-newline *)
|
||||
val endLine =
|
||||
if Cursor.isOnNewlineAfterChr (buffer, endLineIdx - 1) then
|
||||
LineGap.idxToLineNumber (endLineIdx - 1, buffer)
|
||||
else
|
||||
LineGap.idxToLineNumber (endLineIdx, buffer)
|
||||
in
|
||||
if endLineIdx = #textLength buffer andalso endLine = startLine then
|
||||
(* cursor is already on last line so don't yank *)
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val endLineIdx = endLineIdx + 1
|
||||
val length = endLineIdx - startIdx
|
||||
|
||||
(* perform the actual yank *)
|
||||
val buffer = LineGap.goToIdx (endLineIdx, buffer)
|
||||
val str = LineGap.substring (startIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankLine (app: app_type, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val high = Cursor.viDlrForDelete (buffer, low, count)
|
||||
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val length = high - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankToStartOfLine (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
val length = cursorIdx - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankWhenMovingBack (app: app_type, fMove, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = fMove (buffer, cursorIdx, count)
|
||||
|
||||
val length = cursorIdx - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankWhenMovingBackPlusOne (app: app_type, fMove, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = fMove (buffer, cursorIdx, count)
|
||||
|
||||
val length = (cursorIdx + 1) - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankWhenMovingForward (app: app_type, fMove, count) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val high = fMove (buffer, cursorIdx, count)
|
||||
val beforeHigh = high - 1
|
||||
val buffer = LineGap.goToIdx (beforeHigh, buffer)
|
||||
|
||||
val high =
|
||||
if Cursor.isOnNewlineAfterChr (buffer, beforeHigh) then beforeHigh
|
||||
else high
|
||||
|
||||
val length = high - cursorIdx
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankToFirstNonSpaceChr (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val otherIdx = Cursor.vi0 (buffer, cursorIdx)
|
||||
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val otherIdx = Cursor.firstNonSpaceChr (buffer, otherIdx)
|
||||
in
|
||||
if cursorIdx > otherIdx then
|
||||
(* yanking backwards from cursorIdx *)
|
||||
let
|
||||
val length = cursorIdx - otherIdx + 1
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val str = LineGap.substring (otherIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
else if cursorIdx < otherIdx then
|
||||
(* yanking forward from cursorIdx *)
|
||||
let
|
||||
val length = otherIdx - cursorIdx
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
else
|
||||
NormalFinish.clearMode app
|
||||
end
|
||||
|
||||
fun yankToEndOfText (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
|
||||
val buffer = LineGap.goToEnd buffer
|
||||
val {rightStrings, idx, ...} = buffer
|
||||
val finishIdx = Int.max (0, idx - 1)
|
||||
|
||||
val length = finishIdx - cursorIdx
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankToMatchingPair (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val otherIdx = Cursor.matchPair (buffer, cursorIdx)
|
||||
in
|
||||
if cursorIdx = otherIdx then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low + 1
|
||||
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankToNextMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, buffer, ...} = app
|
||||
val high = PersistentVector.nextMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if high = ~1 orelse high <= cursorIdx then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val length = high - cursorIdx
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankToPrevMatch (app: app_type, count) =
|
||||
let
|
||||
val {cursorIdx, searchList, buffer, ...} = app
|
||||
val low = PersistentVector.prevMatch (cursorIdx, searchList, count)
|
||||
in
|
||||
if low = ~1 orelse low >= cursorIdx then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val length = cursorIdx - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun helpYankToChr
|
||||
(app: app_type, buffer, cursorIdx, otherIdx, count, fMove, fInc, chr) =
|
||||
if count = 0 then
|
||||
let
|
||||
val low = Int.min (cursorIdx, otherIdx)
|
||||
val high = Int.max (cursorIdx, otherIdx)
|
||||
val length = high - low
|
||||
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (otherIdx, buffer)
|
||||
val newOtherIdx = fMove (buffer, otherIdx, chr)
|
||||
val newCount = if newOtherIdx = otherIdx then 0 else count - 1
|
||||
val newOtherIdx = fInc (newOtherIdx, 1)
|
||||
in
|
||||
helpYankToChr
|
||||
(app, buffer, cursorIdx, newOtherIdx, newCount, fMove, fInc, chr)
|
||||
end
|
||||
|
||||
fun yankToNextChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val length = newCursorIdx - cursorIdx + 1
|
||||
val buffer = LineGap.goToIdx (newCursorIdx, buffer)
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankTillNextChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toNextChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val length = newCursorIdx - cursorIdx
|
||||
val buffer = LineGap.goToIdx (newCursorIdx, buffer)
|
||||
val str = LineGap.substring (cursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankToPrevChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val length = cursorIdx - newCursorIdx
|
||||
val str = LineGap.substring (newCursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankTillPrevChr (app: app_type, count, chr) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val newCursorIdx =
|
||||
Cursor.toPrevChr (buffer, cursorIdx, {findChr = chr, count = count})
|
||||
in
|
||||
if newCursorIdx = ~1 then
|
||||
NormalFinish.clearMode app
|
||||
else
|
||||
let
|
||||
val newCursorIdx = newCursorIdx + 1
|
||||
val length = cursorIdx - newCursorIdx
|
||||
val str = LineGap.substring (newCursorIdx, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
end
|
||||
|
||||
fun yankToStart (app: app_type) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
|
||||
val high = Cursor.viDlrForDelete (buffer, cursorIdx, 1)
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val str = LineGap.substring (0, high, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankInsideWord (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = Cursor.prevWordStrict (buffer, cursorIdx, 1)
|
||||
val high = Cursor.endOfWordStrict (buffer, cursorIdx, 1)
|
||||
|
||||
val high = high + 1
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val length = high - low
|
||||
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
if str = "\n" then NormalFinish.clearMode app
|
||||
else finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankInsideWORD (app: app_type) =
|
||||
let
|
||||
val {buffer, cursorIdx, ...} = app
|
||||
val buffer = LineGap.goToIdx (cursorIdx, buffer)
|
||||
val low = Cursor.prevWORDStrict (buffer, cursorIdx, 1)
|
||||
val high = Cursor.endOfWORDStrict (buffer, cursorIdx, 1)
|
||||
|
||||
val high = high + 1
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val length = high - low
|
||||
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
if str = "\n" then NormalFinish.clearMode app
|
||||
else finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun finishAfterYankInside (app: app_type, low, high, buffer) =
|
||||
let
|
||||
val length = high - low
|
||||
val str = LineGap.substring (low, length, buffer)
|
||||
in
|
||||
finish (app, buffer, str)
|
||||
end
|
||||
|
||||
fun yankInsideChrOpen (app: app_type, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
|
||||
val start = cursorIdx + 1
|
||||
val buffer = LineGap.goToIdx (start, buffer)
|
||||
|
||||
val low = Cursor.toPrevChr (buffer, start, {findChr = chr, count = 1})
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val high = Cursor.matchPair (buffer, low)
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val low = low + 1
|
||||
in
|
||||
if low = high then NormalFinish.clearMode app
|
||||
else finishAfterYankInside (app, low, high, buffer)
|
||||
end
|
||||
|
||||
fun yankInsideChrClose (app: app_type, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
|
||||
val start = Int.max (cursorIdx - 1, 0)
|
||||
val buffer = LineGap.goToIdx (start, buffer)
|
||||
|
||||
val high = Cursor.toNextChr (buffer, start, {findChr = chr, count = 1})
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val low = Cursor.matchPair (buffer, high) + 1
|
||||
in
|
||||
if low = high then NormalFinish.clearMode app
|
||||
else finishAfterYankInside (app, low, high, buffer)
|
||||
end
|
||||
|
||||
fun yankAroundChrOpen (app: app_type, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
|
||||
val start = cursorIdx + 1
|
||||
val buffer = LineGap.goToIdx (start, buffer)
|
||||
|
||||
val low = Cursor.toPrevChr (buffer, start, {findChr = chr, count = 1})
|
||||
val buffer = LineGap.goToIdx (low, buffer)
|
||||
val high = Cursor.matchPair (buffer, low) + 1
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val low = low
|
||||
in
|
||||
if low = high then NormalFinish.clearMode app
|
||||
else finishAfterYankInside (app, low, high, buffer)
|
||||
end
|
||||
|
||||
fun yankAroundChrClose (app: app_type, chr) =
|
||||
let
|
||||
val {cursorIdx, buffer, ...} = app
|
||||
|
||||
val start = Int.max (cursorIdx - 1, 0)
|
||||
val buffer = LineGap.goToIdx (start, buffer)
|
||||
|
||||
val high = Cursor.toNextChr (buffer, start, {findChr = chr, count = 1})
|
||||
val buffer = LineGap.goToIdx (high, buffer)
|
||||
val low = Cursor.matchPair (buffer, high)
|
||||
val high = high + 1
|
||||
in
|
||||
if low = high then NormalFinish.clearMode app
|
||||
else finishAfterYankInside (app, low, high, buffer)
|
||||
end
|
||||
end
|
||||
937
shf/fcore/persistent-vector.sml
Normal file
937
shf/fcore/persistent-vector.sml
Normal file
@@ -0,0 +1,937 @@
|
||||
structure PersistentVector =
|
||||
struct
|
||||
(* Clojure-style persistent vector, for building search list.
|
||||
* There is an "int table" too, which stores the last index
|
||||
* at the node with the same index.
|
||||
* We can use the size table for binary search.
|
||||
* *)
|
||||
datatype t =
|
||||
BRANCH of t vector * int vector
|
||||
| LEAF of {start: int, finish: int} vector * int vector
|
||||
|
||||
val maxSize = 32
|
||||
val halfSize = 16
|
||||
|
||||
fun isEmpty t =
|
||||
case t of
|
||||
LEAF (_, sizes) => Vector.length sizes = 0
|
||||
| BRANCH (_, sizes) => Vector.length sizes = 0
|
||||
|
||||
val empty = LEAF (#[], #[])
|
||||
|
||||
datatype append_result = APPEND of t | UPDATE of t
|
||||
|
||||
fun isInRange (checkIdx, t) =
|
||||
case t of
|
||||
BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val searchIdx = BinSearch.equalOrMore (checkIdx, sizes)
|
||||
in
|
||||
if searchIdx = ~1 then
|
||||
false
|
||||
else if searchIdx = 0 then
|
||||
isInRange (checkIdx, Vector.sub (nodes, searchIdx))
|
||||
else
|
||||
let
|
||||
val nextCheckIdx = checkIdx - Vector.sub (sizes, searchIdx - 1)
|
||||
in
|
||||
isInRange (nextCheckIdx, Vector.sub (nodes, searchIdx))
|
||||
end
|
||||
end
|
||||
| LEAF (values, sizes) =>
|
||||
let
|
||||
val searchIdx = BinSearch.equalOrMore (checkIdx, sizes)
|
||||
in
|
||||
if searchIdx = ~1 then
|
||||
false
|
||||
else
|
||||
let
|
||||
val {start, finish} = Vector.sub (values, searchIdx)
|
||||
in
|
||||
checkIdx >= start andalso checkIdx <= finish
|
||||
end
|
||||
end
|
||||
|
||||
fun getFinishIdx t =
|
||||
case t of
|
||||
BRANCH (_, sizes) => Vector.sub (sizes, Vector.length sizes - 1)
|
||||
| LEAF (_, sizes) => Vector.sub (sizes, Vector.length sizes - 1)
|
||||
|
||||
fun getStartIdx t =
|
||||
case t of
|
||||
BRANCH (nodes, _) => getStartIdx (Vector.sub (nodes, 0))
|
||||
| LEAF (items, _) =>
|
||||
if Vector.length items = 0 then
|
||||
0
|
||||
else
|
||||
#start (Vector.sub (items, 0))
|
||||
|
||||
fun helpAppend (start, finish, tree) =
|
||||
case tree of
|
||||
BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val lastNode = Vector.sub (nodes, Vector.length nodes - 1)
|
||||
val prevSize =
|
||||
if Vector.length sizes > 1 then
|
||||
Vector.sub (sizes, Vector.length sizes - 2)
|
||||
else
|
||||
0
|
||||
in
|
||||
case helpAppend (start - prevSize, finish - prevSize, lastNode) of
|
||||
UPDATE newLast =>
|
||||
let
|
||||
val lastPos = Vector.length nodes - 1
|
||||
val newNode = Vector.update (nodes, lastPos, newLast)
|
||||
val newSizes = Vector.update (sizes, lastPos, finish)
|
||||
val newNode = BRANCH (newNode, newSizes)
|
||||
in
|
||||
UPDATE newNode
|
||||
end
|
||||
| APPEND newVec =>
|
||||
if Vector.length nodes = maxSize then
|
||||
let
|
||||
(* adjust "finish" so that it does not consider
|
||||
* offset for "lower" vector *)
|
||||
val finish = finish - Vector.sub (sizes, Vector.length sizes - 1)
|
||||
val newNode = BRANCH (#[newVec], #[finish])
|
||||
in
|
||||
APPEND newNode
|
||||
end
|
||||
else
|
||||
let
|
||||
val newNodes = Vector.concat [nodes, #[newVec]]
|
||||
val newSizes = Vector.concat [sizes, #[finish]]
|
||||
val newNodes = BRANCH (newNodes, newSizes)
|
||||
in
|
||||
UPDATE newNodes
|
||||
end
|
||||
end
|
||||
| LEAF (values, sizes) =>
|
||||
if Vector.length values + 1 > maxSize then
|
||||
(* when we split a leaf into two vectors,
|
||||
* we want to adjust the start and finish parameters
|
||||
* so that they don't contain the offset relevant to the
|
||||
* "lower" vector, which was split from *)
|
||||
let
|
||||
val prevFinish = Vector.sub (sizes, Vector.length sizes - 1)
|
||||
val start = start - prevFinish
|
||||
val finish = finish - prevFinish
|
||||
val newNode = LEAF (#[{start = start, finish = finish}], #[finish])
|
||||
in
|
||||
APPEND newNode
|
||||
end
|
||||
else
|
||||
let
|
||||
val newNode = Vector.concat
|
||||
[values, #[{start = start, finish = finish}]]
|
||||
val newSizes = Vector.concat [sizes, #[finish]]
|
||||
val newNode = LEAF (newNode, newSizes)
|
||||
in
|
||||
UPDATE newNode
|
||||
end
|
||||
|
||||
fun append (start, finish, tree) =
|
||||
case helpAppend (start, finish, tree) of
|
||||
UPDATE t => t
|
||||
| APPEND newNode =>
|
||||
let
|
||||
val maxSize = getFinishIdx tree
|
||||
in
|
||||
BRANCH (#[tree, newNode], #[maxSize, finish])
|
||||
end
|
||||
|
||||
fun getStart tree =
|
||||
case tree of
|
||||
LEAF (values, _) => Vector.sub (values, 0)
|
||||
| BRANCH (nodes, _) => getStart (Vector.sub (nodes, 0))
|
||||
|
||||
fun helpNextMatch (cursorIdx, tree, absOffset) =
|
||||
case tree of
|
||||
LEAF (values, sizes) =>
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (cursorIdx, sizes)
|
||||
in
|
||||
if idx = ~1 then {start = ~1, finish = ~1}
|
||||
else
|
||||
let
|
||||
val {start, finish} = Vector.sub (values, idx)
|
||||
in
|
||||
{start = start + absOffset, finish = finish + absOffset}
|
||||
end
|
||||
end
|
||||
| BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (cursorIdx, sizes)
|
||||
in
|
||||
if idx = ~1 then
|
||||
{start = ~1, finish = ~1}
|
||||
else if idx = 0 then
|
||||
helpNextMatch (cursorIdx, Vector.sub (nodes, idx), absOffset)
|
||||
else
|
||||
let
|
||||
val prevSize = Vector.sub (sizes, idx - 1)
|
||||
val cursorIdx = cursorIdx - prevSize
|
||||
val absOffset = absOffset + prevSize
|
||||
in
|
||||
helpNextMatch (cursorIdx, Vector.sub (nodes, idx), absOffset)
|
||||
end
|
||||
end
|
||||
|
||||
fun loopNextMatch (prevStart, prevFinish, tree, count) =
|
||||
if count = 0 then
|
||||
prevStart
|
||||
else
|
||||
let
|
||||
val {start, finish} = helpNextMatch (prevFinish + 1, tree, 0)
|
||||
in
|
||||
if start = ~1 then
|
||||
let val {start, finish} = getStart tree
|
||||
in loopNextMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
else
|
||||
loopNextMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
|
||||
fun nextMatch (cursorIdx, tree, count) =
|
||||
if isEmpty tree then
|
||||
~1
|
||||
else
|
||||
let
|
||||
val {start, finish} = helpNextMatch (cursorIdx, tree, 0)
|
||||
in
|
||||
if start = ~1 then
|
||||
let val {start, finish} = getStart tree
|
||||
in loopNextMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
else if cursorIdx >= start andalso cursorIdx <= finish then
|
||||
loopNextMatch (start, finish, tree, count)
|
||||
else
|
||||
loopNextMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
|
||||
fun getLast (tree, absOffset) =
|
||||
case tree of
|
||||
LEAF (values, _) =>
|
||||
let
|
||||
val {start, finish} = Vector.sub (values, Vector.length values - 1)
|
||||
in
|
||||
{start = start + absOffset, finish = finish + absOffset}
|
||||
end
|
||||
| BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val prevSize =
|
||||
if Vector.length sizes - 2 >= 0 then
|
||||
Vector.sub (sizes, Vector.length sizes - 2)
|
||||
else
|
||||
0
|
||||
val absOffset = absOffset + prevSize
|
||||
in
|
||||
getLast (Vector.sub (nodes, Vector.length nodes - 1), absOffset)
|
||||
end
|
||||
|
||||
(* slightly tricky.
|
||||
* The `sizes` vector contains the last/finish position of the item
|
||||
* at the corresponding index in the `nodes` or `values` vector
|
||||
* However, what we when searching for the previous match
|
||||
* is different: we want the node that has a start prior
|
||||
* to the cursorIdx.
|
||||
* This information cannot be retrieved with 100% accuracy
|
||||
* using the `sizes` vector.
|
||||
* To get what we want, we recurse downwards using the `sizes` vector.
|
||||
* If we found the node we want, we return it.
|
||||
* Otherwise, we return a state meaning "no node at this position"
|
||||
* and we use the call stack to descend down the node at the previous index.
|
||||
* There might not be a previous index because the current index is 0.
|
||||
* In this case, either the call stack will handle it,
|
||||
* or the caller to `helpPrevMatch` will. *)
|
||||
fun helpPrevMatch (cursorIdx, tree, absOffset) =
|
||||
case tree of
|
||||
LEAF (values, sizes) =>
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (cursorIdx, sizes)
|
||||
in
|
||||
if idx < 0 then
|
||||
{start = ~1, finish = ~1}
|
||||
else if idx = 0 then
|
||||
let
|
||||
val {start, finish} = Vector.sub (values, 0)
|
||||
in
|
||||
if start < cursorIdx then
|
||||
{start = start + absOffset, finish = finish + absOffset}
|
||||
else
|
||||
{start = ~1, finish = ~1}
|
||||
end
|
||||
else
|
||||
let
|
||||
val {start, finish} = Vector.sub (values, idx)
|
||||
in
|
||||
if cursorIdx > start then
|
||||
{start = start + absOffset, finish = finish + absOffset}
|
||||
else
|
||||
let
|
||||
val {start, finish} = Vector.sub (values, idx - 1)
|
||||
in
|
||||
{start = start + absOffset, finish = finish + absOffset}
|
||||
end
|
||||
end
|
||||
end
|
||||
| BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (cursorIdx, sizes)
|
||||
in
|
||||
if idx < 0 then
|
||||
{start = ~1, finish = ~1}
|
||||
else if idx = 0 then
|
||||
helpPrevMatch (cursorIdx, Vector.sub (nodes, idx), absOffset)
|
||||
else
|
||||
let
|
||||
val prevSize = Vector.sub (sizes, idx - 1)
|
||||
val node = Vector.sub (nodes, idx)
|
||||
val result =
|
||||
helpPrevMatch (cursorIdx - prevSize, node, absOffset + prevSize)
|
||||
in
|
||||
if #start result = ~1 then
|
||||
let
|
||||
val prevSize =
|
||||
if idx - 2 >= 0 then
|
||||
Vector.sub (sizes, idx - 2)
|
||||
else
|
||||
0
|
||||
val absOffset = absOffset + prevSize
|
||||
in
|
||||
getLast (Vector.sub (nodes, idx - 1), absOffset)
|
||||
end
|
||||
else result
|
||||
end
|
||||
end
|
||||
|
||||
fun loopPrevMatch (prevStart, prevFinish, tree, count) =
|
||||
if count = 0 then
|
||||
prevStart
|
||||
else
|
||||
let
|
||||
val {start, finish} = helpPrevMatch (prevFinish - 1, tree, 0)
|
||||
in
|
||||
if start = ~1 then
|
||||
let val {start, finish} = getLast (tree, 0)
|
||||
in loopPrevMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
else
|
||||
loopPrevMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
|
||||
fun prevMatch (cursorIdx, tree, count) =
|
||||
if isEmpty tree then
|
||||
~1
|
||||
else
|
||||
let
|
||||
val {start, finish} = helpPrevMatch (cursorIdx, tree, 0)
|
||||
in
|
||||
if start = ~1 then
|
||||
let val {start, finish} = getLast (tree, 0)
|
||||
in loopPrevMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
else if cursorIdx >= start andalso cursorIdx <= finish then
|
||||
loopPrevMatch (start, finish, tree, count)
|
||||
else
|
||||
loopPrevMatch (start, finish, tree, count - 1)
|
||||
end
|
||||
|
||||
fun splitLeft (splitIdx, tree) =
|
||||
case tree of
|
||||
LEAF (items, sizes) =>
|
||||
if Vector.length items = 0 then
|
||||
(* if tree is empty, then just return tree *)
|
||||
tree
|
||||
else
|
||||
let
|
||||
val {start, ...} = Vector.sub (items, 0)
|
||||
in
|
||||
(* if all items are after splitIdx,
|
||||
* then we want to return an empty tree,
|
||||
* splitting everything *)
|
||||
if splitIdx < start then
|
||||
empty
|
||||
else if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then
|
||||
(* if all items are before splitIdx,
|
||||
* then we want to return the same tree,
|
||||
* splitting nothing *)
|
||||
tree
|
||||
else
|
||||
(* we want to split from somewhere in middle, keeping left *)
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (splitIdx, sizes)
|
||||
val idx = SOME idx
|
||||
|
||||
val items = VectorSlice.slice (items, 0, idx)
|
||||
val items = VectorSlice.vector items
|
||||
|
||||
val sizes = VectorSlice.slice (sizes, 0, idx)
|
||||
val sizes = VectorSlice.vector sizes
|
||||
in
|
||||
LEAF (items, sizes)
|
||||
end
|
||||
end
|
||||
| BRANCH (nodes, sizes) =>
|
||||
if Vector.length nodes = 0 then
|
||||
tree
|
||||
else
|
||||
if splitIdx < Vector.sub (sizes, 0) then
|
||||
(* we want to split first node from rest *)
|
||||
splitLeft (splitIdx, Vector.sub (nodes, 0))
|
||||
else if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then
|
||||
(* split point is after this subtree,
|
||||
* so return this subtree unchanged *)
|
||||
tree
|
||||
else
|
||||
(* we want to split from somewhere in middle *)
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (splitIdx, sizes)
|
||||
val prevSize =
|
||||
if idx = 0 then
|
||||
0
|
||||
else
|
||||
Vector.sub (sizes, idx - 1)
|
||||
val child =
|
||||
splitLeft (splitIdx - prevSize, Vector.sub (nodes, idx))
|
||||
|
||||
val sizes = VectorSlice.slice (sizes, 0, SOME idx)
|
||||
val nodes = VectorSlice.slice (nodes, 0, SOME idx)
|
||||
in
|
||||
if isEmpty child then
|
||||
let
|
||||
val sizes = VectorSlice.vector sizes
|
||||
val nodes = VectorSlice.vector nodes
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
else
|
||||
let
|
||||
val childSize = VectorSlice.full #[getFinishIdx child + prevSize]
|
||||
val sizes =VectorSlice.concat [sizes, childSize]
|
||||
|
||||
val childNode = VectorSlice.full #[child]
|
||||
val nodes = VectorSlice.concat [nodes, childNode]
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
end
|
||||
|
||||
(* When we split in this function,
|
||||
* we always want to update the sizes vector
|
||||
* so that the relative rope-like metadata is valid *)
|
||||
fun splitRight (splitIdx, tree) =
|
||||
case tree of
|
||||
BRANCH (nodes, sizes) =>
|
||||
if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then
|
||||
(* splitIdx is greater than largest element,
|
||||
* so we want to remove everything;
|
||||
* or, in other words, we want to return an empty vec *)
|
||||
empty
|
||||
else
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (splitIdx, sizes)
|
||||
val prevSize =
|
||||
if idx = 0 then
|
||||
0
|
||||
else
|
||||
Vector.sub (sizes, idx - 1)
|
||||
|
||||
val oldChildSize = Vector.sub (sizes, idx)
|
||||
val child = splitRight (splitIdx - prevSize, Vector.sub (nodes, idx))
|
||||
|
||||
val len = Vector.length nodes - (idx + 1)
|
||||
val sizesSlice = VectorSlice.slice (sizes, idx + 1, SOME len)
|
||||
val nodesSlice = VectorSlice.slice (nodes, idx + 1, SOME len)
|
||||
in
|
||||
if isEmpty child then
|
||||
if VectorSlice.length sizesSlice = 0 then
|
||||
(* if we descended down last node and last node became empty,
|
||||
* then return empty vector *)
|
||||
empty
|
||||
else
|
||||
let
|
||||
val nodes = VectorSlice.vector nodesSlice
|
||||
val sizes = VectorSlice.map (fn el => el - oldChildSize) sizesSlice
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
else
|
||||
let
|
||||
val newChildSize = getFinishIdx child
|
||||
val sizes = Vector.tabulate (VectorSlice.length sizesSlice + 1,
|
||||
fn i =>
|
||||
if i = 0 then
|
||||
newChildSize
|
||||
else
|
||||
let
|
||||
val el = VectorSlice.sub (sizesSlice, i - 1)
|
||||
in
|
||||
el - oldChildSize + newChildSize
|
||||
end
|
||||
)
|
||||
|
||||
val child = VectorSlice.full #[child]
|
||||
val nodes = VectorSlice.concat [child, nodesSlice]
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
end
|
||||
| LEAF (items, sizes) =>
|
||||
if Vector.length items = 0 then
|
||||
tree
|
||||
else
|
||||
if splitIdx > Vector.sub (sizes, Vector.length sizes - 1) then
|
||||
empty
|
||||
else if splitIdx < #start (Vector.sub (items, 0)) then
|
||||
tree
|
||||
else
|
||||
let
|
||||
val idx = BinSearch.equalOrMore (splitIdx, sizes)
|
||||
val {start, finish} = Vector.sub (items, idx)
|
||||
val idx =
|
||||
if splitIdx >= start then
|
||||
idx + 1
|
||||
else
|
||||
idx
|
||||
in
|
||||
if idx >= Vector.length items then
|
||||
empty
|
||||
else
|
||||
let
|
||||
val prevSize =
|
||||
if idx > 0 then
|
||||
Vector.sub (sizes, idx - 1)
|
||||
else
|
||||
0
|
||||
val len = Vector.length items - idx
|
||||
val itemsSlice = VectorSlice.slice (items, idx, SOME len)
|
||||
val items = VectorSlice.map
|
||||
(fn {start, finish} =>
|
||||
{start = start - prevSize, finish = finish - prevSize}
|
||||
)
|
||||
itemsSlice
|
||||
val sizes = Vector.map #finish items
|
||||
in
|
||||
LEAF (items, sizes)
|
||||
end
|
||||
end
|
||||
|
||||
fun decrementBy (decBy, tree) =
|
||||
case tree of
|
||||
BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val child = decrementBy (decBy, Vector.sub (nodes, 0))
|
||||
val nodes = Vector.update (nodes, 0, child)
|
||||
val sizes = Vector.map (fn sz => sz - decBy) sizes
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
| LEAF (items, sizes) =>
|
||||
let
|
||||
val items = Vector.map
|
||||
(fn {start, finish} =>
|
||||
{start = start - decBy, finish = finish - decBy}
|
||||
) items
|
||||
val sizes = Vector.map #finish items
|
||||
in
|
||||
LEAF (items, sizes)
|
||||
end
|
||||
|
||||
fun incrementBy (incBy, tree) =
|
||||
case tree of
|
||||
BRANCH (nodes, sizes) =>
|
||||
let
|
||||
val child = incrementBy (incBy, Vector.sub (nodes, 0))
|
||||
val nodes = Vector.update (nodes, 0, child)
|
||||
val sizes = Vector.map (fn sz => sz + incBy) sizes
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
| LEAF (items, sizes) =>
|
||||
let
|
||||
val items = Vector.map
|
||||
(fn {start, finish} =>
|
||||
{start = start + incBy, finish = finish + incBy}
|
||||
) items
|
||||
val sizes = Vector.map #finish items
|
||||
in
|
||||
LEAF (items, sizes)
|
||||
end
|
||||
|
||||
fun countDepthLoop (counter, tree) =
|
||||
case tree of
|
||||
BRANCH (nodes, _) => countDepthLoop (counter + 1, Vector.sub (nodes, 0))
|
||||
| LEAF (_, _) => counter + 1
|
||||
|
||||
fun countDepth tree = countDepthLoop (0, tree)
|
||||
|
||||
datatype merge_same_depth_result =
|
||||
MERGE_SAME_DEPTH_UPDATE of t
|
||||
| MERGE_SAME_DEPTH_FULL
|
||||
|
||||
fun mergeSameDepth (left, right) =
|
||||
case (left, right) of
|
||||
(LEAF (leftItems, leftSizes), LEAF (rightItems, rightSizes)) =>
|
||||
if Vector.length leftItems + Vector.length rightItems <= maxSize then
|
||||
let
|
||||
val offset = Vector.sub (leftSizes, Vector.length leftSizes - 1)
|
||||
val newVecLen = Vector.length leftItems + Vector.length rightItems
|
||||
val items = Vector.tabulate (newVecLen,
|
||||
fn i =>
|
||||
if i < Vector.length leftItems then
|
||||
Vector.sub (leftItems, i)
|
||||
else
|
||||
let
|
||||
val {start, finish} =
|
||||
Vector.sub (rightItems, i - Vector.length leftItems)
|
||||
in
|
||||
{start = start + offset, finish = finish + offset}
|
||||
end
|
||||
)
|
||||
val sizes = Vector.map #finish items
|
||||
in
|
||||
MERGE_SAME_DEPTH_UPDATE (LEAF (items, sizes))
|
||||
end
|
||||
else
|
||||
MERGE_SAME_DEPTH_FULL
|
||||
| (BRANCH (leftNodes, leftSizes), BRANCH (rightNodes, rightSizes)) =>
|
||||
if Vector.length leftNodes + Vector.length rightNodes <= maxSize then
|
||||
let
|
||||
val offset = Vector.sub (leftSizes, Vector.length leftSizes - 1)
|
||||
val nodes = Vector.concat [leftNodes, rightNodes]
|
||||
|
||||
val sizes = Vector.tabulate (Vector.length nodes,
|
||||
fn i =>
|
||||
if i < Vector.length leftSizes then
|
||||
Vector.sub (leftSizes, i)
|
||||
else
|
||||
Vector.sub (rightSizes, i - Vector.length leftSizes) + offset
|
||||
)
|
||||
in
|
||||
MERGE_SAME_DEPTH_UPDATE (BRANCH (nodes, sizes))
|
||||
end
|
||||
else
|
||||
MERGE_SAME_DEPTH_FULL
|
||||
| _ =>
|
||||
raise Fail "PersistentVector.mergeSameDepth: \
|
||||
\left and right should both be BRANCH or both be LEAF \
|
||||
\but one is BRANCH and one is LEAF"
|
||||
|
||||
datatype merge_diff_depth_result =
|
||||
MERGE_DIFF_DEPTH_UPDATE of t
|
||||
| MERGE_DIFF_DEPTH_FULL
|
||||
|
||||
fun mergeWhenRightDepthIsGreater (left, right, targetDepth, curDepth) =
|
||||
if curDepth = targetDepth then
|
||||
case mergeSameDepth (left, right) of
|
||||
MERGE_SAME_DEPTH_UPDATE tree => MERGE_DIFF_DEPTH_UPDATE tree
|
||||
| MERGE_SAME_DEPTH_FULL => MERGE_DIFF_DEPTH_FULL
|
||||
else
|
||||
case right of
|
||||
BRANCH (nodes, sizes) =>
|
||||
(case mergeWhenRightDepthIsGreater
|
||||
(left, Vector.sub (nodes, 0), targetDepth, curDepth + 1) of
|
||||
MERGE_DIFF_DEPTH_UPDATE child =>
|
||||
let
|
||||
val oldChildSize = Vector.sub (sizes, 0)
|
||||
val newChildSize = getFinishIdx child
|
||||
val difference = newChildSize - oldChildSize
|
||||
|
||||
val nodes = Vector.update (nodes, 0, child)
|
||||
val sizes = Vector.map (fn el => el + difference) sizes
|
||||
in
|
||||
MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes))
|
||||
end
|
||||
| MERGE_DIFF_DEPTH_FULL =>
|
||||
let
|
||||
val leftSize = getFinishIdx left
|
||||
val sizes = Vector.tabulate (Vector.length nodes + 1,
|
||||
fn i =>
|
||||
if i = 0 then
|
||||
leftSize
|
||||
else
|
||||
Vector.sub (sizes, i - 1) + leftSize
|
||||
)
|
||||
val nodes = Vector.concat [#[left], nodes]
|
||||
in
|
||||
MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes))
|
||||
end)
|
||||
| LEAF _ =>
|
||||
raise Fail "PersistentVector.mergeWhenRightDepthIsGreater: \
|
||||
\reached LEAF before (curDepth = targetDepth)"
|
||||
|
||||
fun mergeWhenLeftDepthIsGreater (left, right, targetDepth, curDepth) =
|
||||
if targetDepth = curDepth then
|
||||
case mergeSameDepth (left, right) of
|
||||
MERGE_SAME_DEPTH_UPDATE tree => MERGE_DIFF_DEPTH_UPDATE tree
|
||||
| MERGE_SAME_DEPTH_FULL => MERGE_DIFF_DEPTH_FULL
|
||||
else
|
||||
case left of
|
||||
BRANCH (nodes, sizes) =>
|
||||
(case
|
||||
mergeWhenLeftDepthIsGreater (
|
||||
Vector.sub (nodes, Vector.length nodes - 1),
|
||||
right,
|
||||
targetDepth,
|
||||
curDepth + 1) of
|
||||
MERGE_DIFF_DEPTH_UPDATE child =>
|
||||
let
|
||||
val lastIdx = Vector.length sizes - 1
|
||||
val oldChildSize = Vector.sub (sizes, lastIdx)
|
||||
val newChildSize = getFinishIdx child
|
||||
val difference = newChildSize - oldChildSize
|
||||
|
||||
val nodes = Vector.update (nodes, lastIdx, child)
|
||||
val sizes = Vector.map (fn el => el + difference) sizes
|
||||
in
|
||||
MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes))
|
||||
end
|
||||
| MERGE_DIFF_DEPTH_FULL =>
|
||||
let
|
||||
val maxLeftSize = Vector.sub (sizes, Vector.length sizes - 1)
|
||||
val rightSize = getFinishIdx right + maxLeftSize
|
||||
val sizes = Vector.concat [sizes, #[rightSize]]
|
||||
val nodes = Vector.concat [nodes, #[right]]
|
||||
in
|
||||
MERGE_DIFF_DEPTH_UPDATE (BRANCH (nodes, sizes))
|
||||
end)
|
||||
| LEAF _ =>
|
||||
raise Fail "PersistentVector.mergeWhenLeftDepthIsGreater: \
|
||||
\reached LEAF before (curDepth = targetDepth)"
|
||||
|
||||
fun merge (left, right) =
|
||||
let
|
||||
val leftDepth = countDepth left
|
||||
val rightDepth = countDepth right
|
||||
in
|
||||
if leftDepth = rightDepth then
|
||||
case mergeSameDepth (left, right) of
|
||||
MERGE_SAME_DEPTH_UPDATE t => t
|
||||
| MERGE_SAME_DEPTH_FULL =>
|
||||
let
|
||||
val leftSize = getFinishIdx left
|
||||
val sizes = #[leftSize, getFinishIdx right + leftSize]
|
||||
val nodes = #[left, right]
|
||||
in
|
||||
BRANCH (nodes, sizes)
|
||||
end
|
||||
else if leftDepth < rightDepth then
|
||||
let
|
||||
val targetDepth = rightDepth - leftDepth
|
||||
in
|
||||
case mergeWhenRightDepthIsGreater
|
||||
(left, right, targetDepth, 0) of
|
||||
MERGE_DIFF_DEPTH_UPDATE t => t
|
||||
| MERGE_DIFF_DEPTH_FULL => empty
|
||||
end
|
||||
else
|
||||
let
|
||||
val targetDepth = leftDepth - rightDepth
|
||||
in
|
||||
case mergeWhenLeftDepthIsGreater
|
||||
(left, right, targetDepth, 0) of
|
||||
MERGE_DIFF_DEPTH_UPDATE t => t
|
||||
| MERGE_DIFF_DEPTH_FULL => empty
|
||||
end
|
||||
end
|
||||
|
||||
fun delete (start, length, tree) =
|
||||
if isEmpty tree then
|
||||
empty
|
||||
else
|
||||
let
|
||||
val finish = start + length
|
||||
|
||||
val matchAfterFinish = nextMatch (finish, tree, 1)
|
||||
val matchAfterFinish =
|
||||
if matchAfterFinish < finish then
|
||||
~1
|
||||
else
|
||||
matchAfterFinish
|
||||
in
|
||||
let
|
||||
val left = splitLeft (start, tree)
|
||||
val right = splitRight (finish, tree)
|
||||
in
|
||||
if isEmpty left andalso isEmpty right then
|
||||
empty
|
||||
else if isEmpty left then
|
||||
(* just decrement right and return it *)
|
||||
let
|
||||
val rightStart = getStartIdx right
|
||||
val shouldBeStartIdx = matchAfterFinish - length
|
||||
val difference = rightStart - shouldBeStartIdx
|
||||
in
|
||||
if difference = 0 then
|
||||
right
|
||||
else
|
||||
decrementBy (difference, right)
|
||||
end
|
||||
else if isEmpty right then
|
||||
(* return left half without doing anything *)
|
||||
left
|
||||
else
|
||||
(* decrement right, and then merge both together *)
|
||||
let
|
||||
val leftSize = getFinishIdx left
|
||||
val rightStartRelative = getStartIdx right
|
||||
val rightStartAbsolute = leftSize + rightStartRelative
|
||||
|
||||
val shouldBeStartIdx = matchAfterFinish - length
|
||||
val difference = rightStartAbsolute - shouldBeStartIdx
|
||||
in
|
||||
if difference = 0 then
|
||||
merge (left, right)
|
||||
else
|
||||
let
|
||||
val right = decrementBy (difference, right)
|
||||
in
|
||||
merge (left, right)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
(* Usually, when inserting, we want the absolute metadata
|
||||
* to be adjusted appropriately.
|
||||
* An insertion should cause the absolute metadata to increment.
|
||||
* However, we sometimes want to insert a match without adjusting
|
||||
* the absolute metadata in this way.
|
||||
* We want to do this when deleting some part of the buffer
|
||||
* would cause a new match to be found, for example. *)
|
||||
fun insertMatchKeepingAbsoluteInddices (start, finish, tree) =
|
||||
let
|
||||
val matchAfterFinish = nextMatch (finish, tree, 1)
|
||||
in
|
||||
if matchAfterFinish <= finish then
|
||||
(* no match after the 'finish', so we can just append to 'tree' *)
|
||||
append (start, finish, tree)
|
||||
else
|
||||
let
|
||||
val left = splitLeft (start, tree)
|
||||
val right = splitRight (finish, tree)
|
||||
|
||||
val left = append (start, finish, left)
|
||||
|
||||
val rightStartRelative = getStartIdx right
|
||||
val rightStartAbsolute = rightStartRelative + finish
|
||||
val difference = rightStartAbsolute - matchAfterFinish
|
||||
val right = decrementBy (difference, right)
|
||||
in
|
||||
merge (left, right)
|
||||
end
|
||||
end
|
||||
|
||||
fun extendExistingMatch (start, newFinish, tree) =
|
||||
let
|
||||
val matchAfterFinish = nextMatch (newFinish, tree, 1)
|
||||
val left = splitLeft (start, tree)
|
||||
val left = append (start, newFinish, left)
|
||||
in
|
||||
if matchAfterFinish <= newFinish then
|
||||
(* no match after newFinish, so we can return 'left'
|
||||
* which has the newFinish appended *)
|
||||
left
|
||||
else
|
||||
let
|
||||
val right = splitRight (newFinish, tree)
|
||||
|
||||
val leftFinish = getFinishIdx left
|
||||
val rightStartRelative = getStartIdx right
|
||||
|
||||
val rightStartAbsolute = rightStartRelative + leftFinish
|
||||
val difference = rightStartAbsolute - matchAfterFinish
|
||||
val right = decrementBy (difference, right)
|
||||
in
|
||||
merge (left, right)
|
||||
end
|
||||
end
|
||||
|
||||
(* functions only for testing *)
|
||||
fun childrenHaveSameDepth (pos, nodes, expectedDepth) =
|
||||
if pos = Vector.length nodes then
|
||||
true
|
||||
else
|
||||
let
|
||||
val node = Vector.sub (nodes, pos)
|
||||
in
|
||||
if allLeavesAtSameDepth node then
|
||||
let
|
||||
val nodeDepth = countDepth node
|
||||
in
|
||||
if nodeDepth = expectedDepth then
|
||||
childrenHaveSameDepth (pos + 1, nodes, expectedDepth)
|
||||
else
|
||||
false
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
and allLeavesAtSameDepth tree =
|
||||
case tree of
|
||||
BRANCH (nodes, _) =>
|
||||
let
|
||||
val expectedDepth = countDepth (Vector.sub (nodes, 0))
|
||||
in
|
||||
childrenHaveSameDepth (0, nodes, expectedDepth)
|
||||
end
|
||||
| LEAF _ => true
|
||||
|
||||
fun fromListLoop (lst, acc) =
|
||||
case lst of
|
||||
{start, finish} :: tl =>
|
||||
let
|
||||
val acc = append (start, finish, acc)
|
||||
in
|
||||
fromListLoop (tl, acc)
|
||||
end
|
||||
| [] => acc
|
||||
|
||||
fun fromList coords = fromListLoop (coords, empty)
|
||||
|
||||
fun toListLoop (tree, acc) =
|
||||
case tree of
|
||||
BRANCH (nodes, _) =>
|
||||
let
|
||||
fun branchLoop (pos, acc) =
|
||||
if pos = Vector.length nodes then
|
||||
acc
|
||||
else
|
||||
let
|
||||
val acc = toListLoop (Vector.sub (nodes, pos), acc)
|
||||
in
|
||||
branchLoop (pos + 1, acc)
|
||||
end
|
||||
in
|
||||
branchLoop (0, acc)
|
||||
end
|
||||
| LEAF (items, _) =>
|
||||
let
|
||||
fun itemLoop (pos, acc, offset) =
|
||||
if pos = Vector.length items then
|
||||
acc
|
||||
else
|
||||
let
|
||||
val {start, finish} = Vector.sub (items, pos)
|
||||
val item = {start = start + offset, finish = finish + offset}
|
||||
in
|
||||
itemLoop (pos + 1, item :: acc, offset)
|
||||
end
|
||||
|
||||
val offset =
|
||||
case acc of
|
||||
{finish, ...} :: _ => finish
|
||||
| [] => 0
|
||||
in
|
||||
itemLoop (0, acc, offset)
|
||||
end
|
||||
|
||||
fun toList tree =
|
||||
let
|
||||
val result = toListLoop (tree, [])
|
||||
in
|
||||
List.rev result
|
||||
end
|
||||
end
|
||||
41
shf/fcore/pipe-cursor.sml
Normal file
41
shf/fcore/pipe-cursor.sml
Normal file
@@ -0,0 +1,41 @@
|
||||
structure PipeCursor =
|
||||
struct
|
||||
fun xToNdc (xOffset, xpos, scale, halfWidth) =
|
||||
((xpos * scale + xOffset) - halfWidth) / halfWidth
|
||||
|
||||
fun yToNdc (yOffset, ypos, scale, halfHeight) =
|
||||
~(((ypos * scale + yOffset) - halfHeight) / halfHeight)
|
||||
|
||||
fun lerp (xOffset: Real32.real, yOffset, z, scale, windowWidth, windowHeight, r, g, b) =
|
||||
let
|
||||
val halfWidth = windowWidth / 2.0
|
||||
val halfHeight = windowHeight / 2.0
|
||||
in
|
||||
#[
|
||||
xToNdc (xOffset, 0.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 13.000000000000000, scale, halfHeight),
|
||||
z,
|
||||
r, g, b,
|
||||
xToNdc (xOffset, 1.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 13.000000000000000, scale, halfHeight),
|
||||
z,
|
||||
r, g, b,
|
||||
xToNdc (xOffset, 0.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 0.000000000000000, scale, halfHeight),
|
||||
z,
|
||||
r, g, b,
|
||||
xToNdc (xOffset, 0.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 0.000000000000000, scale, halfHeight),
|
||||
z,
|
||||
r, g, b,
|
||||
xToNdc (xOffset, 1.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 13.000000000000000, scale, halfHeight),
|
||||
z,
|
||||
r, g, b,
|
||||
xToNdc (xOffset, 1.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 0.000000000000000, scale, halfHeight),
|
||||
z,
|
||||
r, g, b
|
||||
]
|
||||
end
|
||||
end
|
||||
35
shf/fcore/rect.sml
Normal file
35
shf/fcore/rect.sml
Normal file
@@ -0,0 +1,35 @@
|
||||
structure Rect =
|
||||
struct
|
||||
fun xToNdc (xOffset, xpos, scale, halfWidth) =
|
||||
((xpos * scale + xOffset) - halfWidth) / halfWidth
|
||||
|
||||
fun yToNdc (yOffset, ypos, scale, halfHeight) =
|
||||
~(((ypos * scale + yOffset) - halfHeight) / halfHeight)
|
||||
|
||||
fun lerp (xOffset: Real32.real, yOffset, z, scale, windowWidth, windowHeight, r, g, b) =
|
||||
let
|
||||
val halfWidth = windowWidth / 2.0
|
||||
val halfHeight = windowHeight / 2.0
|
||||
in
|
||||
#[
|
||||
xToNdc (xOffset, 0.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 13.000000000000000, scale, halfHeight),
|
||||
z, r, g, b,
|
||||
xToNdc (xOffset, 7.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 13.000000000000000, scale, halfHeight),
|
||||
z, r, g, b,
|
||||
xToNdc (xOffset, 0.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 0.000000000000000, scale, halfHeight),
|
||||
z, r, g, b,
|
||||
xToNdc (xOffset, 0.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 0.000000000000000, scale, halfHeight),
|
||||
z, r, g, b,
|
||||
xToNdc (xOffset, 7.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 13.000000000000000, scale, halfHeight),
|
||||
z, r, g, b,
|
||||
xToNdc (xOffset, 7.000000000000000, scale, halfWidth),
|
||||
yToNdc (yOffset, 0.000000000000000, scale, halfHeight),
|
||||
z, r, g, b
|
||||
]
|
||||
end
|
||||
end
|
||||
928
shf/fcore/search-list/dfa-gen.sml
Normal file
928
shf/fcore/search-list/dfa-gen.sml
Normal file
@@ -0,0 +1,928 @@
|
||||
signature DFA_GEN_PARAMS =
|
||||
sig
|
||||
val endMarker: char
|
||||
val charIsEqual: char * char -> bool
|
||||
end
|
||||
|
||||
signature DFA_GEN =
|
||||
sig
|
||||
type dfa = int vector vector
|
||||
type dfa_state = int
|
||||
|
||||
val fromString: string -> dfa
|
||||
|
||||
val nextState: dfa * dfa_state * char -> dfa_state
|
||||
val isFinal: dfa * dfa_state -> bool
|
||||
val isDead: dfa_state -> bool
|
||||
|
||||
val matchString: dfa * string -> (int * int) list
|
||||
end
|
||||
|
||||
functor MakeDfaGen(Fn: DFA_GEN_PARAMS): DFA_GEN =
|
||||
struct
|
||||
datatype parse_tree =
|
||||
CHAR_LITERAL of {char: char, position: int}
|
||||
| WILDCARD of int
|
||||
| IS_ANY_CHARACTER of {chars: char vector, position: int}
|
||||
| NOT_ANY_CHARACTER of {chars: char vector, position: int}
|
||||
| CONCAT of
|
||||
{l: parse_tree, r: parse_tree, leftMaxState: int, rightMaxState: int}
|
||||
| ALTERNATION of
|
||||
{l: parse_tree, r: parse_tree, leftMaxState: int, rightMaxState: int}
|
||||
| ZERO_OR_ONE of parse_tree
|
||||
| ZERO_OR_MORE of parse_tree
|
||||
| ONE_OR_MORE of parse_tree
|
||||
| GROUP of parse_tree
|
||||
|
||||
fun isNullable tree =
|
||||
case tree of
|
||||
CHAR_LITERAL _ => false
|
||||
| WILDCARD _ => false
|
||||
| IS_ANY_CHARACTER _ => false
|
||||
| NOT_ANY_CHARACTER _ => false
|
||||
|
||||
| CONCAT {l, r, ...} => isNullable l andalso isNullable r
|
||||
| ALTERNATION {l, r, ...} => isNullable l orelse isNullable r
|
||||
|
||||
| ZERO_OR_ONE _ => true
|
||||
| ZERO_OR_MORE _ => true
|
||||
| ONE_OR_MORE regex => isNullable regex
|
||||
|
||||
| GROUP regex => isNullable regex
|
||||
|
||||
fun firstpos (tree, acc) =
|
||||
case tree of
|
||||
CHAR_LITERAL {position, ...} => position :: acc
|
||||
| IS_ANY_CHARACTER {position, ...} => position :: acc
|
||||
| NOT_ANY_CHARACTER {position, ...} => position :: acc
|
||||
| WILDCARD i => i :: acc
|
||||
|
||||
| CONCAT {l, r, ...} =>
|
||||
if isNullable l then
|
||||
let val acc = firstpos (l, acc)
|
||||
in firstpos (r, acc)
|
||||
end
|
||||
else
|
||||
firstpos (l, acc)
|
||||
| ALTERNATION {l, r, ...} =>
|
||||
let val acc = firstpos (l, acc)
|
||||
in firstpos (r, acc)
|
||||
end
|
||||
|
||||
| ZERO_OR_ONE regex => firstpos (regex, acc)
|
||||
| ZERO_OR_MORE regex => firstpos (regex, acc)
|
||||
| ONE_OR_MORE regex => firstpos (regex, acc)
|
||||
| GROUP regex => firstpos (regex, acc)
|
||||
|
||||
fun lastpos (tree, acc) =
|
||||
case tree of
|
||||
CHAR_LITERAL {position, ...} => position :: acc
|
||||
| IS_ANY_CHARACTER {position, ...} => position :: acc
|
||||
| NOT_ANY_CHARACTER {position, ...} => position :: acc
|
||||
| WILDCARD i => i :: acc
|
||||
|
||||
| CONCAT {l, r, ...} =>
|
||||
if isNullable r then
|
||||
let val acc = lastpos (l, acc)
|
||||
in lastpos (r, acc)
|
||||
end
|
||||
else
|
||||
lastpos (r, acc)
|
||||
| ALTERNATION {l, r, ...} =>
|
||||
let val acc = lastpos (l, acc)
|
||||
in lastpos (r, acc)
|
||||
end
|
||||
|
||||
| ZERO_OR_ONE regex => lastpos (regex, acc)
|
||||
| ZERO_OR_MORE regex => lastpos (regex, acc)
|
||||
| ONE_OR_MORE regex => lastpos (regex, acc)
|
||||
| GROUP regex => lastpos (regex, acc)
|
||||
|
||||
structure Set =
|
||||
struct
|
||||
datatype 'a set = BRANCH of 'a set * int * 'a * 'a set | LEAF
|
||||
|
||||
fun isEmpty set =
|
||||
case set of
|
||||
BRANCH _ => false
|
||||
| LEAF => true
|
||||
|
||||
fun insertOrReplace (newKey, newVal, tree) =
|
||||
case tree of
|
||||
BRANCH (l, curKey, curVal, r) =>
|
||||
if newKey > curKey then
|
||||
let val r = insertOrReplace (newKey, newVal, r)
|
||||
in BRANCH (l, curKey, curVal, r)
|
||||
end
|
||||
else if newKey < curKey then
|
||||
let val l = insertOrReplace (newKey, newVal, l)
|
||||
in BRANCH (l, curKey, curVal, r)
|
||||
end
|
||||
else
|
||||
BRANCH (l, newKey, newVal, r)
|
||||
| LEAF => BRANCH (LEAF, newKey, newVal, LEAF)
|
||||
|
||||
fun addFromList (lst, tree) =
|
||||
case lst of
|
||||
[] => tree
|
||||
| k :: tl =>
|
||||
let val tree = insertOrReplace (k, (), tree)
|
||||
in addFromList (tl, tree)
|
||||
end
|
||||
|
||||
fun getOrDefault (findKey, tree, default) =
|
||||
case tree of
|
||||
BRANCH (l, curKey, curVal, r) =>
|
||||
if findKey > curKey then getOrDefault (findKey, r, default)
|
||||
else if findKey < curKey then getOrDefault (findKey, l, default)
|
||||
else curVal
|
||||
| LEAF => default
|
||||
|
||||
fun helpToList (tree, acc) =
|
||||
case tree of
|
||||
BRANCH (l, curKey, curVal, r) =>
|
||||
let
|
||||
val acc = helpToList (r, acc)
|
||||
val acc = (curKey, curVal) :: acc
|
||||
in
|
||||
helpToList (l, acc)
|
||||
end
|
||||
| LEAF => acc
|
||||
|
||||
fun toList tree = helpToList (tree, [])
|
||||
|
||||
fun helpKeysToList (tree, acc) =
|
||||
case tree of
|
||||
BRANCH (l, curKey, _, r) =>
|
||||
let
|
||||
val acc = helpKeysToList (r, acc)
|
||||
val acc = curKey :: acc
|
||||
in
|
||||
helpKeysToList (l, acc)
|
||||
end
|
||||
| LEAF => acc
|
||||
|
||||
fun keysToList tree = helpKeysToList (tree, [])
|
||||
|
||||
fun helpValuesToList (tree, acc) =
|
||||
case tree of
|
||||
BRANCH (l, _, v, r) =>
|
||||
let
|
||||
val acc = helpValuesToList (r, acc)
|
||||
val acc = v :: acc
|
||||
in
|
||||
helpValuesToList (l, acc)
|
||||
end
|
||||
| LEAF => acc
|
||||
|
||||
fun valuesToList tree = helpValuesToList (tree, [])
|
||||
|
||||
fun map (f, tree) =
|
||||
case tree of
|
||||
BRANCH (l, key, value, r) =>
|
||||
let
|
||||
val r = map (f, r)
|
||||
val l = map (f, l)
|
||||
val value = f value
|
||||
in
|
||||
BRANCH (l, key, value, r)
|
||||
end
|
||||
| LEAF => LEAF
|
||||
|
||||
fun foldl (f, tree, acc) =
|
||||
case tree of
|
||||
BRANCH (l, k, v, r) =>
|
||||
let
|
||||
val acc = foldl (f, l, acc)
|
||||
val acc = f (v, acc)
|
||||
in
|
||||
foldl (f, r, acc)
|
||||
end
|
||||
| LEAF => acc
|
||||
|
||||
fun foldr (f, tree, acc) =
|
||||
case tree of
|
||||
BRANCH (l, k, v, r) =>
|
||||
let
|
||||
val acc = foldr (f, r, acc)
|
||||
val acc = f (v, acc)
|
||||
in
|
||||
foldr (f, l, acc)
|
||||
end
|
||||
| LEAF => acc
|
||||
end
|
||||
|
||||
structure ParseDfa =
|
||||
struct
|
||||
(* parsing through precedence climbing algorithm. *)
|
||||
val postfixLevel = 1
|
||||
val concatLevel = 2
|
||||
val altLevel = 3
|
||||
|
||||
local
|
||||
fun loop (pos, str, openParens, closeParens) =
|
||||
if pos = String.size str then
|
||||
NONE
|
||||
else
|
||||
case String.sub (str, pos) of
|
||||
#"(" => loop (pos + 1, str, openParens + 1, closeParens)
|
||||
| #")" =>
|
||||
if closeParens + 1 = openParens then SOME pos
|
||||
else loop (pos + 1, str, openParens, closeParens + 1)
|
||||
| _ => loop (pos + 1, str, openParens, closeParens)
|
||||
in
|
||||
fun getRightParenIdx (pos, str) = loop (pos, str, 1, 0)
|
||||
end
|
||||
|
||||
(* assumes previous char is a backslash *)
|
||||
fun isValidEscapeSequence chr =
|
||||
case chr of
|
||||
(* regex metacharacters *)
|
||||
#"(" => (true, chr)
|
||||
| #")" => (true, chr)
|
||||
| #"[" => (true, chr)
|
||||
| #"]" => (true, chr)
|
||||
| #"+" => (true, chr)
|
||||
| #"*" => (true, chr)
|
||||
| #"|" => (true, chr)
|
||||
| #"?" => (true, chr)
|
||||
| #"." => (true, chr)
|
||||
| #"-" => (true, chr)
|
||||
(* standard escape sequences *)
|
||||
| #"a" => (true, #"\a")
|
||||
| #"b" => (true, #"\b")
|
||||
| #"t" => (true, #"\t")
|
||||
| #"n" => (true, #"\n")
|
||||
| #"v" => (true, #"\v")
|
||||
| #"f" => (true, #"\f")
|
||||
| #"r" => (true, #"\r")
|
||||
| #"\\" => (true, chr)
|
||||
| _ => (false, chr)
|
||||
|
||||
fun getCharsBetween (lowChr, highChr, acc) =
|
||||
if lowChr = highChr then
|
||||
highChr :: acc
|
||||
else
|
||||
let
|
||||
val acc = lowChr :: acc
|
||||
val lowChr = Char.succ lowChr
|
||||
in
|
||||
getCharsBetween (lowChr, highChr, acc)
|
||||
end
|
||||
|
||||
fun getCharsInBrackets (pos, str, acc) =
|
||||
if pos = String.size str then
|
||||
NONE
|
||||
else
|
||||
case String.sub (str, pos) of
|
||||
#"\\" =>
|
||||
(* escape sequences *)
|
||||
if pos + 1 = String.size str then
|
||||
NONE
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, pos + 1)
|
||||
val (isValid, chr) = isValidEscapeSequence chr
|
||||
in
|
||||
if isValid then
|
||||
(* Edge case:
|
||||
* We have to check if there is a char range like a-z,
|
||||
* and if there is,
|
||||
* we have to check if the second char in the range
|
||||
* is another escaped-character *)
|
||||
if
|
||||
pos + 2 < String.size str
|
||||
andalso String.sub (str, pos + 2) = #"-"
|
||||
andalso pos + 3 < String.size str
|
||||
then
|
||||
(* we do have a character range,
|
||||
* which may possibly be escaped *)
|
||||
case String.sub (str, pos + 3) of
|
||||
#"(" => NONE
|
||||
| #")" => NONE
|
||||
| #"[" => NONE
|
||||
| #"]" => NONE
|
||||
| #"+" => NONE
|
||||
| #"*" => NONE
|
||||
| #"|" => NONE
|
||||
| #"?" => NONE
|
||||
| #"." => NONE
|
||||
| #"-" => NONE
|
||||
| #"\\" =>
|
||||
if pos + 4 < String.size str then
|
||||
let
|
||||
val chr2 = String.sub (str, pos + 4)
|
||||
val (isValid, chr2) = isValidEscapeSequence chr2
|
||||
val acc =
|
||||
if chr < chr2 then
|
||||
getCharsBetween (chr, chr2, acc)
|
||||
else
|
||||
getCharsBetween (chr2, chr, acc)
|
||||
in
|
||||
getCharsInBrackets (pos + 5, str, acc)
|
||||
end
|
||||
else
|
||||
NONE
|
||||
| chr2 =>
|
||||
let
|
||||
val acc =
|
||||
if chr < chr2 then getCharsBetween (chr, chr2, acc)
|
||||
else getCharsBetween (chr2, chr, acc)
|
||||
in
|
||||
getCharsInBrackets (pos + 4, str, acc)
|
||||
end
|
||||
else
|
||||
(* no character range we have to check *)
|
||||
getCharsInBrackets (pos + 2, str, chr :: acc)
|
||||
else
|
||||
NONE
|
||||
end
|
||||
| #"]" =>
|
||||
let val chars = Vector.fromList acc
|
||||
in SOME (pos + 1, chars)
|
||||
end
|
||||
| chr =>
|
||||
if
|
||||
pos + 1 < String.size str andalso String.sub (str, pos + 1) = #"-"
|
||||
andalso pos + 2 < String.size str
|
||||
then
|
||||
(* handle character ranges like a-z.
|
||||
* There are edge cases regarding
|
||||
* the second character in the range.
|
||||
* We have to check that any unescaped metacharacters
|
||||
* return an invalid parse state.
|
||||
* We also have to unescape any escape sequences.
|
||||
* *)
|
||||
case String.sub (str, pos + 2) of
|
||||
#"\\" =>
|
||||
(* second char contains an escape sequence *)
|
||||
if pos + 3 < String.size str then
|
||||
let
|
||||
val chr2 = String.sub (str, pos + 3)
|
||||
val (isValid, chr2) = isValidEscapeSequence chr2
|
||||
val acc =
|
||||
if chr < chr2 then getCharsBetween (chr, chr2, acc)
|
||||
else getCharsBetween (chr2, chr, acc)
|
||||
in
|
||||
if isValid then getCharsInBrackets (pos + 4, str, acc)
|
||||
else NONE
|
||||
end
|
||||
else
|
||||
NONE
|
||||
| #"(" => NONE
|
||||
| #")" => NONE
|
||||
| #"[" => NONE
|
||||
| #"]" => NONE
|
||||
| #"+" => NONE
|
||||
| #"*" => NONE
|
||||
| #"|" => NONE
|
||||
| #"?" => NONE
|
||||
| #"." => NONE
|
||||
| #"-" => NONE
|
||||
| chr2 =>
|
||||
(* valid char range *)
|
||||
let
|
||||
val acc =
|
||||
if chr < chr2 then getCharsBetween (chr, chr2, acc)
|
||||
else getCharsBetween (chr2, chr, acc)
|
||||
in
|
||||
getCharsInBrackets (pos + 3, str, acc)
|
||||
end
|
||||
else
|
||||
getCharsInBrackets (pos + 1, str, chr :: acc)
|
||||
|
||||
fun parseCharacterClass (pos, str, stateNum) =
|
||||
case getCharsInBrackets (pos, str, []) of
|
||||
SOME (pos, chars) =>
|
||||
let
|
||||
val node = IS_ANY_CHARACTER {chars = chars, position = stateNum + 1}
|
||||
in
|
||||
SOME (pos, node, stateNum + 1)
|
||||
end
|
||||
| NONE => NONE
|
||||
|
||||
fun parseNegateCharacterClass (pos, str, stateNum) =
|
||||
case getCharsInBrackets (pos, str, []) of
|
||||
SOME (pos, chars) =>
|
||||
let
|
||||
val node =
|
||||
NOT_ANY_CHARACTER {chars = chars, position = stateNum + 1}
|
||||
in
|
||||
SOME (pos, node, stateNum + 1)
|
||||
end
|
||||
| NONE => NONE
|
||||
|
||||
fun computeAtom (pos, str, stateNum) =
|
||||
if pos = String.size str then
|
||||
NONE
|
||||
else
|
||||
case String.sub (str, pos) of
|
||||
#"(" =>
|
||||
(case getRightParenIdx (pos + 1, str) of
|
||||
SOME groupEndIdx =>
|
||||
let
|
||||
val substr = String.substring
|
||||
(str, pos + 1, groupEndIdx - pos - 1)
|
||||
in
|
||||
case parse (substr, stateNum) of
|
||||
SOME (rhs, stateNum) =>
|
||||
SOME (groupEndIdx + 1, rhs, stateNum)
|
||||
| NONE => NONE
|
||||
end
|
||||
| NONE => NONE)
|
||||
| #"\\" =>
|
||||
(* escape sequences *)
|
||||
if pos + 1 = String.size str then
|
||||
NONE
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, pos + 1)
|
||||
val (isValid, chr) = isValidEscapeSequence chr
|
||||
in
|
||||
if Fn.charIsEqual (chr, Fn.endMarker) then
|
||||
NONE
|
||||
else if isValid then
|
||||
let
|
||||
val chr = CHAR_LITERAL {char = chr, position = stateNum + 1}
|
||||
in
|
||||
SOME (pos + 2, chr, stateNum + 1)
|
||||
end
|
||||
else
|
||||
NONE
|
||||
end
|
||||
| #"." =>
|
||||
let val w = WILDCARD (stateNum + 1)
|
||||
in SOME (pos + 1, w, stateNum + 1)
|
||||
end
|
||||
| #"[" =>
|
||||
if pos + 1 = String.size str then
|
||||
NONE
|
||||
else if String.sub (str, pos + 1) = #"^" then
|
||||
parseNegateCharacterClass (pos + 2, str, stateNum)
|
||||
else
|
||||
parseCharacterClass (pos + 1, str, stateNum)
|
||||
| #")" => NONE
|
||||
| #"]" => NONE
|
||||
| #"+" => NONE
|
||||
| #"*" => NONE
|
||||
| #"|" => NONE
|
||||
| #"?" => NONE
|
||||
| #"-" => NONE
|
||||
| chr =>
|
||||
if Fn.charIsEqual (chr, Fn.endMarker) then
|
||||
NONE
|
||||
else
|
||||
let val chr = CHAR_LITERAL {char = chr, position = stateNum + 1}
|
||||
in SOME (pos + 1, chr, stateNum + 1)
|
||||
end
|
||||
|
||||
and climb (pos, str, lhs, level, stateNum) : (int * parse_tree * int) option =
|
||||
if pos = String.size str then
|
||||
SOME (pos, lhs, stateNum)
|
||||
else
|
||||
case String.sub (str, pos) of
|
||||
#"|" =>
|
||||
if level < altLevel then
|
||||
SOME (pos, lhs, stateNum)
|
||||
else if pos + 1 < String.size str then
|
||||
let
|
||||
val chr = String.sub (str, pos + 1)
|
||||
val chr = CHAR_LITERAL {char = chr, position = stateNum + 1}
|
||||
in
|
||||
case climb (pos + 2, str, chr, altLevel, stateNum + 1) of
|
||||
SOME (pos, rhs, rightStateNum) =>
|
||||
let
|
||||
val result = ALTERNATION
|
||||
{ l = lhs
|
||||
, r = rhs
|
||||
, leftMaxState = stateNum
|
||||
, rightMaxState = rightStateNum
|
||||
}
|
||||
in
|
||||
SOME (pos, result, rightStateNum)
|
||||
end
|
||||
| NONE => NONE
|
||||
end
|
||||
else
|
||||
NONE
|
||||
| #"?" =>
|
||||
if level < postfixLevel then
|
||||
SOME (pos, lhs, stateNum)
|
||||
else
|
||||
let val lhs = ZERO_OR_ONE lhs
|
||||
in climb (pos + 1, str, lhs, postfixLevel, stateNum)
|
||||
end
|
||||
| #"*" =>
|
||||
if level < postfixLevel then
|
||||
SOME (pos, lhs, stateNum)
|
||||
else
|
||||
let val lhs = ZERO_OR_MORE lhs
|
||||
in climb (pos + 1, str, lhs, postfixLevel, stateNum)
|
||||
end
|
||||
| #"+" =>
|
||||
if level < postfixLevel then
|
||||
SOME (pos, lhs, stateNum)
|
||||
else
|
||||
let val lhs = ONE_OR_MORE lhs
|
||||
in climb (pos + 1, str, lhs, postfixLevel, stateNum)
|
||||
end
|
||||
| chr =>
|
||||
if level < concatLevel then
|
||||
SOME (pos, lhs, stateNum)
|
||||
else
|
||||
case computeAtom (pos, str, stateNum) of
|
||||
SOME (nextPos, curAtom, atomStateNum) =>
|
||||
(case climb (nextPos, str, curAtom, concatLevel, atomStateNum) of
|
||||
SOME (pos, rhs, rightStateNum) =>
|
||||
let
|
||||
val result = CONCAT
|
||||
{ l = lhs
|
||||
, r = rhs
|
||||
, leftMaxState = stateNum
|
||||
, rightMaxState = rightStateNum
|
||||
}
|
||||
in
|
||||
SOME (pos, result, rightStateNum)
|
||||
end
|
||||
| NONE => NONE)
|
||||
| NONE => NONE
|
||||
|
||||
and loop (pos, str, ast, stateNum) =
|
||||
if pos = String.size str then
|
||||
SOME (ast, stateNum)
|
||||
else
|
||||
case climb (pos, str, ast, altLevel, stateNum) of
|
||||
SOME (pos, ast, stateNum) => loop (pos, str, ast, stateNum)
|
||||
| NONE => NONE
|
||||
|
||||
and parse (str, stateNum) =
|
||||
if String.size str > 0 then
|
||||
case computeAtom (0, str, stateNum) of
|
||||
SOME (nextPos, lhs, stateNum) => loop (nextPos, str, lhs, stateNum)
|
||||
| NONE => NONE
|
||||
else
|
||||
NONE
|
||||
end
|
||||
|
||||
structure ToDfa =
|
||||
struct
|
||||
type dstate_element = {marked: bool, transitions: int list}
|
||||
type dstate_vec = dstate_element vector
|
||||
|
||||
fun chrExistsInVec (idx, vec, curChr) =
|
||||
if idx = Vector.length vec then
|
||||
false
|
||||
else
|
||||
let
|
||||
val idxChr = Vector.sub (vec, idx)
|
||||
in
|
||||
Fn.charIsEqual (idxChr, curChr)
|
||||
orelse chrExistsInVec (idx + 1, vec, curChr)
|
||||
end
|
||||
|
||||
fun addKeysToFollowSet (lst, addSet, followSet) =
|
||||
case lst of
|
||||
hd :: tl =>
|
||||
let
|
||||
val currentFollows = Set.getOrDefault (hd, followSet, [])
|
||||
val updatedFollows = Set.addFromList (currentFollows, addSet)
|
||||
val updatedFollows: int list = Set.keysToList updatedFollows
|
||||
val followSet = Set.insertOrReplace (hd, updatedFollows, followSet)
|
||||
in
|
||||
addKeysToFollowSet (tl, addSet, followSet)
|
||||
end
|
||||
| [] => followSet
|
||||
|
||||
fun addToFollowSet (tree, followSet) =
|
||||
case tree of
|
||||
WILDCARD _ => followSet
|
||||
| CHAR_LITERAL {char, position} =>
|
||||
(* we add the endMarker and its position to the followSet *)
|
||||
if char = Fn.endMarker then
|
||||
Set.insertOrReplace (position, [Char.ord Fn.endMarker], followSet)
|
||||
else
|
||||
followSet
|
||||
| IS_ANY_CHARACTER _ => followSet
|
||||
| NOT_ANY_CHARACTER _ => followSet
|
||||
| CONCAT {l, r, ...} =>
|
||||
let
|
||||
val followSet = addToFollowSet (l, followSet)
|
||||
val followSet = addToFollowSet (r, followSet)
|
||||
|
||||
val lpOfLeft = lastpos (l, [])
|
||||
val fpOfRight = firstpos (r, [])
|
||||
val fpOfRight = Set.addFromList (fpOfRight, Set.LEAF)
|
||||
in
|
||||
addKeysToFollowSet (lpOfLeft, fpOfRight, followSet)
|
||||
end
|
||||
| ALTERNATION {l, r, ...} =>
|
||||
let val followSet = addToFollowSet (l, followSet)
|
||||
in addToFollowSet (r, followSet)
|
||||
end
|
||||
| ZERO_OR_MORE child =>
|
||||
let
|
||||
val followSet = addToFollowSet (child, followSet)
|
||||
val fp = firstpos (child, [])
|
||||
val fp = Set.addFromList (fp, Set.LEAF)
|
||||
val lp = lastpos (child, [])
|
||||
in
|
||||
addKeysToFollowSet (lp, fp, followSet)
|
||||
end
|
||||
| ONE_OR_MORE child =>
|
||||
let
|
||||
val followSet = addToFollowSet (child, followSet)
|
||||
val lp = lastpos (child, [])
|
||||
val fp = firstpos (child, [])
|
||||
val fp = Set.addFromList (fp, Set.LEAF)
|
||||
in
|
||||
addKeysToFollowSet (lp, fp, followSet)
|
||||
end
|
||||
| ZERO_OR_ONE child => addToFollowSet (child, followSet)
|
||||
| GROUP child => addToFollowSet (child, followSet)
|
||||
|
||||
fun appendIfNew (pos, dstates, newStates) =
|
||||
if pos = Vector.length dstates then
|
||||
let
|
||||
val record = {transitions = newStates, marked = false}
|
||||
val dstates = Vector.concat [dstates, Vector.fromList [record]]
|
||||
in
|
||||
(pos, dstates)
|
||||
end
|
||||
else
|
||||
let
|
||||
val {transitions: int list, marked = _} = Vector.sub (dstates, pos)
|
||||
in
|
||||
if transitions = newStates then (pos, dstates)
|
||||
else appendIfNew (pos + 1, dstates, newStates)
|
||||
end
|
||||
|
||||
fun getUnmarkedTransitionsIfExists (pos, dstates) =
|
||||
if pos = Vector.length dstates then
|
||||
NONE
|
||||
else
|
||||
let
|
||||
val record: dstate_element = Vector.sub (dstates, pos)
|
||||
in
|
||||
if #marked record then
|
||||
getUnmarkedTransitionsIfExists (pos + 1, dstates)
|
||||
else
|
||||
SOME (pos, #transitions record)
|
||||
end
|
||||
|
||||
fun isCharMatch (regex, pos, curChr) =
|
||||
case regex of
|
||||
CHAR_LITERAL {char, ...} => Fn.charIsEqual (char, curChr)
|
||||
| WILDCARD _ => true
|
||||
| IS_ANY_CHARACTER {chars, ...} => chrExistsInVec (0, chars, curChr)
|
||||
| NOT_ANY_CHARACTER {chars, ...} =>
|
||||
let val charIsValid = chrExistsInVec (0, chars, curChr)
|
||||
in not charIsValid
|
||||
end
|
||||
| ALTERNATION {l, r, leftMaxState, ...} =>
|
||||
if pos > leftMaxState then isCharMatch (r, pos, curChr)
|
||||
else isCharMatch (l, pos, curChr)
|
||||
| CONCAT {l, r, leftMaxState, ...} =>
|
||||
if pos > leftMaxState then isCharMatch (r, pos, curChr)
|
||||
else isCharMatch (l, pos, curChr)
|
||||
| ZERO_OR_ONE child => isCharMatch (child, pos, curChr)
|
||||
| ZERO_OR_MORE child => isCharMatch (child, pos, curChr)
|
||||
| ONE_OR_MORE child => isCharMatch (child, pos, curChr)
|
||||
| GROUP child => isCharMatch (child, pos, curChr)
|
||||
|
||||
fun positionsThatCorrespondToChar
|
||||
(char, curStates, regex, acc, followSet, hasAnyMatch) =
|
||||
case curStates of
|
||||
[] => List.concat (Set.valuesToList acc)
|
||||
| pos :: tl =>
|
||||
if isCharMatch (regex, pos, Char.chr char) then
|
||||
let
|
||||
(* get union of new and previous follows *)
|
||||
val prevFollows = Set.getOrDefault (char, acc, [])
|
||||
val newFollows = Set.getOrDefault (pos, followSet, [])
|
||||
|
||||
val tempSet = Set.addFromList (prevFollows, Set.LEAF)
|
||||
val tempSet = Set.addFromList (newFollows, tempSet)
|
||||
val allFollowList = Set.keysToList tempSet
|
||||
|
||||
(* store union of new and previous follows so far *)
|
||||
val acc = Set.insertOrReplace (char, allFollowList, acc)
|
||||
in
|
||||
positionsThatCorrespondToChar
|
||||
(char, tl, regex, acc, followSet, true)
|
||||
end
|
||||
else
|
||||
positionsThatCorrespondToChar
|
||||
(char, tl, regex, acc, followSet, hasAnyMatch)
|
||||
|
||||
structure Dtran =
|
||||
struct
|
||||
(* vector, with idx corresponding to state in dstate,
|
||||
* an int key which corresponds to char's ascii code,
|
||||
* and an int value corresponding to state we will transition to *)
|
||||
type t = int Set.set vector
|
||||
|
||||
fun insert (dStateIdx, char, toStateIdx, dtran: t) =
|
||||
if dStateIdx = Vector.length dtran then
|
||||
let
|
||||
val el = Set.insertOrReplace (char, toStateIdx, Set.LEAF)
|
||||
val el = Vector.fromList [el]
|
||||
in
|
||||
Vector.concat [dtran, el]
|
||||
end
|
||||
else if dStateIdx < Vector.length dtran then
|
||||
let
|
||||
val el = Vector.sub (dtran, dStateIdx)
|
||||
val el = Set.insertOrReplace (char, toStateIdx, el)
|
||||
in
|
||||
Vector.update (dtran, dStateIdx, el)
|
||||
end
|
||||
else
|
||||
let
|
||||
val appendLength = dStateIdx - Vector.length dtran
|
||||
val appendVecs = Vector.tabulate (appendLength, fn _ => Set.LEAF)
|
||||
val dtran = Vector.concat [dtran, appendVecs]
|
||||
in
|
||||
insert (dStateIdx, char, toStateIdx, dtran)
|
||||
end
|
||||
end
|
||||
|
||||
fun convertChar
|
||||
( char
|
||||
, regex
|
||||
, dstates
|
||||
, dtran: Dtran.t
|
||||
, unmarkedState
|
||||
, unmarkedIdx
|
||||
, followSet
|
||||
, prevDstateLength
|
||||
) =
|
||||
if char < 0 then
|
||||
(dstates, dtran)
|
||||
else
|
||||
let
|
||||
val u = positionsThatCorrespondToChar
|
||||
(char, unmarkedState, regex, Set.LEAF, followSet, false)
|
||||
in
|
||||
case u of
|
||||
[] =>
|
||||
convertChar
|
||||
( char - 1
|
||||
, regex
|
||||
, dstates
|
||||
, dtran
|
||||
, unmarkedState
|
||||
, unmarkedIdx
|
||||
, followSet
|
||||
, prevDstateLength
|
||||
)
|
||||
| _ =>
|
||||
let
|
||||
(* dtran is idx -> char -> state_list map *)
|
||||
val (uIdx, dstates) = appendIfNew (0, dstates, u)
|
||||
val dtran = Dtran.insert (unmarkedIdx, char, uIdx, dtran)
|
||||
in
|
||||
convertChar
|
||||
( char - 1
|
||||
, regex
|
||||
, dstates
|
||||
, dtran
|
||||
, unmarkedState
|
||||
, unmarkedIdx
|
||||
, followSet
|
||||
, prevDstateLength
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
fun convertLoop (regex, dstates, dtran, followSet) =
|
||||
case getUnmarkedTransitionsIfExists (0, dstates) of
|
||||
SOME (unmarkedIdx, unamarkedTransition) =>
|
||||
let
|
||||
(* mark transition *)
|
||||
val dstates =
|
||||
let
|
||||
val newMark = {marked = true, transitions = unamarkedTransition}
|
||||
in
|
||||
Vector.update (dstates, unmarkedIdx, newMark)
|
||||
end
|
||||
|
||||
val (dstates, dtran) = convertChar
|
||||
( 255
|
||||
, regex
|
||||
, dstates
|
||||
, dtran
|
||||
, unamarkedTransition
|
||||
, unmarkedIdx
|
||||
, followSet
|
||||
, Vector.length dstates
|
||||
)
|
||||
in
|
||||
convertLoop (regex, dstates, dtran, followSet)
|
||||
end
|
||||
| NONE =>
|
||||
Vector.map
|
||||
(fn set =>
|
||||
Vector.tabulate (256, fn i => Set.getOrDefault (i, set, ~1)))
|
||||
dtran
|
||||
|
||||
fun convert regex =
|
||||
let
|
||||
val followSet = addToFollowSet (regex, Set.LEAF)
|
||||
|
||||
(* get firstpos, sorted *)
|
||||
val first = firstpos (regex, [])
|
||||
val first = Set.addFromList (first, Set.LEAF)
|
||||
val first = Set.keysToList first
|
||||
|
||||
val dstates = Vector.fromList [{transitions = first, marked = false}]
|
||||
in
|
||||
convertLoop (regex, dstates, Vector.fromList [Set.LEAF], followSet)
|
||||
end
|
||||
end
|
||||
|
||||
fun fromString str =
|
||||
case ParseDfa.parse (str, 0) of
|
||||
SOME (ast, numStates) =>
|
||||
let
|
||||
val endMarker =
|
||||
CHAR_LITERAL {char = Fn.endMarker, position = numStates + 1}
|
||||
val ast = CONCAT
|
||||
{ l = ast
|
||||
, leftMaxState = numStates
|
||||
, r = endMarker
|
||||
, rightMaxState = numStates + 1
|
||||
}
|
||||
in
|
||||
ToDfa.convert ast
|
||||
end
|
||||
| NONE => Vector.fromList []
|
||||
|
||||
type dfa = int vector vector
|
||||
type dfa_state = int
|
||||
|
||||
fun nextState (dfa: dfa, curState: dfa_state, chr) =
|
||||
let val curTable = Vector.sub (dfa, curState)
|
||||
in Vector.sub (curTable, Char.ord chr)
|
||||
end
|
||||
|
||||
fun isFinal (dfa: dfa, curState: dfa_state) =
|
||||
curState <> ~1
|
||||
andalso
|
||||
let
|
||||
val curTable = Vector.sub (dfa, curState)
|
||||
val endMarkerCode = Char.ord Fn.endMarker
|
||||
in
|
||||
Vector.sub (curTable, endMarkerCode) <> ~1
|
||||
end
|
||||
|
||||
fun isDead (curState: dfa_state) = curState = ~1
|
||||
|
||||
fun helpMatchString (strPos, str, dfa, curState, startPos, prevFinalPos, acc) =
|
||||
if strPos = String.size str then
|
||||
let
|
||||
val acc =
|
||||
if prevFinalPos = ~1 then acc else (startPos, prevFinalPos) :: acc
|
||||
in
|
||||
List.rev acc
|
||||
end
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, strPos)
|
||||
val newState = nextState (dfa, curState, chr)
|
||||
val prevFinalPos =
|
||||
if isFinal (dfa, newState) then strPos else prevFinalPos
|
||||
in
|
||||
if isDead newState then
|
||||
if prevFinalPos = ~1 then
|
||||
(* restart from startPos *)
|
||||
helpMatchString (startPos + 1, str, dfa, 0, startPos + 1, ~1, acc)
|
||||
else
|
||||
let
|
||||
val acc = (startPos, prevFinalPos) :: acc
|
||||
in
|
||||
helpMatchString
|
||||
(prevFinalPos + 1, str, dfa, 0, prevFinalPos + 1, ~1, acc)
|
||||
end
|
||||
else
|
||||
helpMatchString
|
||||
(strPos + 1, str, dfa, newState, startPos, prevFinalPos, acc)
|
||||
end
|
||||
|
||||
fun matchString (dfa, string) =
|
||||
if Vector.length dfa = 0 then []
|
||||
else helpMatchString (0, string, dfa, 0, 0, ~1, [])
|
||||
end
|
||||
|
||||
structure CaseInsensitiveDfa =
|
||||
MakeDfaGen
|
||||
(struct
|
||||
val endMarker = #"\^@"
|
||||
fun charIsEqual (a: char, b: char) = Char.toLower a = Char.toLower b
|
||||
end)
|
||||
|
||||
structure CaseSensitiveDfa =
|
||||
MakeDfaGen
|
||||
(struct
|
||||
val endMarker = #"\^@"
|
||||
fun charIsEqual (a: char, b: char) = a = b
|
||||
end)
|
||||
254
shf/fcore/search-list/search-list.sml
Normal file
254
shf/fcore/search-list/search-list.sml
Normal file
@@ -0,0 +1,254 @@
|
||||
structure SearchList =
|
||||
struct
|
||||
structure Dfa = CaseInsensitiveDfa
|
||||
|
||||
fun buildLoop (idx, buffer, dfa, acc, curState, startPos, prevFinalPos) =
|
||||
let
|
||||
val buffer = LineGap.goToIdx (idx, buffer)
|
||||
in
|
||||
if idx = #textLength buffer then
|
||||
let
|
||||
val acc =
|
||||
if prevFinalPos < 0 then acc
|
||||
else PersistentVector.append (startPos, prevFinalPos, acc)
|
||||
in
|
||||
(buffer, acc)
|
||||
end
|
||||
else
|
||||
let
|
||||
val chr = LineGap.sub (idx, buffer)
|
||||
val newState = Dfa.nextState (dfa, curState, chr)
|
||||
val prevFinalPos =
|
||||
if Dfa.isFinal (dfa, newState) then idx else prevFinalPos
|
||||
in
|
||||
if Dfa.isDead newState then
|
||||
if prevFinalPos = ~1 then
|
||||
(* no match found: restart search from `startPos + 1` *)
|
||||
buildLoop (startPos + 1, buffer, dfa, acc, 0, startPos + 1, ~1)
|
||||
else
|
||||
(* match found: append and continue *)
|
||||
let
|
||||
val acc = PersistentVector.append (startPos, prevFinalPos, acc)
|
||||
|
||||
(* we start 1 idx after the final position we found *)
|
||||
val newStart = prevFinalPos + 1
|
||||
in
|
||||
buildLoop (newStart, buffer, dfa, acc, 0, newStart, ~1)
|
||||
end
|
||||
else
|
||||
buildLoop
|
||||
(idx + 1, buffer, dfa, acc, newState, startPos, prevFinalPos)
|
||||
end
|
||||
end
|
||||
|
||||
fun build (buffer, dfa) =
|
||||
if Vector.length dfa > 0 then
|
||||
let val buffer = LineGap.goToStart buffer
|
||||
in buildLoop (0, buffer, dfa, PersistentVector.empty, 0, 0, ~1)
|
||||
end
|
||||
else
|
||||
(buffer, PersistentVector.empty)
|
||||
|
||||
fun rangeLoop
|
||||
( dfa
|
||||
, bufferPos
|
||||
, buffer
|
||||
, finishIdx
|
||||
, searchList
|
||||
, curState
|
||||
, startPos
|
||||
, prevFinalPos
|
||||
) =
|
||||
if bufferPos = #textLength buffer orelse bufferPos > finishIdx then
|
||||
let
|
||||
val searchList =
|
||||
if prevFinalPos = ~1 then searchList
|
||||
else PersistentVector.append (startPos, prevFinalPos, searchList)
|
||||
in
|
||||
(buffer, searchList)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (bufferPos, buffer)
|
||||
val chr = LineGap.sub (bufferPos, buffer)
|
||||
val newState = Dfa.nextState (dfa, curState, chr)
|
||||
val prevFinalPos =
|
||||
if Dfa.isFinal (dfa, newState) then bufferPos else prevFinalPos
|
||||
in
|
||||
if Dfa.isDead newState then
|
||||
if prevFinalPos = ~1 then
|
||||
(* no match found: restart search from `startPos + 1` *)
|
||||
rangeLoop
|
||||
( dfa
|
||||
, startPos + 1
|
||||
, buffer
|
||||
, finishIdx
|
||||
, searchList
|
||||
, 0
|
||||
, startPos + 1
|
||||
, ~1
|
||||
)
|
||||
else
|
||||
(* match found: append and continue *)
|
||||
let
|
||||
val searchList =
|
||||
PersistentVector.append (startPos, prevFinalPos, searchList)
|
||||
|
||||
(* we start 1 idx after the final position we found *)
|
||||
val newStart = prevFinalPos + 1
|
||||
in
|
||||
rangeLoop
|
||||
(dfa, newStart, buffer, finishIdx, searchList, 0, newStart, ~1)
|
||||
end
|
||||
else
|
||||
(* continue searching for match *)
|
||||
rangeLoop
|
||||
( dfa
|
||||
, bufferPos + 1
|
||||
, buffer
|
||||
, finishIdx
|
||||
, searchList
|
||||
, newState
|
||||
, startPos
|
||||
, prevFinalPos
|
||||
)
|
||||
end
|
||||
|
||||
fun buildRange (buffer, finishIdx, dfa) =
|
||||
if Vector.length dfa > 0 then
|
||||
rangeLoop
|
||||
( dfa
|
||||
, #idx buffer
|
||||
, buffer
|
||||
, finishIdx
|
||||
, PersistentVector.empty
|
||||
, 0
|
||||
, #idx buffer
|
||||
, ~1
|
||||
)
|
||||
else
|
||||
(buffer, PersistentVector.empty)
|
||||
|
||||
fun insertUntilMatch
|
||||
(idx, buffer, searchList, dfa, curState, startPos, prevFinalPos) =
|
||||
if idx = #textLength buffer then
|
||||
if prevFinalPos < 0 then
|
||||
(buffer, searchList)
|
||||
else if PersistentVector.isInRange (prevFinalPos, searchList) then
|
||||
(buffer, searchList)
|
||||
else
|
||||
let
|
||||
val searchList =
|
||||
PersistentVector.insertMatchKeepingAbsoluteInddices
|
||||
(startPos, prevFinalPos, searchList)
|
||||
in
|
||||
(buffer, searchList)
|
||||
end
|
||||
else if Dfa.isDead curState then
|
||||
if prevFinalPos = ~1 then
|
||||
(* no match found: restart search from `startPos + 1` *)
|
||||
insertUntilMatch
|
||||
(startPos + 1, buffer, searchList, dfa, 0, startPos + 1, ~1)
|
||||
else if PersistentVector.isInRange (prevFinalPos, searchList) then
|
||||
(buffer, searchList)
|
||||
else
|
||||
(* new match. Insert and continue *)
|
||||
let
|
||||
val searchList =
|
||||
PersistentVector.insertMatchKeepingAbsoluteInddices
|
||||
(startPos, prevFinalPos, searchList)
|
||||
val newStart = prevFinalPos + 1
|
||||
in
|
||||
insertUntilMatch (newStart, buffer, searchList, dfa, 0, newStart, ~1)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (idx, buffer)
|
||||
val chr = LineGap.sub (idx, buffer)
|
||||
val newState = Dfa.nextState (dfa, curState, chr)
|
||||
val prevFinalPos =
|
||||
if Dfa.isFinal (dfa, newState) then idx else prevFinalPos
|
||||
in
|
||||
(* continue *)
|
||||
insertUntilMatch
|
||||
(idx + 1, buffer, searchList, dfa, newState, startPos, prevFinalPos)
|
||||
end
|
||||
|
||||
fun tryExtendingPrevMatch
|
||||
(idx, buffer, searchList, dfa, finalPos, curState, start) =
|
||||
if idx = #textLength buffer then
|
||||
let
|
||||
val searchList =
|
||||
PersistentVector.extendExistingMatch (start, finalPos, searchList)
|
||||
in
|
||||
(buffer, searchList)
|
||||
end
|
||||
else if Dfa.isDead curState then
|
||||
let
|
||||
val searchList =
|
||||
PersistentVector.extendExistingMatch (start, finalPos, searchList)
|
||||
in
|
||||
insertUntilMatch
|
||||
(finalPos + 1, buffer, searchList, dfa, 0, finalPos + 1, ~1)
|
||||
end
|
||||
else
|
||||
let
|
||||
val buffer = LineGap.goToIdx (idx, buffer)
|
||||
val chr = LineGap.sub (idx, buffer)
|
||||
val newState = Dfa.nextState (dfa, curState, chr)
|
||||
val finalPos = if Dfa.isFinal (dfa, newState) then idx else finalPos
|
||||
in
|
||||
(* continue *)
|
||||
tryExtendingPrevMatch
|
||||
(idx + 1, buffer, searchList, dfa, finalPos, newState, start)
|
||||
end
|
||||
|
||||
fun deleteBufferAndSearchList (start, length, buffer, searchList, dfa) =
|
||||
let
|
||||
val buffer = LineGap.delete (start, length, buffer)
|
||||
val searchList = PersistentVector.delete (start, length, searchList)
|
||||
val oldStart = PersistentVector.prevMatch (start, searchList, 1)
|
||||
in
|
||||
if Vector.length dfa = 0 then
|
||||
(buffer, searchList)
|
||||
else if oldStart >= start orelse oldStart = ~1 then
|
||||
(* no previous match, so try searching for a match from start of buffer *)
|
||||
insertUntilMatch (0, buffer, searchList, dfa, 0, 0, ~1)
|
||||
else
|
||||
tryExtendingPrevMatch
|
||||
(oldStart, buffer, searchList, dfa, ~1, 0, oldStart)
|
||||
end
|
||||
|
||||
(* inserts into buffer and searchList both *)
|
||||
fun insert (insIdx, insString, buffer, searchList, dfa) =
|
||||
let
|
||||
val buffer = LineGap.insert (insIdx, insString, buffer)
|
||||
|
||||
(* incremennt existing elements in the searchList after the insIdx
|
||||
* by the length of the string that was just inserted *)
|
||||
val searchList =
|
||||
let
|
||||
val searchListLeft = PersistentVector.splitLeft (insIdx, searchList)
|
||||
|
||||
val insLength = String.size insString
|
||||
val searchListRight =
|
||||
PersistentVector.splitRight (insIdx + insLength, searchList)
|
||||
val searchListRight = PersistentVector.empty
|
||||
in
|
||||
if PersistentVector.isEmpty searchListLeft then searchListRight
|
||||
else if PersistentVector.isEmpty searchListRight then searchListLeft
|
||||
else PersistentVector.merge (searchListLeft, searchListRight)
|
||||
end
|
||||
|
||||
val oldStart = PersistentVector.prevMatch (insIdx, searchList, 1)
|
||||
in
|
||||
if Vector.length dfa = 0 then
|
||||
(buffer, searchList)
|
||||
else if oldStart >= insIdx orelse oldStart = ~1 then
|
||||
(* no previous match, so try searching for a match from start of buffer *)
|
||||
insertUntilMatch (0, buffer, searchList, dfa, 0, 0, ~1)
|
||||
else
|
||||
tryExtendingPrevMatch
|
||||
(oldStart, buffer, searchList, dfa, ~1, 0, oldStart)
|
||||
end
|
||||
end
|
||||
142
shf/fcore/text-builder/normal-mode-text-builder.sml
Normal file
142
shf/fcore/text-builder/normal-mode-text-builder.sml
Normal file
@@ -0,0 +1,142 @@
|
||||
structure NormalModeTextBuilder =
|
||||
struct
|
||||
structure Utils = TextBuilderUtils
|
||||
|
||||
(* Prerequisite to all functions in this structure:
|
||||
* - Move buffer to startLine before calling any function. *)
|
||||
|
||||
fun startBuild
|
||||
( startLine
|
||||
, cursorPos
|
||||
, buffer: LineGap.t
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, acc
|
||||
) =
|
||||
let
|
||||
val
|
||||
{ rightStrings
|
||||
, rightLines
|
||||
, line = curLine
|
||||
, idx = curIdx
|
||||
, textLength
|
||||
, ...
|
||||
} = buffer
|
||||
|
||||
val env = Utils.initEnv
|
||||
( 0
|
||||
, 0
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, startLine
|
||||
)
|
||||
val {startX, startY, ...} = env
|
||||
in
|
||||
if textLength = 1 then
|
||||
(* empty string, so there is nothing we can draw
|
||||
* except a cursor at the line start.
|
||||
* An empty string is usually thought of to have a length of 0
|
||||
* and this is true, but we always have a \n at the end of the buffer
|
||||
* to respect Unix-style file endings, which we always uphold.
|
||||
* So, for us, an empty string has a length of 1. *)
|
||||
[Utils.makeCursor (startX, startY, env)]
|
||||
else
|
||||
case (rightStrings, rightLines) of
|
||||
(shd :: stl, lhd :: ltl) =>
|
||||
let
|
||||
(* get relative index of line to start building from *)
|
||||
val strPos =
|
||||
Utils.getRelativeLineStartFromRightHead
|
||||
(startLine, curLine, lhd)
|
||||
(* get absolute idx of line *)
|
||||
val absIdx = curIdx + strPos
|
||||
in
|
||||
if PersistentVector.isEmpty searchList then
|
||||
TextBuilderWithCursor.build
|
||||
( strPos
|
||||
, shd
|
||||
, stl
|
||||
, lhd
|
||||
, ltl
|
||||
, startX
|
||||
, startY
|
||||
, 0
|
||||
, startLine
|
||||
, absIdx
|
||||
, cursorPos
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
else
|
||||
TextBuilderWithHighlight.build
|
||||
( strPos
|
||||
, shd
|
||||
, stl
|
||||
, lhd
|
||||
, ltl
|
||||
, startX
|
||||
, startY
|
||||
, 0
|
||||
, startLine
|
||||
, absIdx
|
||||
, cursorPos
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
| (_, _) => acc
|
||||
end
|
||||
|
||||
fun buildWithExisting
|
||||
( startLine
|
||||
, cursorPos
|
||||
, buffer: LineGap.t
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, acc
|
||||
) =
|
||||
startBuild
|
||||
( startLine
|
||||
, cursorPos
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, Real32.fromInt windowWidth
|
||||
, Real32.fromInt windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, []
|
||||
)
|
||||
|
||||
fun build
|
||||
( startLine
|
||||
, cursorPos
|
||||
, buffer: LineGap.t
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
) =
|
||||
startBuild
|
||||
( startLine
|
||||
, cursorPos
|
||||
, buffer
|
||||
, windowWidth
|
||||
, windowHeight
|
||||
, Real32.fromInt windowWidth
|
||||
, Real32.fromInt windowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, []
|
||||
)
|
||||
end
|
||||
131
shf/fcore/text-builder/search-bar.sml
Normal file
131
shf/fcore/text-builder/search-bar.sml
Normal file
@@ -0,0 +1,131 @@
|
||||
structure SearchBar =
|
||||
struct
|
||||
structure TC = TextConstants
|
||||
structure Utils = TextBuilderUtils
|
||||
|
||||
fun loop
|
||||
(pos, str, posX, posY, endX, acc, floatWindowWidth, floatWindowHeight) =
|
||||
if pos = String.size str then
|
||||
acc
|
||||
else if posX >= endX then
|
||||
acc
|
||||
else
|
||||
let
|
||||
val chr = String.sub (str, pos)
|
||||
val r: Real32.real = 0.01
|
||||
val g: Real32.real = 0.01
|
||||
val b: Real32.real = 0.01
|
||||
val fPosX = Real32.fromInt posX
|
||||
val fPosY = Real32.fromInt posY
|
||||
val z: Real32.real = 0.1
|
||||
|
||||
val chr = CozetteAscii.make
|
||||
( chr
|
||||
, fPosX
|
||||
, fPosY
|
||||
, z
|
||||
, TC.scale
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, r
|
||||
, g
|
||||
, b
|
||||
)
|
||||
|
||||
val acc = chr :: acc
|
||||
val nextPosX = posX + TC.xSpace
|
||||
in
|
||||
loop
|
||||
( pos + 1
|
||||
, str
|
||||
, nextPosX
|
||||
, posY
|
||||
, endX
|
||||
, acc
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
)
|
||||
end
|
||||
|
||||
(* builds a single text line from a string.
|
||||
* Used for getting Real32.real vector representing search input.
|
||||
* Todo: add scrolling, so that text scrolls horizontally when greater than width. *)
|
||||
fun build
|
||||
( str
|
||||
, startX
|
||||
, startY
|
||||
, endX
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, searchCursorIdx
|
||||
, searchScrollColumn
|
||||
, caseSensitive
|
||||
) =
|
||||
let
|
||||
val r: Real32.real = 0.1
|
||||
val g: Real32.real = 0.1
|
||||
val b: Real32.real = 0.1
|
||||
val z: Real32.real = 0.1
|
||||
|
||||
val width = endX - startX
|
||||
val (startX, endX) =
|
||||
if TC.textLineWidth > width then
|
||||
(startX, endX)
|
||||
else
|
||||
let
|
||||
val startX = (width - TC.textLineWidth) div 2
|
||||
val endX = startX + TC.textLineWidth
|
||||
in
|
||||
(startX, endX)
|
||||
end
|
||||
|
||||
val fPosX = Real32.fromInt startX
|
||||
val fPosY = Real32.fromInt startY
|
||||
|
||||
val searchSymbol = CozetteAscii.make
|
||||
( if caseSensitive then #"?" else #"/"
|
||||
, fPosX
|
||||
, fPosY
|
||||
, z
|
||||
, TC.scale
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, r
|
||||
, g
|
||||
, b
|
||||
)
|
||||
|
||||
val cursor =
|
||||
let
|
||||
val xpos = (searchCursorIdx + 1) - searchScrollColumn
|
||||
val xpos = TextConstants.xSpace * xpos + startX
|
||||
val xpos = Int.min (endX, xpos)
|
||||
val x = Real32.fromInt xpos
|
||||
in
|
||||
PipeCursor.lerp
|
||||
( x
|
||||
, fPosY
|
||||
, 0.01
|
||||
, TC.scale
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, r
|
||||
, g
|
||||
, b
|
||||
)
|
||||
end
|
||||
|
||||
val posX = startX + TC.xSpace
|
||||
in
|
||||
loop
|
||||
( searchScrollColumn
|
||||
, str
|
||||
, posX
|
||||
, startY
|
||||
, endX
|
||||
, [cursor, searchSymbol]
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
)
|
||||
end
|
||||
end
|
||||
250
shf/fcore/text-builder/text-builder-utils.sml
Normal file
250
shf/fcore/text-builder/text-builder-utils.sml
Normal file
@@ -0,0 +1,250 @@
|
||||
structure TextBuilderUtils =
|
||||
struct
|
||||
structure TC = TextConstants
|
||||
|
||||
type env_data =
|
||||
{ charR: Real32.real
|
||||
, charG: Real32.real
|
||||
, charB: Real32.real
|
||||
|
||||
, cursorR: Real32.real
|
||||
, cursorG: Real32.real
|
||||
, cursorB: Real32.real
|
||||
|
||||
, searchHighlightR: Real32.real
|
||||
, searchHighlightG: Real32.real
|
||||
, searchHighlightB: Real32.real
|
||||
|
||||
(* different colours for char when cursor is on char *)
|
||||
, cursorHighlightedCharR: Real32.real
|
||||
, cursorHighlightedCharG: Real32.real
|
||||
, cursorHighlightedCharB: Real32.real
|
||||
|
||||
, searchHighlightedCharR: Real32.real
|
||||
, searchHighlightedCharG: Real32.real
|
||||
, searchHighlightedCharB: Real32.real
|
||||
|
||||
, charZ: Real32.real
|
||||
, cursorZ: Real32.real
|
||||
, searchHighlightZ: Real32.real
|
||||
|
||||
, startX: int
|
||||
, startY: int
|
||||
|
||||
, scrollColumnStart: int
|
||||
, scrollColumnEnd: int
|
||||
, lastLineNumber: int
|
||||
|
||||
(* fw/fh = float window width and float window height *)
|
||||
, fw: Real32.real
|
||||
, fh: Real32.real
|
||||
|
||||
, searchList: PersistentVector.t
|
||||
}
|
||||
|
||||
fun initEnv
|
||||
( startX
|
||||
, startY
|
||||
, endX
|
||||
, endY
|
||||
, floatWindowWidth
|
||||
, floatWindowHeight
|
||||
, searchList
|
||||
, visualScrollColumn
|
||||
, startLine
|
||||
) : env_data =
|
||||
let
|
||||
val width = endX - startX
|
||||
val lastLineNumber =
|
||||
let
|
||||
val height = endY - startY
|
||||
val howManyLines = height div TC.ySpace
|
||||
in
|
||||
startLine + howManyLines
|
||||
end
|
||||
in
|
||||
if TC.textLineWidth > width then
|
||||
{ charR = 0.0
|
||||
, charG = 0.0
|
||||
, charB = 0.0
|
||||
|
||||
, searchHighlightR = 0.41
|
||||
, searchHighlightG = 0.05
|
||||
, searchHighlightB = 0.67
|
||||
|
||||
, cursorR = 0.0
|
||||
, cursorG = 0.0
|
||||
, cursorB = 0.0
|
||||
|
||||
, searchHighlightedCharR = 0.89
|
||||
, searchHighlightedCharG = 0.89
|
||||
, searchHighlightedCharB = 0.89
|
||||
|
||||
, cursorHighlightedCharR = 0.89
|
||||
, cursorHighlightedCharG = 0.89
|
||||
, cursorHighlightedCharB = 0.89
|
||||
|
||||
, charZ = 0.01
|
||||
, cursorZ = 0.03
|
||||
, searchHighlightZ = 0.05
|
||||
|
||||
, startX = startX
|
||||
, startY = startX
|
||||
|
||||
, scrollColumnStart = visualScrollColumn
|
||||
, scrollColumnEnd = width div TC.xSpace + visualScrollColumn
|
||||
, lastLineNumber = lastLineNumber
|
||||
|
||||
, fw = floatWindowWidth
|
||||
, fh = floatWindowHeight
|
||||
|
||||
, searchList = searchList
|
||||
}
|
||||
else
|
||||
let
|
||||
val startX = (width - TC.textLineWidth) div 2
|
||||
in
|
||||
{ charR = 0.0
|
||||
, charG = 0.0
|
||||
, charB = 0.0
|
||||
|
||||
, searchHighlightR = 0.41
|
||||
, searchHighlightG = 0.05
|
||||
, searchHighlightB = 0.67
|
||||
|
||||
, cursorR = 0.0
|
||||
, cursorG = 0.0
|
||||
, cursorB = 0.0
|
||||
|
||||
, searchHighlightedCharR = 0.89
|
||||
, searchHighlightedCharG = 0.89
|
||||
, searchHighlightedCharB = 0.89
|
||||
|
||||
, cursorHighlightedCharR = 0.89
|
||||
, cursorHighlightedCharG = 0.89
|
||||
, cursorHighlightedCharB = 0.89
|
||||
|
||||
, charZ = 0.01
|
||||
, cursorZ = 0.03
|
||||
, searchHighlightZ = 0.05
|
||||
|
||||
, startX = startX
|
||||
, startY = startY
|
||||
|
||||
, scrollColumnStart = visualScrollColumn
|
||||
, scrollColumnEnd = visualScrollColumn + TC.textLineCount
|
||||
, lastLineNumber = lastLineNumber
|
||||
|
||||
, fw = floatWindowWidth
|
||||
, fh = floatWindowHeight
|
||||
|
||||
, searchList = searchList
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
(* different functions to make vectors of different things we want to draw. *)
|
||||
fun makeCursor (posX, posY, env: env_data) =
|
||||
Rect.lerp
|
||||
( Real32.fromInt (posX - 2)
|
||||
, Real32.fromInt posY
|
||||
, #cursorZ env
|
||||
, TC.scale
|
||||
, #fw env
|
||||
, #fh env
|
||||
, #cursorR env
|
||||
, #cursorG env
|
||||
, #cursorB env
|
||||
)
|
||||
|
||||
fun makeSearchHighlight (posX, posY, env: env_data) =
|
||||
Rect.lerp
|
||||
( Real32.fromInt (posX - 2)
|
||||
, Real32.fromInt posY
|
||||
, #searchHighlightZ env
|
||||
, TC.scale
|
||||
, #fw env
|
||||
, #fh env
|
||||
, #searchHighlightR env
|
||||
, #searchHighlightG env
|
||||
, #searchHighlightB env
|
||||
)
|
||||
|
||||
fun makeChr (chr, posX, posY, env: env_data) =
|
||||
CozetteAscii.make
|
||||
( chr
|
||||
, Real32.fromInt posX
|
||||
, Real32.fromInt posY
|
||||
, #charZ env
|
||||
, TC.scale
|
||||
, #fw env
|
||||
, #fh env
|
||||
, #charR env
|
||||
, #charG env
|
||||
, #charB env
|
||||
)
|
||||
|
||||
fun makeCursorHighlightedChr (chr, posX, posY, env: env_data) =
|
||||
CozetteAscii.make
|
||||
( chr
|
||||
, Real32.fromInt posX
|
||||
, Real32.fromInt posY
|
||||
, #charZ env
|
||||
, TC.scale
|
||||
, #fw env
|
||||
, #fh env
|
||||
, #cursorHighlightedCharR env
|
||||
, #cursorHighlightedCharG env
|
||||
, #cursorHighlightedCharB env
|
||||
)
|
||||
|
||||
fun makeSearchHighlightedChr (chr, posX, posY, env: env_data) =
|
||||
CozetteAscii.make
|
||||
( chr
|
||||
, Real32.fromInt posX
|
||||
, Real32.fromInt posY
|
||||
, #charZ env
|
||||
, TC.scale
|
||||
, #fw env
|
||||
, #fh env
|
||||
, #searchHighlightedCharR env
|
||||
, #searchHighlightedCharG env
|
||||
, #searchHighlightedCharB env
|
||||
)
|
||||
|
||||
(* gets line start idx, relative to right hd *)
|
||||
fun getRelativeLineStartFromRightHead (startLine, curLine, rLnHd) =
|
||||
if startLine > curLine then
|
||||
let val lnPos = startLine - curLine - 1
|
||||
in Vector.sub (rLnHd, lnPos) + 1
|
||||
end
|
||||
else
|
||||
0
|
||||
|
||||
(* gets line start idx, absolute *)
|
||||
fun getAbsoluteLineStartFromRightHead (curIdx, startLine, curLine, rLnHd) =
|
||||
let
|
||||
val startIdx =
|
||||
if startLine > curLine then
|
||||
let val lnPos = startLine - curLine - 1
|
||||
in Vector.sub (rLnHd, lnPos) + 1
|
||||
end
|
||||
else
|
||||
0
|
||||
in
|
||||
curIdx + startIdx
|
||||
end
|
||||
|
||||
fun getLineAbsIdxFromBuffer (startLine, buffer: LineGap.t) =
|
||||
let
|
||||
val {rightLines, line = curLine, idx = curIdx, ...} = buffer
|
||||
in
|
||||
case rightLines of
|
||||
rLnHd :: _ =>
|
||||
getAbsoluteLineStartFromRightHead (curIdx, startLine, curLine, rLnHd)
|
||||
| [] =>
|
||||
raise Fail
|
||||
"text-builder-utils.sml 268:\
|
||||
\should never call function when at end of buffer"
|
||||
end
|
||||
end
|
||||
253
shf/fcore/text-builder/text-builder-with-cursor.sml
Normal file
253
shf/fcore/text-builder/text-builder-with-cursor.sml
Normal file
@@ -0,0 +1,253 @@
|
||||
structure TextBuilderWithCursor =
|
||||
struct
|
||||
structure TC = TextConstants
|
||||
structure Utils = TextBuilderUtils
|
||||
|
||||
fun goToFirstLineAfter
|
||||
(stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) =
|
||||
case (stl, ltl) of
|
||||
(shd :: stl, lhd :: ltl) =>
|
||||
if Vector.length lhd > 0 then
|
||||
let
|
||||
val lineOffset = Vector.sub (lhd, 0)
|
||||
val strPos = lineOffset + 1
|
||||
val absIdx = absIdx + strPos
|
||||
val posY = posY + TC.ySpace
|
||||
val lineNumber = lineNumber + 1
|
||||
in
|
||||
build
|
||||
( strPos
|
||||
, shd
|
||||
, stl
|
||||
, lhd
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY
|
||||
, 0
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
else
|
||||
(* keep looping until we find a linebreak *)
|
||||
goToFirstLineAfter
|
||||
( stl
|
||||
, ltl
|
||||
, posY
|
||||
, lineNumber
|
||||
, absIdx + String.size shd
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
| (_, _) => acc
|
||||
|
||||
and skipToNextLine
|
||||
(pos, str, stl, line, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) =
|
||||
if Vector.length line = 0 then
|
||||
let
|
||||
(* get index of buffer after this string *)
|
||||
val absIdx = absIdx - pos
|
||||
val absIdx = absIdx + String.size str
|
||||
in
|
||||
goToFirstLineAfter
|
||||
(stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc)
|
||||
end
|
||||
else
|
||||
(* bin search lines *)
|
||||
let
|
||||
val linePos = BinSearch.equalOrMore (pos + 1, line)
|
||||
in
|
||||
if linePos = ~1 then
|
||||
(* next line is not in this node *)
|
||||
let
|
||||
val absIdx = absIdx - pos
|
||||
val absIdx = absIdx + String.size str
|
||||
in
|
||||
goToFirstLineAfter
|
||||
(stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc)
|
||||
end
|
||||
else
|
||||
let
|
||||
val lineOffset = Vector.sub (line, linePos)
|
||||
val newStrPos = lineOffset + 1
|
||||
val absIdx = absIdx - pos + newStrPos
|
||||
val posY = posY + TC.ySpace
|
||||
val lineNumber = lineNumber + 1
|
||||
in
|
||||
build
|
||||
( newStrPos
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY
|
||||
, 0
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
and build
|
||||
( pos
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX
|
||||
, posY
|
||||
, column
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env: Utils.env_data
|
||||
, acc
|
||||
) =
|
||||
if pos = String.size str then
|
||||
case (stl, ltl) of
|
||||
(str :: stl, line :: ltl) =>
|
||||
build
|
||||
( 0
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX
|
||||
, posY
|
||||
, column
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
| (_, _) => acc
|
||||
else
|
||||
case String.sub (str, pos) of
|
||||
#"\n" =>
|
||||
if lineNumber + 1 > #lastLineNumber env then
|
||||
acc
|
||||
else
|
||||
let
|
||||
val acc =
|
||||
if absIdx = cursorIdx then
|
||||
Utils.makeCursor (posX, posY, env) :: acc
|
||||
else
|
||||
acc
|
||||
in
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY + TC.ySpace
|
||||
, 0
|
||||
, lineNumber + 1
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
| #" " =>
|
||||
let
|
||||
val acc =
|
||||
if absIdx = cursorIdx then
|
||||
Utils.makeCursor (posX, posY, env) :: acc
|
||||
else
|
||||
acc
|
||||
val posX =
|
||||
if column < #scrollColumnStart env then
|
||||
(* if we are prior to the start column,
|
||||
* we want to set the x position to be at the start
|
||||
* in preparation for when we are at the start column *)
|
||||
#startX env
|
||||
else
|
||||
posX + TC.xSpace
|
||||
in
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX
|
||||
, posY
|
||||
, column + 1
|
||||
, lineNumber
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
| chr =>
|
||||
if column < #scrollColumnStart env then
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY
|
||||
, column + 1
|
||||
, lineNumber
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
else if column > #scrollColumnEnd env then
|
||||
skipToNextLine
|
||||
( pos
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posY
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
else
|
||||
let
|
||||
val acc =
|
||||
if absIdx = cursorIdx then
|
||||
let
|
||||
val acc = Utils.makeCursor (posX, posY, env) :: acc
|
||||
in
|
||||
Utils.makeCursorHighlightedChr (chr, posX, posY, env) :: acc
|
||||
end
|
||||
else
|
||||
Utils.makeChr (chr, posX, posY, env) :: acc
|
||||
in
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX + TC.xSpace
|
||||
, posY
|
||||
, column + 1
|
||||
, lineNumber
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
end
|
||||
258
shf/fcore/text-builder/text-builder-with-highlight.sml
Normal file
258
shf/fcore/text-builder/text-builder-with-highlight.sml
Normal file
@@ -0,0 +1,258 @@
|
||||
structure TextBuilderWithHighlight =
|
||||
struct
|
||||
structure TC = TextConstants
|
||||
structure Utils = TextBuilderUtils
|
||||
|
||||
fun isSecondLastChr (pos, str, tl) =
|
||||
case tl of
|
||||
[] => pos = String.size str - 2
|
||||
| _ => false
|
||||
|
||||
fun goToFirstLineAfter
|
||||
(stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) =
|
||||
case (stl, ltl) of
|
||||
(shd :: stl, lhd :: ltl) =>
|
||||
if Vector.length lhd > 0 then
|
||||
let
|
||||
val lineOffset = Vector.sub (lhd, 0)
|
||||
val strPos = lineOffset + 1
|
||||
val absIdx = absIdx + strPos
|
||||
val posY = posY + TC.ySpace
|
||||
val lineNumber = lineNumber + 1
|
||||
in
|
||||
build
|
||||
( strPos
|
||||
, shd
|
||||
, stl
|
||||
, lhd
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY
|
||||
, 0
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
else
|
||||
(* keep looping until we find a linebreak *)
|
||||
goToFirstLineAfter
|
||||
( stl
|
||||
, ltl
|
||||
, posY
|
||||
, lineNumber
|
||||
, absIdx + String.size shd
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
| (_, _) => acc
|
||||
|
||||
and skipToNextLine
|
||||
(pos, str, stl, line, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc) =
|
||||
if Vector.length line = 0 then
|
||||
let
|
||||
(* get index of buffer after this string *)
|
||||
val absIdx = absIdx - pos
|
||||
val absIdx = absIdx + String.size str
|
||||
in
|
||||
goToFirstLineAfter
|
||||
(stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc)
|
||||
end
|
||||
else
|
||||
(* bin search lines *)
|
||||
let
|
||||
val linePos = BinSearch.equalOrMore (pos + 1, line)
|
||||
in
|
||||
if linePos = ~1 then
|
||||
(* next line is not in this node *)
|
||||
let
|
||||
val absIdx = absIdx - pos
|
||||
val absIdx = absIdx + String.size str
|
||||
in
|
||||
goToFirstLineAfter
|
||||
(stl, ltl, posY, lineNumber, absIdx, cursorIdx, env, acc)
|
||||
end
|
||||
else
|
||||
let
|
||||
val lineOffset = Vector.sub (line, linePos)
|
||||
val newStrPos = lineOffset + 1
|
||||
val absIdx = absIdx - pos + newStrPos
|
||||
val posY = posY + TC.ySpace
|
||||
val lineNumber = lineNumber + 1
|
||||
in
|
||||
build
|
||||
( newStrPos
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY
|
||||
, 0
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
and build
|
||||
( pos
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX
|
||||
, posY
|
||||
, column
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env: Utils.env_data
|
||||
, acc
|
||||
) =
|
||||
if pos = String.size str then
|
||||
case (stl, ltl) of
|
||||
(str :: stl, line :: ltl) =>
|
||||
build
|
||||
( 0
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX
|
||||
, posY
|
||||
, column
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
| (_, _) => acc
|
||||
else
|
||||
case String.sub (str, pos) of
|
||||
#" " =>
|
||||
let
|
||||
val acc =
|
||||
if absIdx = cursorIdx then
|
||||
Utils.makeCursor (posX, posY, env) :: acc
|
||||
else
|
||||
acc
|
||||
val acc =
|
||||
if PersistentVector.isInRange (absIdx, #searchList env) then
|
||||
Utils.makeSearchHighlight (posX, posY, env) :: acc
|
||||
else
|
||||
acc
|
||||
val posX =
|
||||
if column < #scrollColumnStart env then #startX env
|
||||
else posX + TC.xSpace
|
||||
in
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX
|
||||
, posY
|
||||
, column + 1
|
||||
, lineNumber
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
| #"\n" =>
|
||||
if lineNumber + 1 > #lastLineNumber env then
|
||||
acc
|
||||
else
|
||||
let
|
||||
val acc =
|
||||
if absIdx = cursorIdx then
|
||||
Utils.makeCursor (posX, posY, env) :: acc
|
||||
else
|
||||
acc
|
||||
in
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY + TC.ySpace
|
||||
, 0
|
||||
, lineNumber + 1
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
| chr =>
|
||||
if column < #scrollColumnStart env then
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, #startX env
|
||||
, posY
|
||||
, column + 1
|
||||
, lineNumber
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
else if column > #scrollColumnEnd env then
|
||||
skipToNextLine
|
||||
( pos
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posY
|
||||
, lineNumber
|
||||
, absIdx
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
else
|
||||
let
|
||||
val acc =
|
||||
if absIdx = cursorIdx then
|
||||
Utils.makeCursorHighlightedChr (chr, posX, posY, env)
|
||||
:: Utils.makeCursor (posX, posY, env) :: acc
|
||||
else if PersistentVector.isInRange (absIdx, #searchList env) then
|
||||
Utils.makeSearchHighlightedChr (chr, posX, posY, env)
|
||||
:: Utils.makeSearchHighlight (posX, posY, env) :: acc
|
||||
else
|
||||
Utils.makeChr (chr, posX, posY, env) :: acc
|
||||
in
|
||||
build
|
||||
( pos + 1
|
||||
, str
|
||||
, stl
|
||||
, line
|
||||
, ltl
|
||||
, posX + TC.xSpace
|
||||
, posY
|
||||
, column + 1
|
||||
, lineNumber
|
||||
, absIdx + 1
|
||||
, cursorIdx
|
||||
, env
|
||||
, acc
|
||||
)
|
||||
end
|
||||
end
|
||||
10
shf/fcore/text-constants.sml
Normal file
10
shf/fcore/text-constants.sml
Normal file
@@ -0,0 +1,10 @@
|
||||
structure TextConstants =
|
||||
struct
|
||||
val xSpace = 13
|
||||
val xSpace3 = xSpace * 3
|
||||
val ySpace = 25
|
||||
val scale: Real32.real = 2.0
|
||||
|
||||
val textLineCount = 80
|
||||
val textLineWidth = xSpace * textLineCount
|
||||
end
|
||||
68
shf/fcore/text-scroll.sml
Normal file
68
shf/fcore/text-scroll.sml
Normal file
@@ -0,0 +1,68 @@
|
||||
structure TextScroll =
|
||||
struct
|
||||
structure TC = TextConstants
|
||||
|
||||
(* calculates new scroll column from integer arguments *)
|
||||
fun calculateScrollColumn
|
||||
(startOfLine, cursorIdx, windowWidth, prevScrollColumn) =
|
||||
let
|
||||
val newColumn = cursorIdx - startOfLine
|
||||
val howManyColumnsCanWeFit =
|
||||
if windowWidth >= TC.textLineWidth then TC.textLineCount
|
||||
else windowWidth div TC.xSpace
|
||||
val howManyColumnsCanWeFit = howManyColumnsCanWeFit - 1
|
||||
in
|
||||
if newColumn < prevScrollColumn then
|
||||
(* we are moving the cursor backwards
|
||||
* so make sure that newColumn is on the left side *)
|
||||
newColumn
|
||||
else if newColumn > prevScrollColumn + howManyColumnsCanWeFit then
|
||||
(* we are scrolling forwards *)
|
||||
newColumn - howManyColumnsCanWeFit
|
||||
else
|
||||
(* we can display the current column without moving the scroll column
|
||||
* so we do that *)
|
||||
prevScrollColumn
|
||||
end
|
||||
|
||||
(* Preqreuisite: move buffer to cursorIdx *)
|
||||
fun getScrollColumn (buffer, cursorIdx, windowWidth, prevScrollColumn) =
|
||||
let
|
||||
val startOfLine = Cursor.vi0 (buffer, cursorIdx)
|
||||
in
|
||||
calculateScrollColumn
|
||||
(startOfLine, cursorIdx, windowWidth, prevScrollColumn)
|
||||
end
|
||||
|
||||
fun getScrollColumnFromString (cursorIdx, windowWidth, prevScrollColumn) =
|
||||
calculateScrollColumn (0, cursorIdx, windowWidth, prevScrollColumn)
|
||||
|
||||
fun getStartLine (prevLineNumber, cursorLine, windowHeight, totalLines) =
|
||||
if cursorLine <= (prevLineNumber + 3) then
|
||||
(* cursorLine is prior to or same as prevLineNumber,
|
||||
* so use cursorLine to calculate the start line we want. *)
|
||||
Int.max (cursorLine - 3, 0)
|
||||
else
|
||||
(* cursorLine > prevLineNumber *)
|
||||
let
|
||||
val howManyLinesWeCanFit = windowHeight div TC.ySpace
|
||||
in
|
||||
if cursorLine > prevLineNumber + (howManyLinesWeCanFit - 3) then
|
||||
(* cursorLine is after the visible part of the screen
|
||||
* so return the mimimum line where cursorLine is visible *)
|
||||
if cursorLine >= totalLines - 3 then
|
||||
Int.max (totalLines - howManyLinesWeCanFit, 0)
|
||||
else
|
||||
cursorLine - howManyLinesWeCanFit + 3
|
||||
else
|
||||
prevLineNumber
|
||||
end
|
||||
|
||||
fun getLineCentre (cursorLine, windowHeight) =
|
||||
let
|
||||
val howManyLinesWeCanFit = windowHeight div TC.ySpace
|
||||
val startLine = cursorLine - (howManyLinesWeCanFit div 2)
|
||||
in
|
||||
Int.max (startLine, 0)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user