Compare commits
2 commits
88098a9ce0
...
99ee0da8c1
| Author | SHA1 | Date | |
|---|---|---|---|
| 99ee0da8c1 | |||
| b4372b69fa |
2 changed files with 429 additions and 472 deletions
|
|
@ -1,543 +1,466 @@
|
|||
@textEditor
|
||||
|
||||
_ = "TODO
|
||||
# R to replace instead of insert
|
||||
# s to seek
|
||||
# w / b bug, can't select `textEditor` at start of line
|
||||
# smart case searching
|
||||
";
|
||||
_TODO = [
|
||||
"# R to replace instead of insert",
|
||||
"# s to seek",
|
||||
"# w / b bug, can't select `textEditor` at start of line",
|
||||
"# smart case searching"
|
||||
];
|
||||
|
||||
textEditor = name \
|
||||
# defaults = {};
|
||||
# c = { ...defaults, ...config };
|
||||
scale = 2;
|
||||
charH = 12;
|
||||
charW = 5;
|
||||
lineGap = 1;
|
||||
charGap = 2;
|
||||
lineH = charH * scale + lineGap;
|
||||
|
||||
lineH = (charH * scale) + lineGap;
|
||||
source = getModuleSource name
|
||||
| Some s \ s
|
||||
| (Some s) \ s
|
||||
| None \ getSource name;
|
||||
|
||||
lines = split "\n" source;
|
||||
|
||||
clampCursor = state \
|
||||
line = nth state.cursorRow state.lines ~ unwrapOr "";
|
||||
|
||||
newRow = max 0 state.cursorRow;
|
||||
newRow2 = min (len state.lines - 1) newRow;
|
||||
|
||||
maxCol = state.mode
|
||||
| Insert \ len line
|
||||
| Normal \ max 0 (len line - 1);
|
||||
newCol2 = min maxCol (max 0 state.cursorCol);
|
||||
|
||||
state.{ cursorRow = newRow2, cursorCol = newCol2 };
|
||||
|
||||
line = unwrapOr "" (nth state.cursorRow state.lines);
|
||||
newRow = max 0 state.cursorRow;
|
||||
newRow2 = min ((len state.lines) - 1) newRow;
|
||||
maxCol = state.mode
|
||||
| Insert \ len line
|
||||
| Normal \ max 0 ((len line) - 1);
|
||||
newCol2 = min maxCol (max 0 state.cursorCol);
|
||||
state.{ cursorRow = newRow2, cursorCol = newCol2 };
|
||||
charKind = ch \
|
||||
ch == " " | True \ Space
|
||||
| False \ (isWordChar ch
|
||||
| True \ Word
|
||||
| False \ Punct);
|
||||
|
||||
ch == " "
|
||||
| True \ Space
|
||||
| False \ (isWordChar ch
|
||||
| True \ Word
|
||||
| False \ Punct);
|
||||
skipWhile = pred lines row col \
|
||||
line = nth row lines ~ unwrapOr "";
|
||||
col >= len line
|
||||
| True \ (row + 1 < len lines
|
||||
| True \ skipWhile pred lines (row + 1) 0
|
||||
| False \ { row = row, col = col })
|
||||
| False \ (pred (nth col line ~ unwrapOr "")
|
||||
| True \ skipWhile pred lines row (col + 1)
|
||||
| False \ { row = row, col = col });
|
||||
|
||||
line = unwrapOr "" (nth row lines);
|
||||
col >= (len line)
|
||||
| True \ ((row + 1) < (len lines)
|
||||
| True \ skipWhile pred lines (row + 1) 0
|
||||
| False \ { row = row, col = col })
|
||||
| False \ (pred (unwrapOr "" (nth col line))
|
||||
| True \ skipWhile pred lines row (col + 1)
|
||||
| False \ { row = row, col = col });
|
||||
nextWordStart = lines row col \
|
||||
line = nth row lines ~ unwrapOr "";
|
||||
kind = charKind (nth col line ~ unwrapOr " ");
|
||||
pos = skipWhile (ch \ charKind ch == kind) lines row col;
|
||||
skipWhile (ch \ ch == " ") lines pos.row pos.col;
|
||||
|
||||
line = unwrapOr "" (nth row lines);
|
||||
kind = charKind (unwrapOr " " (nth col line));
|
||||
pos = skipWhile (ch \ (charKind ch) == kind) lines row col;
|
||||
skipWhile (ch \ ch == " ") lines pos.row pos.col;
|
||||
skipBack = pred line row col lines \
|
||||
col < 0
|
||||
| True \ (row > 0
|
||||
| True \ (
|
||||
prevLine = nth (row - 1) lines ~ unwrapOr "";
|
||||
skipBack pred prevLine (row - 1) (len prevLine - 1) lines)
|
||||
| False \ { row = 0, col = 0 - 1 })
|
||||
| False \ (pred (nth col line ~ unwrapOr "")
|
||||
| True \ skipBack pred line row (col - 1) lines
|
||||
| False \ { row = row, col = col });
|
||||
|
||||
col < 0
|
||||
| True \ (row > 0
|
||||
| True \ (prevLine = unwrapOr "" (nth (row - 1) lines);
|
||||
skipBack pred prevLine (row - 1) ((len prevLine) - 1) lines)
|
||||
| False \ { row = 0, col = 0 - 1 })
|
||||
| False \ (pred (unwrapOr "" (nth col line))
|
||||
| True \ skipBack pred line row (col - 1) lines
|
||||
| False \ { row = row, col = col });
|
||||
prevWordStart = lines row col \
|
||||
# step back 1
|
||||
c = col - 1;
|
||||
r = row;
|
||||
r2 = c < 0
|
||||
| True \ r - 1
|
||||
| False \ r;
|
||||
c2 = c < 0
|
||||
| True \ (max 0 (len (nth (r - 1) lines ~ unwrapOr "") - 1))
|
||||
| False \ c;
|
||||
# no prev line
|
||||
r2 < 0
|
||||
c = col - 1;
|
||||
r = row;
|
||||
r2 = c < 0
|
||||
| True \ r - 1
|
||||
| False \ r;
|
||||
c2 = c < 0
|
||||
| True \ max 0 ((len (unwrapOr "" (nth (r - 1) lines))) - 1)
|
||||
| False \ c;
|
||||
r2 < 0
|
||||
| True \ { row = 0, col = 0 }
|
||||
| False \ (
|
||||
line = nth r2 lines ~ unwrapOr "";
|
||||
# skip spaces backward
|
||||
| False \ (line = unwrapOr "" (nth r2 lines);
|
||||
pos = skipBack (ch \ ch == " ") line r2 c2 lines;
|
||||
# skip same kind backward
|
||||
kind = charKind (nth pos.col (nth pos.row lines ~ unwrapOr "") ~ unwrapOr " ");
|
||||
pos2 = skipBack (ch \ charKind ch == kind) (nth pos.row lines ~ unwrapOr "") pos.row (pos.col - 1) lines;
|
||||
# go 1 ahead
|
||||
{ row = pos2.row, col = pos2.col + 1 }
|
||||
);
|
||||
|
||||
kind = charKind (unwrapOr " " (nth pos.col (unwrapOr "" (nth pos.row lines))));
|
||||
pos2 = skipBack (ch \ (charKind ch) == kind) (unwrapOr "" (nth pos.row lines)) pos.row (pos.col - 1) lines;
|
||||
{ row = pos2.row, col = pos2.col + 1 });
|
||||
resolveMotion = motion state \
|
||||
from = { row = state.cursorRow, col = state.cursorCol };
|
||||
to = motion
|
||||
| Word \ nextWordStart state.lines from.row from.col
|
||||
| BackWord \ prevWordStart state.lines from.row from.col
|
||||
| WholeLine \ { row = from.row, col = 0, linewise = True }
|
||||
| StartOfLine \ { row = from.row, col = 0 }
|
||||
| StartOfLineChars \ nextWordStart state.lines from.row 0
|
||||
| EndOfLine \ (
|
||||
line = nth state.cursorRow state.lines ~ unwrapOr "";
|
||||
{ row = state.cursorRow, col = (len line) })
|
||||
| FirstLine \ (
|
||||
line = nth 0 state.lines ~ unwrapOr "";
|
||||
{ row = 0, col = len line - 1 })
|
||||
| LastLine \ (
|
||||
lastRow = len state.lines - 1;
|
||||
line = nth lastRow state.lines ~ unwrapOr "";
|
||||
{ row = lastRow, col = len line - 1 })
|
||||
| Cursor \ { row = state.cursorRow, col = state.cursorCol + 1 };
|
||||
{ from = from, to = to };
|
||||
|
||||
from = { row = state.cursorRow, col = state.cursorCol };
|
||||
to = motion
|
||||
| Word \ nextWordStart state.lines from.row from.col
|
||||
| BackWord \ prevWordStart state.lines from.row from.col
|
||||
| WholeLine \ { row = from.row, col = 0, linewise = True }
|
||||
| StartOfLine \ { row = from.row, col = 0 }
|
||||
| StartOfLineChars \ nextWordStart state.lines from.row 0
|
||||
| EndOfLine \ (line = unwrapOr "" (nth state.cursorRow state.lines);
|
||||
{ row = state.cursorRow, col = len line })
|
||||
| FirstLine \ (line = unwrapOr "" (nth 0 state.lines);
|
||||
{ row = 0, col = (len line) - 1 })
|
||||
| LastLine \ (lastRow = (len state.lines) - 1;
|
||||
line = unwrapOr "" (nth lastRow state.lines);
|
||||
{ row = lastRow, col = (len line) - 1 })
|
||||
| Cursor \ { row = state.cursorRow, col = state.cursorCol + 1 };
|
||||
{ from = from, to = to };
|
||||
deleteLine = state \
|
||||
newLines = [...(take (state.cursorRow) state.lines), ...(drop (state.cursorRow + 1) state.lines)];
|
||||
{ state = state.{ lines = newLines }, emit = [] };
|
||||
|
||||
newLines = [
|
||||
...take state.cursorRow state.lines,
|
||||
...drop (state.cursorRow + 1) state.lines
|
||||
];
|
||||
{ state = state.{ lines = newLines }, emit = [] };
|
||||
deleteRange = state from to \
|
||||
line = nth from.row state.lines ~ unwrapOr "";
|
||||
startCol = min from.col to.col;
|
||||
endCol = max from.col to.col;
|
||||
newLine = (slice line 0 startCol) & (slice line endCol (len line));
|
||||
state.{ lines = updateAt from.row (_ \ newLine) state.lines, cursorCol = startCol };
|
||||
|
||||
line = unwrapOr "" (nth from.row state.lines);
|
||||
startCol = min from.col to.col;
|
||||
endCol = max from.col to.col;
|
||||
newLine = (slice line 0 startCol) & (slice line endCol (len line));
|
||||
state.{ lines = updateAt from.row (_ \ newLine) state.lines, cursorCol = startCol };
|
||||
applyOperator = operator motion state \
|
||||
target = resolveMotion motion state;
|
||||
action = { operator = operator, motion = motion };
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
newState = operator
|
||||
| Delete \ (hasField "linewise" target.to
|
||||
| True \ deleteLine state
|
||||
| False \ { state = deleteRange state target.from target.to, emit = [] });
|
||||
{ state = clampCursor newState.state.{ undoStack = [stateSnapshot, ...state.undoStack], pending = None, lastAction = Some action }, emit = newState.emit };
|
||||
|
||||
moveCursor = motion state \
|
||||
state.{ cursorCol = motion.to.col, cursorRow = motion.to.row };
|
||||
|
||||
target = resolveMotion motion state;
|
||||
action = { operator = operator, motion = motion };
|
||||
stateSnapshot = {
|
||||
lines = state.lines,
|
||||
cursorRow = state.cursorRow,
|
||||
cursorCol = state.cursorCol
|
||||
};
|
||||
newState = operator
|
||||
| Delete \ (hasField "linewise" target.to
|
||||
| True \ deleteLine state
|
||||
| False \ {
|
||||
state = deleteRange state target.from target.to,
|
||||
emit = []
|
||||
});
|
||||
{
|
||||
state = clampCursor newState.state.{ undoStack = [stateSnapshot, ...state.undoStack], pending = None, lastAction = Some action },
|
||||
emit = newState.emit
|
||||
};
|
||||
moveCursor = motion state \ state.{ cursorCol = motion.to.col, cursorRow = motion.to.row };
|
||||
write = state \
|
||||
content = join "\n" state.lines;
|
||||
{ state = state, emit = [rebindAt [buffersKey, name] content] };
|
||||
|
||||
content = join "\n" state.lines;
|
||||
{
|
||||
state = state,
|
||||
emit = [rebindAt [buffersKey, name] content]
|
||||
};
|
||||
apply = state \
|
||||
content = (join "\n" state.lines);
|
||||
result = eval! content;
|
||||
_ = debug! "apply" [content, result];
|
||||
result
|
||||
| Defined _ \ { state = state, emit = [] }
|
||||
| Err msg \ (
|
||||
_ = debug! "error applying" [];
|
||||
{ state = state, emit = [] }
|
||||
)
|
||||
| _ \ { state = state, emit = [] };
|
||||
|
||||
content = join "\n" state.lines;
|
||||
isModule = getModuleSource name
|
||||
| (Some _) \ True
|
||||
| None \ False;
|
||||
result = isModule
|
||||
| True \ applyModule! name content
|
||||
| False \ eval! content;
|
||||
_ = debug! "apply" result;
|
||||
result
|
||||
| (Defined _) \ { state = state, emit = [] }
|
||||
| (Err msg) \ (_ = debug! "error applying" [];
|
||||
{ state = state, emit = [] })
|
||||
| _ \ { state = state, emit = [] };
|
||||
insertChar = char state \
|
||||
newLines = updateAt state.cursorRow (line \
|
||||
insertCharAt line state.cursorCol char
|
||||
) state.lines;
|
||||
|
||||
{ state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 }, emit = [] };
|
||||
|
||||
newLines = updateAt state.cursorRow (line \ insertCharAt line state.cursorCol char) state.lines;
|
||||
{
|
||||
state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 },
|
||||
emit = []
|
||||
};
|
||||
replaceChar = char state \
|
||||
newLines = updateAt state.cursorRow (line \
|
||||
newLine = deleteCharAt line state.cursorCol;
|
||||
insertCharAt newLine state.cursorCol char
|
||||
) state.lines;
|
||||
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
|
||||
{ state = state.{ lines = newLines, mode = Normal, undoStack = newUndoStack }, emit = [] };
|
||||
|
||||
insertMode = state \
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
{
|
||||
state = state.{
|
||||
undoStack = newUndoStack,
|
||||
mode = Insert
|
||||
},
|
||||
emit = []
|
||||
};
|
||||
|
||||
append = state \ insertMode state.{ cursorCol = state.cursorCol + 1 };
|
||||
|
||||
appendEndLine = state \
|
||||
line = nth state.cursorRow state.lines ~ unwrapOr "";
|
||||
insertMode state.{ cursorCol = len line };
|
||||
|
||||
openLine = state \
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
cursorRow = state.cursorRow + 1;
|
||||
newLines = insertAt cursorRow "" state.lines;
|
||||
{
|
||||
state = state.{
|
||||
lines = newLines,
|
||||
cursorRow = cursorRow,
|
||||
cursorCol = 0,
|
||||
undoStack = newUndoStack,
|
||||
mode = Insert
|
||||
},
|
||||
emit = []
|
||||
};
|
||||
|
||||
openLineAbove = state \ openLine state.{ cursorRow = state.cursorRow - 1 };
|
||||
|
||||
escape = state \ { state = state.{ mode = Normal, pending = None }, emit = [] };
|
||||
|
||||
undo = state \ state.undoStack
|
||||
| [] \ { state = state, emit = [] }
|
||||
| [prev, ...rest] \ (
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
|
||||
{ state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = rest, redoStack = [stateSnapshot, ...state.redoStack] }, emit = [] });
|
||||
|
||||
redo = state \ state.redoStack
|
||||
| [] \ { state = state, emit = [] }
|
||||
| [prev, ...rest] \ (
|
||||
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
|
||||
|
||||
{ state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = [stateSnapshot, ...state.undoStack], redoStack = rest }, emit = [] });
|
||||
|
||||
backspace = state \
|
||||
state.cursorCol == 0
|
||||
| True \ (
|
||||
state.cursorRow == 0
|
||||
| True \ { state = state, emit = [] }
|
||||
| False \ (
|
||||
{ state = state, emit = [] } # todo, join with previous line
|
||||
)
|
||||
)
|
||||
| False \ (
|
||||
newLines = updateAt state.cursorRow (line \
|
||||
before = slice line 0 (state.cursorCol - 1);
|
||||
after = slice line state.cursorCol (len line);
|
||||
before & after) state.lines;
|
||||
|
||||
{ state = state.{ lines = newLines, cursorCol = state.cursorCol - 1 }, emit = [] });
|
||||
|
||||
newLine = deleteCharAt line state.cursorCol;
|
||||
insertCharAt newLine state.cursorCol char) state.lines;
|
||||
stateSnapshot = {
|
||||
lines = state.lines,
|
||||
cursorRow = state.cursorRow,
|
||||
cursorCol = state.cursorCol
|
||||
};
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
{
|
||||
state = state.{ lines = newLines, mode = Normal, undoStack = newUndoStack },
|
||||
emit = []
|
||||
};
|
||||
insertMode = state \
|
||||
stateSnapshot = {
|
||||
lines = state.lines,
|
||||
cursorRow = state.cursorRow,
|
||||
cursorCol = state.cursorCol
|
||||
};
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
{
|
||||
state = state.{ undoStack = newUndoStack, mode = Insert },
|
||||
emit = []
|
||||
};
|
||||
append = state \ insertMode state.{ cursorCol = state.cursorCol + 1 };
|
||||
appendEndLine = state \
|
||||
line = unwrapOr "" (nth state.cursorRow state.lines);
|
||||
insertMode state.{ cursorCol = len line };
|
||||
openLine = state \
|
||||
stateSnapshot = {
|
||||
lines = state.lines,
|
||||
cursorRow = state.cursorRow,
|
||||
cursorCol = state.cursorCol
|
||||
};
|
||||
newUndoStack = [stateSnapshot, ...state.undoStack];
|
||||
cursorRow = state.cursorRow + 1;
|
||||
newLines = insertAt cursorRow "" state.lines;
|
||||
{
|
||||
state = state.{ lines = newLines, cursorRow = cursorRow, cursorCol = 0, undoStack = newUndoStack, mode = Insert },
|
||||
emit = []
|
||||
};
|
||||
openLineAbove = state \ openLine state.{ cursorRow = state.cursorRow - 1 };
|
||||
escape = state \ {
|
||||
state = state.{ mode = Normal, pending = None },
|
||||
emit = []
|
||||
};
|
||||
undo = state \
|
||||
state.undoStack
|
||||
| [] \ { state = state, emit = [] }
|
||||
| [prev, ...rest] \ (stateSnapshot = {
|
||||
lines = state.lines,
|
||||
cursorRow = state.cursorRow,
|
||||
cursorCol = state.cursorCol
|
||||
};
|
||||
{
|
||||
state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = rest, redoStack = [stateSnapshot, ...state.redoStack] },
|
||||
emit = []
|
||||
});
|
||||
redo = state \
|
||||
state.redoStack
|
||||
| [] \ { state = state, emit = [] }
|
||||
| [prev, ...rest] \ (stateSnapshot = {
|
||||
lines = state.lines,
|
||||
cursorRow = state.cursorRow,
|
||||
cursorCol = state.cursorCol
|
||||
};
|
||||
{
|
||||
state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = [stateSnapshot, ...state.undoStack], redoStack = rest },
|
||||
emit = []
|
||||
});
|
||||
backspace = state \
|
||||
state.cursorCol == 0
|
||||
| True \ (state.cursorRow == 0
|
||||
| True \ { state = state, emit = [] }
|
||||
| False \ { state = state, emit = [] })
|
||||
| False \ (newLines = updateAt state.cursorRow (line \
|
||||
before = slice line 0 (state.cursorCol - 1);
|
||||
after = slice line state.cursorCol (len line);
|
||||
before & after) state.lines;
|
||||
{
|
||||
state = state.{ lines = newLines, cursorCol = state.cursorCol - 1 },
|
||||
emit = []
|
||||
});
|
||||
enter = state \
|
||||
line = nth state.cursorRow state.lines ~ unwrapOr "";
|
||||
before = slice line 0 state.cursorCol;
|
||||
after = slice line state.cursorCol (len line);
|
||||
|
||||
newLines = updateAt state.cursorRow (_ \ before) state.lines;
|
||||
newLines2 = insertAt (state.cursorRow + 1) after newLines;
|
||||
|
||||
{ state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 }, emit = [] };
|
||||
|
||||
line = unwrapOr "" (nth state.cursorRow state.lines);
|
||||
before = slice line 0 state.cursorCol;
|
||||
after = slice line state.cursorCol (len line);
|
||||
newLines = updateAt state.cursorRow (_ \ before) state.lines;
|
||||
newLines2 = insertAt (state.cursorRow + 1) after newLines;
|
||||
{
|
||||
state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 },
|
||||
emit = []
|
||||
};
|
||||
upArrow = state \
|
||||
newState = clampCursor state.{ cursorRow = state.cursorRow - 1 };
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
newState = clampCursor state.{ cursorRow = state.cursorRow - 1 };
|
||||
{ state = newState, emit = [] };
|
||||
downArrow = state \
|
||||
newState = clampCursor state.{ cursorRow = state.cursorRow + 1 };
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
newState = clampCursor state.{ cursorRow = state.cursorRow + 1 };
|
||||
{ state = newState, emit = [] };
|
||||
leftArrow = state \
|
||||
newState = clampCursor state.{ cursorCol = state.cursorCol - 1 };
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
newState = clampCursor state.{ cursorCol = state.cursorCol - 1 };
|
||||
{ state = newState, emit = [] };
|
||||
rightArrow = state \
|
||||
newState = clampCursor state.{ cursorCol = state.cursorCol + 1 };
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
newState = clampCursor state.{ cursorCol = state.cursorCol + 1 };
|
||||
{ state = newState, emit = [] };
|
||||
findNext = query lines row col \
|
||||
rest = strIndexOf query (nth row lines ~ unwrapOr "") col;
|
||||
rest
|
||||
| Some c \ { row = row, col = c }
|
||||
| None \ (
|
||||
findInLines = r \
|
||||
r >= len lines
|
||||
| True \ findInLines 0 # wrap to top
|
||||
| False \ (r == row
|
||||
| True \ (strIndexOf query (nth row lines ~ unwrapOr "") 0
|
||||
| Some c \ { row = row, col = c }
|
||||
| None \ { row = row, col = col }) # no match
|
||||
| False \ (strIndexOf query (nth r lines ~ unwrapOr "") 0
|
||||
| Some c \ { row = r, col = c }
|
||||
| None \ findInLines (r + 1)));
|
||||
findInLines (row + 1));
|
||||
|
||||
rest = strIndexOf query (unwrapOr "" (nth row lines)) col;
|
||||
rest
|
||||
| (Some c) \ { row = row, col = c }
|
||||
| None \ (findInLines = r \
|
||||
r >= (len lines)
|
||||
| True \ findInLines 0
|
||||
| False \ (r == row
|
||||
| True \ (strIndexOf query (unwrapOr "" (nth row lines)) 0
|
||||
| (Some c) \ { row = row, col = c }
|
||||
| None \ { row = row, col = col })
|
||||
| False \ (strIndexOf query (unwrapOr "" (nth r lines)) 0
|
||||
| (Some c) \ { row = r, col = c }
|
||||
| None \ findInLines (r + 1)));
|
||||
findInLines (row + 1));
|
||||
findPrev = query lines row col \
|
||||
line = slice (nth row lines ~ unwrapOr "") 0 col;
|
||||
rest = strLastIndexOf query line;
|
||||
rest
|
||||
| Some c \ { row = row, col = c }
|
||||
| None \ (
|
||||
findInLines = r \
|
||||
r < 0
|
||||
| True \ findInLines (len lines - 1) # wrap to bottom
|
||||
| False \ (r == row
|
||||
| True \ (strLastIndexOf query (nth row lines ~ unwrapOr "")
|
||||
| Some c \ { row = row, col = c }
|
||||
| None \ { row = row, col = col }) # no match
|
||||
| False \ (strLastIndexOf query (nth r lines ~ unwrapOr "")
|
||||
| Some c \ { row = r, col = c }
|
||||
| None \ findInLines (r - 1)));
|
||||
findInLines (row - 1));
|
||||
|
||||
{
|
||||
view = ctx \
|
||||
|
||||
line = slice (unwrapOr "" (nth row lines)) 0 col;
|
||||
rest = strLastIndexOf query line;
|
||||
rest
|
||||
| (Some c) \ { row = row, col = c }
|
||||
| None \ (findInLines = r \
|
||||
r < 0
|
||||
| True \ findInLines ((len lines) - 1)
|
||||
| False \ (r == row
|
||||
| True \ (strLastIndexOf query (unwrapOr "" (nth row lines))
|
||||
| (Some c) \ { row = row, col = c }
|
||||
| None \ { row = row, col = col })
|
||||
| False \ (strLastIndexOf query (unwrapOr "" (nth r lines))
|
||||
| (Some c) \ { row = r, col = c }
|
||||
| None \ findInLines (r - 1)));
|
||||
findInLines (row - 1));
|
||||
{ view = ctx \
|
||||
autoScroll = state \
|
||||
cursorY = state.cursorRow * lineH;
|
||||
newScrollY = (cursorY < state.scrollY
|
||||
| True \ cursorY
|
||||
| False \ (cursorY + lineH > state.scrollY + ctx.h
|
||||
| True \ cursorY + lineH - ctx.h
|
||||
| False \ state.scrollY));
|
||||
|
||||
cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
|
||||
newScrollX = (cursorX < state.scrollX
|
||||
| True \ cursorX
|
||||
| False \ (cursorX + charW * scale > state.scrollX + ctx.w
|
||||
| True \ cursorX + charW * scale - ctx.w
|
||||
| False \ state.scrollX));
|
||||
state.{ scrollY = newScrollY, scrollX = newScrollX };
|
||||
|
||||
withScroll = result \
|
||||
{ state = autoScroll result.state, emit = result.emit };
|
||||
|
||||
# search stuff
|
||||
cursorY = state.cursorRow * lineH;
|
||||
newScrollY = cursorY < state.scrollY
|
||||
| True \ cursorY
|
||||
| False \ ((cursorY + lineH) > (state.scrollY + ctx.h)
|
||||
| True \ (cursorY + lineH) - ctx.h
|
||||
| False \ state.scrollY);
|
||||
cursorX = ((state.cursorCol * charW) * scale) + (charGap * state.cursorCol);
|
||||
newScrollX = cursorX < state.scrollX
|
||||
| True \ cursorX
|
||||
| False \ ((cursorX + (charW * scale)) > (state.scrollX + ctx.w)
|
||||
| True \ (cursorX + (charW * scale)) - ctx.w
|
||||
| False \ state.scrollX);
|
||||
state.{ scrollY = newScrollY, scrollX = newScrollX };
|
||||
withScroll = result \ { state = autoScroll result.state, emit = result.emit };
|
||||
initSearch = state \
|
||||
newState = state.{
|
||||
mode = Search,
|
||||
searchQuery = "",
|
||||
savedCursorCol = state.cursorCol,
|
||||
savedCursorRow = state.cursorRow,
|
||||
};
|
||||
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
newState = state.{ mode = Search, searchQuery = "", savedCursorCol = state.cursorCol, savedCursorRow = state.cursorRow };
|
||||
{ state = newState, emit = [] };
|
||||
cancelSearch = state \
|
||||
newState = state.{
|
||||
mode = Normal,
|
||||
searchQuery = "",
|
||||
cursorCol = state.savedCursorCol,
|
||||
cursorRow = state.savedCursorRow,
|
||||
};
|
||||
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
newState = state.{ mode = Normal, searchQuery = "", cursorCol = state.savedCursorCol, cursorRow = state.savedCursorRow };
|
||||
{ state = newState, emit = [] };
|
||||
updateSearchQuery = query state \
|
||||
next = findNext query state.lines state.cursorRow state.cursorCol;
|
||||
newState = autoScroll state.{
|
||||
searchQuery = query,
|
||||
cursorCol = next.col,
|
||||
cursorRow = next.row,
|
||||
};
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
backspaceSearch = state \
|
||||
updateSearchQuery (slice state.searchQuery 0 (len state.searchQuery - 1)) state;
|
||||
|
||||
addCharToSearchQuery = char state \
|
||||
updateSearchQuery (state.searchQuery & char) state;
|
||||
|
||||
endSearch = state \
|
||||
{ state = autoScroll state.{ mode = Normal }, emit = [] };
|
||||
|
||||
next = findNext query state.lines state.cursorRow state.cursorCol;
|
||||
newState = autoScroll state.{ searchQuery = query, cursorCol = next.col, cursorRow = next.row };
|
||||
{ state = newState, emit = [] };
|
||||
backspaceSearch = state \ updateSearchQuery (slice state.searchQuery 0 ((len state.searchQuery) - 1)) state;
|
||||
addCharToSearchQuery = char state \ updateSearchQuery (state.searchQuery & char) state;
|
||||
endSearch = state \ { state = autoScroll state.{ mode = Normal }, emit = [] };
|
||||
nextSearch = state \
|
||||
next = findNext state.searchQuery state.lines state.cursorRow (state.cursorCol + 1);
|
||||
newState = autoScroll state.{
|
||||
mode = Normal,
|
||||
cursorCol = next.col,
|
||||
cursorRow = next.row,
|
||||
};
|
||||
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
next = findNext state.searchQuery state.lines state.cursorRow (state.cursorCol + 1);
|
||||
newState = autoScroll state.{ mode = Normal, cursorCol = next.col, cursorRow = next.row };
|
||||
{ state = newState, emit = [] };
|
||||
prevSearch = state \
|
||||
prev = findPrev state.searchQuery state.lines state.cursorRow state.cursorCol;
|
||||
newState = autoScroll state.{
|
||||
mode = Normal,
|
||||
cursorCol = prev.col,
|
||||
cursorRow = prev.row,
|
||||
};
|
||||
|
||||
{ state = newState, emit = [] };
|
||||
|
||||
scrollHalfUp = state ctx \ (
|
||||
diff = floor ((ctx.h / 2) / lineH);
|
||||
newRow = state.cursorRow - diff;
|
||||
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
|
||||
|
||||
scrollHalfDown = state ctx \ (
|
||||
diff = floor ((ctx.h / 2) / lineH);
|
||||
newRow = state.cursorRow + diff;
|
||||
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
|
||||
|
||||
prev = findPrev state.searchQuery state.lines state.cursorRow state.cursorCol;
|
||||
newState = autoScroll state.{ mode = Normal, cursorCol = prev.col, cursorRow = prev.row };
|
||||
{ state = newState, emit = [] };
|
||||
scrollHalfUp = state ctx \
|
||||
diff = floor ((ctx.h / 2) / lineH);
|
||||
newRow = state.cursorRow - diff;
|
||||
withScroll {
|
||||
state = clampCursor state.{ cursorRow = newRow },
|
||||
emit = []
|
||||
};
|
||||
scrollHalfDown = state ctx \
|
||||
diff = floor ((ctx.h / 2) / lineH);
|
||||
newRow = state.cursorRow + diff;
|
||||
withScroll {
|
||||
state = clampCursor state.{ cursorRow = newRow },
|
||||
emit = []
|
||||
};
|
||||
handleMotion = motion state \
|
||||
state.pending
|
||||
| None \ withScroll { state = moveCursor (resolveMotion motion state) state, emit = [] }
|
||||
| Some Delete \ withScroll (applyOperator Delete motion state);
|
||||
|
||||
state.pending
|
||||
| None \ withScroll {
|
||||
state = moveCursor (resolveMotion motion state) state,
|
||||
emit = []
|
||||
}
|
||||
| (Some Delete) \ withScroll (applyOperator Delete motion state);
|
||||
ui.stateful {
|
||||
focusable = True,
|
||||
autoFocus = True,
|
||||
|
||||
key = "textEditor-" & name,
|
||||
|
||||
init = {
|
||||
mode = Normal, # Normal | Insert | Visual | Search
|
||||
|
||||
mode = Normal,
|
||||
lines = lines,
|
||||
cursorRow = 0,
|
||||
cursorCol = 0,
|
||||
scrollX = 0,
|
||||
scrollY = 0,
|
||||
|
||||
pending = None, # Some "d" | Some "g" | etc.
|
||||
lastAction = None, # Some { operator, motion },
|
||||
pending = None,
|
||||
lastAction = None,
|
||||
undoStack = [],
|
||||
redoStack = [],
|
||||
|
||||
searchQuery = "",
|
||||
searchIndex = 0,
|
||||
searchIndex = 0
|
||||
},
|
||||
|
||||
update = state event \ state.mode
|
||||
update = state event \
|
||||
state.mode
|
||||
| Search \ (event
|
||||
| Key { key = "Escape" } \ cancelSearch state
|
||||
| Key { key = "Enter" } \ endSearch state
|
||||
| Key { key = "Backspace" } \ backspaceSearch state
|
||||
| Key { key = k, printable = True } \ addCharToSearchQuery k state
|
||||
| _ \ { state = state , emit = [] })
|
||||
| (Key {key = "Escape"}) \ cancelSearch state
|
||||
| (Key {key = "Enter"}) \ endSearch state
|
||||
| (Key {key = "Backspace"}) \ backspaceSearch state
|
||||
| (Key {key = k, printable = True}) \ addCharToSearchQuery k state
|
||||
| _ \ { state = state, emit = [] })
|
||||
| Insert \ (event
|
||||
| Key { key = "Escape" } \ escape state
|
||||
| Key { key = "Control" } \ escape state
|
||||
| Key { key = "Backspace" } \ backspace state
|
||||
| Key { key = "Enter" } \ enter state
|
||||
| Key { key = k, printable = True } \ insertChar k state
|
||||
| _ \ { state = state , emit = [] })
|
||||
| (Key {key = "Escape"}) \ escape state
|
||||
| (Key {key = "Control"}) \ escape state
|
||||
| (Key {key = "Backspace"}) \ backspace state
|
||||
| (Key {key = "Enter"}) \ enter state
|
||||
| (Key {key = k, printable = True}) \ insertChar k state
|
||||
| _ \ { state = state, emit = [] })
|
||||
| ReplaceChar \ (event
|
||||
| Key { key = k, printable = True } \ replaceChar k state
|
||||
| (Key {key = k, printable = True}) \ replaceChar k state
|
||||
| _ \ { state = state.{ mode = Normal }, emit = [] })
|
||||
| Normal \ (event
|
||||
| Scrolled delta \ (
|
||||
maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
|
||||
totalW = maxLineLen * charW * scale + maxLineLen * charGap;
|
||||
totalH = len state.lines * lineH;
|
||||
| (Scrolled delta) \ (maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
|
||||
totalW = ((maxLineLen * charW) * scale) + (maxLineLen * charGap);
|
||||
totalH = (len state.lines) * lineH;
|
||||
newX = max 0 (min (totalW - ctx.w) (state.scrollX + delta.deltaX));
|
||||
newY = max 0 (min (totalH - ctx.h) (state.scrollY + delta.deltaY));
|
||||
{ state = state.{ scrollY = newY, scrollX = newX }, emit = [] })
|
||||
|
||||
| Key { key = "ArrowDown" } \ withScroll (downArrow state)
|
||||
| Key { key = "j" } \ withScroll (downArrow state)
|
||||
| Key { key = "ArrowUp" } \ withScroll (upArrow state)
|
||||
| Key { key = "k" } \ withScroll(upArrow state)
|
||||
| Key { key = "ArrowLeft" } \ withScroll (leftArrow state)
|
||||
| Key { key = "h" } \ withScroll(leftArrow state)
|
||||
| Key { key = "ArrowRight" } \ withScroll (rightArrow state)
|
||||
| Key { key = "l" } \ withScroll(rightArrow state)
|
||||
| Key { key = "a" } \ append state
|
||||
| Key { key = "A" } \ appendEndLine state
|
||||
| Key { key = "i" } \ insertMode state
|
||||
| Key { key = "o" } \ openLine state
|
||||
| Key { key = "O" } \ openLineAbove state
|
||||
| Key { key = "d", ctrl = True } \ scrollHalfDown state ctx
|
||||
| Key { key = "u", ctrl = True } \ scrollHalfUp state ctx
|
||||
| Key { key = "u" } \ undo state
|
||||
| Key { key = "r", ctrl = True } \ redo state
|
||||
| Key { key = "r" } \ { state = state.{ mode = ReplaceChar }, emit = [] }
|
||||
| Key { key = "x" } \ applyOperator Delete Cursor state
|
||||
| Key { key = "d" } \ (state.pending
|
||||
| Some Delete \ applyOperator Delete WholeLine state
|
||||
{
|
||||
state = state.{ scrollY = newY, scrollX = newX },
|
||||
emit = []
|
||||
})
|
||||
| (Key {key = "W", ctrl = True, shift = True}) \ write state
|
||||
| (Key {key = "A", ctrl = True, shift = True}) \ apply state
|
||||
| (Key {key = "ArrowDown"}) \ withScroll (downArrow state)
|
||||
| (Key {key = "j"}) \ withScroll (downArrow state)
|
||||
| (Key {key = "ArrowUp"}) \ withScroll (upArrow state)
|
||||
| (Key {key = "k"}) \ withScroll (upArrow state)
|
||||
| (Key {key = "ArrowLeft"}) \ withScroll (leftArrow state)
|
||||
| (Key {key = "h"}) \ withScroll (leftArrow state)
|
||||
| (Key {key = "ArrowRight"}) \ withScroll (rightArrow state)
|
||||
| (Key {key = "l"}) \ withScroll (rightArrow state)
|
||||
| (Key {key = "a"}) \ append state
|
||||
| (Key {key = "A"}) \ appendEndLine state
|
||||
| (Key {key = "i"}) \ insertMode state
|
||||
| (Key {key = "o"}) \ openLine state
|
||||
| (Key {key = "O"}) \ openLineAbove state
|
||||
| (Key {key = "d", ctrl = True}) \ scrollHalfDown state ctx
|
||||
| (Key {key = "u", ctrl = True}) \ scrollHalfUp state ctx
|
||||
| (Key {key = "u"}) \ undo state
|
||||
| (Key {key = "r", ctrl = True}) \ redo state
|
||||
| (Key {key = "r"}) \ { state = state.{ mode = ReplaceChar }, emit = [] }
|
||||
| (Key {key = "x"}) \ applyOperator Delete Cursor state
|
||||
| (Key {key = "d"}) \ (state.pending
|
||||
| (Some Delete) \ applyOperator Delete WholeLine state
|
||||
| _ \ { state = state.{ pending = Some Delete }, emit = [] })
|
||||
| Key { key = "b" } \ handleMotion BackWord state
|
||||
| Key { key = "w" } \ handleMotion Word state
|
||||
| Key { key = "0" } \ handleMotion StartOfLine state
|
||||
| Key { key = "^" } \ handleMotion StartOfLineChars state
|
||||
| Key { key = "$" } \ handleMotion EndOfLine state
|
||||
| Key { key = "g" } \ (state.pending
|
||||
| Some "g" \ handleMotion FirstLine state.{ pending = None }
|
||||
| (Key {key = "b"}) \ handleMotion BackWord state
|
||||
| (Key {key = "w"}) \ handleMotion Word state
|
||||
| (Key {key = "0"}) \ handleMotion StartOfLine state
|
||||
| (Key {key = "^"}) \ handleMotion StartOfLineChars state
|
||||
| (Key {key = "$"}) \ handleMotion EndOfLine state
|
||||
| (Key {key = "g"}) \ (state.pending
|
||||
| (Some "g") \ handleMotion FirstLine state.{ pending = None }
|
||||
| _ \ { state = state.{ pending = Some "g" }, emit = [] })
|
||||
| Key { key = "G" } \ handleMotion LastLine state
|
||||
| Key { key = "." } \ (state.lastAction
|
||||
| (Key {key = "G"}) \ handleMotion LastLine state
|
||||
| (Key {key = "."}) \ (state.lastAction
|
||||
| None \ { state = state, emit = [] }
|
||||
| Some action \ applyOperator action.operator action.motion state)
|
||||
# Search
|
||||
| Key { key = "/" } \ initSearch state
|
||||
| Key { key = "n" } \ nextSearch state
|
||||
| Key { key = "N" } \ prevSearch state
|
||||
|
||||
| Key { key = "W", ctrl = True, shift = True } \ write state
|
||||
| Key { key = "A", ctrl = True, shift = True } \ apply state
|
||||
|
||||
| Key { key = "Escape" } \ escape state
|
||||
# any other key or event
|
||||
| _ \ { state = state, emit = [] }
|
||||
),
|
||||
|
||||
| (Some action) \ applyOperator action.operator action.motion state)
|
||||
| (Key {key = "/"}) \ initSearch state
|
||||
| (Key {key = "n"}) \ nextSearch state
|
||||
| (Key {key = "N"}) \ prevSearch state
|
||||
| (Key {key = "Escape"}) \ escape state
|
||||
| _ \ { state = state, emit = [] }),
|
||||
view = state emit \
|
||||
cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
|
||||
cursorX = ((state.cursorCol * charW) * scale) + (charGap * state.cursorCol);
|
||||
cursorY = state.cursorRow * lineH;
|
||||
|
||||
cursor = ui.positioned {
|
||||
x = cursorX,
|
||||
y = cursorY,
|
||||
child = ui.rect { w = charW * scale, h = charH * scale, color = "rgba(255,255,255,0.5)" }
|
||||
};
|
||||
|
||||
x = cursorX,
|
||||
y = cursorY,
|
||||
child = ui.rect {
|
||||
w = charW * scale,
|
||||
h = charH * scale,
|
||||
color = "rgba(255,255,255,0.5)"
|
||||
}
|
||||
};
|
||||
maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
|
||||
totalWidth = maxLineLen * charW * scale + maxLineLen * charGap;
|
||||
totalHeight = len state.lines * lineH;
|
||||
|
||||
totalWidth = ((maxLineLen * charW) * scale) + (maxLineLen * charGap);
|
||||
totalHeight = (len state.lines) * lineH;
|
||||
highlights = state.searchQuery == ""
|
||||
| True \ []
|
||||
| False \ findAll state.searchQuery state.lines;
|
||||
|
||||
highlightsUi = map (c \
|
||||
ui.positioned {
|
||||
x = c.col * charW * scale + charGap * c.col,
|
||||
y = c.row * lineH,
|
||||
child = ui.rect {
|
||||
w = (len state.searchQuery) * charW * scale + charGap * (len state.searchQuery - 1),
|
||||
h = charH * scale,
|
||||
color = "rgba(255,200,0,0.5)"
|
||||
}
|
||||
}
|
||||
) highlights;
|
||||
|
||||
# buffer = map (l \ text l) state.lines;
|
||||
# only render visible lines
|
||||
highlightsUi = map (c \ ui.positioned {
|
||||
x = ((c.col * charW) * scale) + (charGap * c.col),
|
||||
y = c.row * lineH,
|
||||
child = ui.rect {
|
||||
w = (((len state.searchQuery) * charW) * scale) + (charGap * ((len state.searchQuery) - 1)),
|
||||
h = charH * scale,
|
||||
color = "rgba(255,200,0,0.5)"
|
||||
}
|
||||
}) highlights;
|
||||
startLine = max 0 (floor (state.scrollY / lineH));
|
||||
visibleCount = floor (ctx.h / lineH) + 2;
|
||||
visibleCount = (floor (ctx.h / lineH)) + 2;
|
||||
endLine = min (len state.lines) (startLine + visibleCount);
|
||||
visibleLines = slice state.lines startLine endLine;
|
||||
buffer = mapWithIndex (l i \
|
||||
ui.positioned {
|
||||
x = 0,
|
||||
y = (startLine + i) * lineH,
|
||||
child = text l
|
||||
}
|
||||
) visibleLines;
|
||||
|
||||
buffer = mapWithIndex (l i \ ui.positioned { x = 0, y = (startLine + i) * lineH, child = text l }) visibleLines;
|
||||
scrollable {
|
||||
w = ctx.w,
|
||||
h = ctx.h,
|
||||
|
|
@ -546,14 +469,8 @@ textEditor = name \
|
|||
scrollX = state.scrollX,
|
||||
scrollY = state.scrollY,
|
||||
onScroll = delta \ emit (Scrolled delta),
|
||||
child = ui.stack {
|
||||
children = [
|
||||
...buffer,
|
||||
cursor,
|
||||
...highlightsUi
|
||||
]
|
||||
}
|
||||
child = ui.stack { children = [...buffer, cursor, ...highlightsUi] }
|
||||
}
|
||||
}
|
||||
};
|
||||
} };
|
||||
|
||||
@
|
||||
|
|
|
|||
|
|
@ -169,6 +169,46 @@ export const _rt = {
|
|||
});
|
||||
}
|
||||
},
|
||||
"applyModule!": (moduleName: string) => (code: string) => {
|
||||
try {
|
||||
const tokens = tokenize(code);
|
||||
const parser = new Parser(tokens, code);
|
||||
const { definitions: defs } = parser.parse();
|
||||
|
||||
// Find existing module defs
|
||||
const existingNames = new Set(
|
||||
[...definitions.values()]
|
||||
.filter(d => d.module === moduleName)
|
||||
.map(d => d.name)
|
||||
);
|
||||
|
||||
// Apply new defs
|
||||
const newNames = new Set<string>();
|
||||
for (const def of defs) {
|
||||
def.module = moduleName;
|
||||
if (def.body) {
|
||||
recompile(def.name, def.body);
|
||||
definitions.set(def.name, def);
|
||||
newNames.add(def.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete missing defs
|
||||
for (const name of existingNames) {
|
||||
if (!newNames.has(name)) {
|
||||
delete store[name];
|
||||
definitions.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Sync to disk
|
||||
syncToFilesystem([...newNames][0] || moduleName);
|
||||
|
||||
return { _tag: 'Defined', _0: moduleName };
|
||||
} catch (e: any) {
|
||||
return { _tag: 'Err', _0: e.message };
|
||||
}
|
||||
},
|
||||
"saveModule!": (moduleName: string) => {
|
||||
const moduleDefs = [...definitions.values()]
|
||||
.filter(d => d.module === moduleName);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue