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