Compare commits

..

No commits in common. "99ee0da8c12b449ed1e12128e64c7fe29b857680" and "88098a9ce0688b8dfcac8b5869523a309824f6d4" have entirely different histories.

2 changed files with 467 additions and 424 deletions

View file

@ -1,77 +1,98 @@
@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 = unwrapOr "" (nth state.cursorRow state.lines);
line = nth state.cursorRow state.lines ~ unwrapOr "";
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
| Normal \ max 0 ((len line) - 1);
| Normal \ max 0 (len line - 1);
newCol2 = min maxCol (max 0 state.cursorCol);
state.{ cursorRow = newRow2, cursorCol = newCol2 };
charKind = ch \
ch == " "
| True \ Space
ch == " " | True \ Space
| False \ (isWordChar ch
| True \ Word
| False \ Punct);
skipWhile = pred lines row col \
line = unwrapOr "" (nth row lines);
col >= (len line)
| True \ ((row + 1) < (len lines)
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 (unwrapOr "" (nth col line))
| False \ (pred (nth col line ~ unwrapOr "")
| True \ skipWhile pred lines row (col + 1)
| False \ { row = row, col = col });
nextWordStart = lines row col \
line = unwrapOr "" (nth row lines);
kind = charKind (unwrapOr " " (nth col line));
pos = skipWhile (ch \ (charKind ch) == kind) 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;
skipBack = pred line row col lines \
col < 0
| True \ (row > 0
| True \ (prevLine = unwrapOr "" (nth (row - 1) lines);
skipBack pred prevLine (row - 1) ((len prevLine) - 1) lines)
| True \ (
prevLine = nth (row - 1) lines ~ unwrapOr "";
skipBack pred prevLine (row - 1) (len prevLine - 1) lines)
| 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
| 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 (unwrapOr "" (nth (r - 1) lines))) - 1)
| True \ (max 0 (len (nth (r - 1) lines ~ unwrapOr "") - 1))
| False \ c;
# no prev line
r2 < 0
| True \ { row = 0, col = 0 }
| False \ (line = unwrapOr "" (nth r2 lines);
| False \ (
line = nth r2 lines ~ unwrapOr "";
# skip spaces backward
pos = skipBack (ch \ ch == " ") line r2 c2 lines;
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 });
# 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 }
);
resolveMotion = motion state \
from = { row = state.cursorRow, col = state.cursorCol };
to = motion
@ -80,387 +101,443 @@ textEditor = name \
| 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 })
| 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 };
deleteLine = state \
newLines = [
...take state.cursorRow state.lines,
...drop (state.cursorRow + 1) state.lines
];
newLines = [...(take (state.cursorRow) state.lines), ...(drop (state.cursorRow + 1) state.lines)];
{ state = state.{ lines = newLines }, emit = [] };
deleteRange = state from to \
line = unwrapOr "" (nth from.row state.lines);
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 };
applyOperator = operator motion state \
target = resolveMotion motion state;
action = { operator = operator, motion = motion };
stateSnapshot = {
lines = state.lines,
cursorRow = state.cursorRow,
cursorCol = state.cursorCol
};
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 };
| 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]
};
{ state = state, emit = [rebindAt [buffersKey, name] content] };
apply = state \
content = join "\n" state.lines;
isModule = getModuleSource name
| (Some _) \ True
| None \ False;
result = isModule
| True \ applyModule! name content
| False \ eval! content;
_ = debug! "apply" result;
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 = [] })
| 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
};
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 = []
};
{ state = state.{ lines = newLines, mode = Normal, undoStack = newUndoStack }, emit = [] };
insertMode = state \
stateSnapshot = {
lines = state.lines,
cursorRow = state.cursorRow,
cursorCol = state.cursorCol
};
stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
newUndoStack = [stateSnapshot, ...state.undoStack];
{
state = state.{ undoStack = newUndoStack, mode = Insert },
state = state.{
undoStack = newUndoStack,
mode = Insert
},
emit = []
};
append = state \ insertMode state.{ cursorCol = state.cursorCol + 1 };
appendEndLine = state \
line = unwrapOr "" (nth state.cursorRow state.lines);
line = nth state.cursorRow state.lines ~ unwrapOr "";
insertMode state.{ cursorCol = len line };
openLine = state \
stateSnapshot = {
lines = state.lines,
cursorRow = state.cursorRow,
cursorCol = state.cursorCol
};
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 },
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
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
| [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 = []
});
| [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.cursorRow == 0
| True \ { state = state, emit = [] }
| False \ { state = state, emit = [] })
| False \ (newLines = updateAt state.cursorRow (line \
| 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 = []
});
{ state = state.{ lines = newLines, cursorCol = state.cursorCol - 1 }, emit = [] });
enter = state \
line = unwrapOr "" (nth state.cursorRow state.lines);
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 = []
};
{ state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 }, emit = [] };
upArrow = state \
newState = clampCursor state.{ cursorRow = state.cursorRow - 1 };
{ state = newState, emit = [] };
downArrow = state \
newState = clampCursor state.{ cursorRow = state.cursorRow + 1 };
{ state = newState, emit = [] };
leftArrow = state \
newState = clampCursor state.{ cursorCol = state.cursorCol - 1 };
{ state = newState, emit = [] };
rightArrow = state \
newState = clampCursor state.{ cursorCol = state.cursorCol + 1 };
{ state = newState, emit = [] };
findNext = query lines row col \
rest = strIndexOf query (unwrapOr "" (nth row lines)) 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
| Some c \ { row = row, col = c }
| None \ (
findInLines = r \
r >= len lines
| True \ findInLines 0 # wrap to top
| 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 }
| 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));
findPrev = query lines row col \
line = slice (unwrapOr "" (nth row lines)) 0 col;
line = slice (nth row lines ~ unwrapOr "") 0 col;
rest = strLastIndexOf query line;
rest
| (Some c) \ { row = row, col = c }
| None \ (findInLines = r \
| Some c \ { row = row, col = c }
| None \ (
findInLines = r \
r < 0
| True \ findInLines ((len lines) - 1)
| True \ findInLines (len lines - 1) # wrap to bottom
| 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 }
| 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 \
{
view = ctx \
autoScroll = state \
cursorY = state.cursorRow * lineH;
newScrollY = cursorY < state.scrollY
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
| 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);
| 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 };
withScroll = result \
{ state = autoScroll result.state, emit = result.emit };
# search stuff
initSearch = state \
newState = state.{ mode = Search, searchQuery = "", savedCursorCol = state.cursorCol, savedCursorRow = state.cursorRow };
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 };
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 };
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 = [] };
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 };
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 };
newState = autoScroll state.{
mode = Normal,
cursorCol = prev.col,
cursorRow = prev.row,
};
{ state = newState, emit = [] };
scrollHalfUp = state ctx \
scrollHalfUp = state ctx \ (
diff = floor ((ctx.h / 2) / lineH);
newRow = state.cursorRow - diff;
withScroll {
state = clampCursor state.{ cursorRow = newRow },
emit = []
};
scrollHalfDown = state ctx \
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 = []
};
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);
| 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,
mode = Normal, # Normal | Insert | Visual | Search
lines = lines,
cursorRow = 0,
cursorCol = 0,
scrollX = 0,
scrollY = 0,
pending = None,
lastAction = None,
pending = None, # Some "d" | Some "g" | etc.
lastAction = None, # Some { operator, motion },
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
| 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
| 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 = "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.{ 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.{ 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)
| (Key {key = "/"}) \ initSearch state
| (Key {key = "n"}) \ nextSearch state
| (Key {key = "N"}) \ prevSearch state
| (Key {key = "Escape"}) \ escape state
| _ \ { 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 = [] }
),
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)"
}
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),
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)),
w = (len state.searchQuery) * charW * scale + charGap * (len state.searchQuery - 1),
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));
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,
@ -469,8 +546,14 @@ 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
]
}
} };
}
}
};
@

View file

@ -169,46 +169,6 @@ 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);