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,466 +1,543 @@
@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 = 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; newRow = max 0 state.cursorRow;
maxCol = state.mode newRow2 = min (len state.lines - 1) newRow;
| Insert \ len line
| Normal \ max 0 ((len line) - 1); maxCol = state.mode
newCol2 = min maxCol (max 0 state.cursorCol); | Insert \ len line
state.{ cursorRow = newRow2, cursorCol = newCol2 }; | Normal \ max 0 (len line - 1);
newCol2 = min maxCol (max 0 state.cursorCol);
state.{ cursorRow = newRow2, cursorCol = newCol2 };
charKind = ch \ charKind = ch \
ch == " " ch == " " | True \ Space
| True \ Space | False \ (isWordChar ch
| False \ (isWordChar ch | True \ Word
| True \ Word | False \ Punct);
| False \ Punct);
skipWhile = pred lines row col \ skipWhile = pred lines row col \
line = unwrapOr "" (nth row lines); line = nth row lines ~ unwrapOr "";
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 (unwrapOr "" (nth col line)) | False \ (pred (nth col line ~ unwrapOr "")
| 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 = unwrapOr "" (nth row lines); line = nth row lines ~ unwrapOr "";
kind = charKind (unwrapOr " " (nth col line)); kind = charKind (nth col line ~ unwrapOr " ");
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 \ (prevLine = unwrapOr "" (nth (row - 1) lines); | True \ (
skipBack pred prevLine (row - 1) ((len prevLine) - 1) lines) prevLine = nth (row - 1) lines ~ unwrapOr "";
| False \ { row = 0, col = 0 - 1 }) skipBack pred prevLine (row - 1) (len prevLine - 1) lines)
| False \ (pred (unwrapOr "" (nth col line)) | False \ { row = 0, col = 0 - 1 })
| True \ skipBack pred line row (col - 1) lines | False \ (pred (nth col line ~ unwrapOr "")
| False \ { row = row, col = col }); | True \ skipBack pred line row (col - 1) lines
| False \ { row = row, col = col });
prevWordStart = lines row col \ prevWordStart = lines row col \
c = col - 1; # step back 1
r = row; c = col - 1;
r2 = c < 0 r = row;
| True \ r - 1 r2 = c < 0
| False \ r; | True \ r - 1
c2 = c < 0 | False \ r;
| True \ max 0 ((len (unwrapOr "" (nth (r - 1) lines))) - 1) c2 = c < 0
| False \ c; | True \ (max 0 (len (nth (r - 1) lines ~ unwrapOr "") - 1))
r2 < 0 | False \ c;
# no prev line
r2 < 0
| True \ { row = 0, col = 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; pos = skipBack (ch \ ch == " ") line r2 c2 lines;
kind = charKind (unwrapOr " " (nth pos.col (unwrapOr "" (nth pos.row lines)))); # skip same kind backward
pos2 = skipBack (ch \ (charKind ch) == kind) (unwrapOr "" (nth pos.row lines)) pos.row (pos.col - 1) lines; kind = charKind (nth pos.col (nth pos.row lines ~ unwrapOr "") ~ unwrapOr " ");
{ row = pos2.row, col = pos2.col + 1 }); 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 \ 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 \ (line = unwrapOr "" (nth state.cursorRow state.lines); | EndOfLine \ (
{ row = state.cursorRow, col = len line }) line = nth state.cursorRow state.lines ~ unwrapOr "";
| FirstLine \ (line = unwrapOr "" (nth 0 state.lines); { row = state.cursorRow, col = (len line) })
{ row = 0, col = (len line) - 1 }) | FirstLine \ (
| LastLine \ (lastRow = (len state.lines) - 1; line = nth 0 state.lines ~ unwrapOr "";
line = unwrapOr "" (nth lastRow state.lines); { row = 0, col = len line - 1 })
{ row = lastRow, col = (len line) - 1 }) | LastLine \ (
| Cursor \ { row = state.cursorRow, col = state.cursorCol + 1 }; lastRow = len state.lines - 1;
{ from = from, to = to }; 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 \ deleteLine = state \
newLines = [ newLines = [...(take (state.cursorRow) state.lines), ...(drop (state.cursorRow + 1) state.lines)];
...take state.cursorRow state.lines, { state = state.{ lines = newLines }, emit = [] };
...drop (state.cursorRow + 1) state.lines
];
{ state = state.{ lines = newLines }, emit = [] };
deleteRange = state from to \ deleteRange = state from to \
line = unwrapOr "" (nth from.row state.lines); line = nth from.row state.lines ~ unwrapOr "";
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 = { stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
lines = state.lines, newState = operator
cursorRow = state.cursorRow, | Delete \ (hasField "linewise" target.to
cursorCol = state.cursorCol | True \ deleteLine state
}; | False \ { state = deleteRange state target.from target.to, emit = [] });
newState = operator { state = clampCursor newState.state.{ undoStack = [stateSnapshot, ...state.undoStack], pending = None, lastAction = Some action }, emit = newState.emit };
| Delete \ (hasField "linewise" target.to
| True \ deleteLine state moveCursor = motion state \
| False \ { state.{ cursorCol = motion.to.col, cursorRow = motion.to.row };
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);
isModule = getModuleSource name result = eval! content;
| (Some _) \ True _ = debug! "apply" [content, result];
| None \ False; result
result = isModule | Defined _ \ { state = state, emit = [] }
| True \ applyModule! name content | Err msg \ (
| False \ eval! content; _ = debug! "error applying" [];
_ = debug! "apply" result; { state = state, emit = [] }
result )
| (Defined _) \ { state = state, emit = [] } | _ \ { 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 \ insertCharAt line state.cursorCol char) state.lines; newLines = updateAt state.cursorRow (line \
{ insertCharAt line state.cursorCol char
state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 }, ) state.lines;
emit = []
}; { state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 }, emit = [] };
replaceChar = char state \ replaceChar = char state \
newLines = updateAt state.cursorRow (line \ newLines = updateAt state.cursorRow (line \
newLine = deleteCharAt line state.cursorCol; newLine = deleteCharAt line state.cursorCol;
insertCharAt newLine state.cursorCol char) state.lines; insertCharAt newLine state.cursorCol char
stateSnapshot = { ) state.lines;
lines = state.lines,
cursorRow = state.cursorRow, stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
cursorCol = state.cursorCol newUndoStack = [stateSnapshot, ...state.undoStack];
};
newUndoStack = [stateSnapshot, ...state.undoStack]; { state = state.{ lines = newLines, mode = Normal, undoStack = newUndoStack }, emit = [] };
{
state = state.{ lines = newLines, mode = Normal, undoStack = newUndoStack },
emit = []
};
insertMode = state \ insertMode = state \
stateSnapshot = { stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
lines = state.lines, newUndoStack = [stateSnapshot, ...state.undoStack];
cursorRow = state.cursorRow, {
cursorCol = state.cursorCol state = state.{
}; undoStack = newUndoStack,
newUndoStack = [stateSnapshot, ...state.undoStack]; mode = Insert
{ },
state = state.{ undoStack = newUndoStack, mode = Insert }, emit = []
emit = [] };
};
append = state \ insertMode state.{ cursorCol = state.cursorCol + 1 }; append = state \ insertMode state.{ cursorCol = state.cursorCol + 1 };
appendEndLine = state \ appendEndLine = state \
line = unwrapOr "" (nth state.cursorRow state.lines); line = nth state.cursorRow state.lines ~ unwrapOr "";
insertMode state.{ cursorCol = len line }; insertMode state.{ cursorCol = len line };
openLine = state \ openLine = state \
stateSnapshot = { stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
lines = state.lines, newUndoStack = [stateSnapshot, ...state.undoStack];
cursorRow = state.cursorRow, cursorRow = state.cursorRow + 1;
cursorCol = state.cursorCol newLines = insertAt cursorRow "" state.lines;
}; {
newUndoStack = [stateSnapshot, ...state.undoStack]; state = state.{
cursorRow = state.cursorRow + 1; lines = newLines,
newLines = insertAt cursorRow "" state.lines; cursorRow = cursorRow,
{ cursorCol = 0,
state = state.{ lines = newLines, cursorRow = cursorRow, cursorCol = 0, undoStack = newUndoStack, mode = Insert }, undoStack = newUndoStack,
emit = [] mode = Insert
}; },
emit = []
};
openLineAbove = state \ openLine state.{ cursorRow = state.cursorRow - 1 }; openLineAbove = state \ openLine state.{ cursorRow = state.cursorRow - 1 };
escape = state \ {
state = state.{ mode = Normal, pending = None }, escape = state \ { state = state.{ mode = Normal, pending = None }, emit = [] };
emit = []
}; undo = state \ state.undoStack
undo = state \ | [] \ { state = state, emit = [] }
state.undoStack | [prev, ...rest] \ (
| [] \ { state = state, emit = [] } stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
| [prev, ...rest] \ (stateSnapshot = {
lines = state.lines, { state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = rest, redoStack = [stateSnapshot, ...state.redoStack] }, emit = [] });
cursorRow = state.cursorRow,
cursorCol = state.cursorCol redo = state \ state.redoStack
}; | [] \ { state = state, emit = [] }
{ | [prev, ...rest] \ (
state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = rest, redoStack = [stateSnapshot, ...state.redoStack] }, stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol };
emit = []
}); { state = state.{ lines = prev.lines, cursorCol = prev.cursorCol, cursorRow = prev.cursorRow, undoStack = [stateSnapshot, ...state.undoStack], redoStack = rest }, 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 \ backspace = state \
state.cursorCol == 0 state.cursorCol == 0
| True \ (state.cursorRow == 0 | True \ (
| True \ { state = state, emit = [] } state.cursorRow == 0
| False \ { state = state, emit = [] }) | True \ { state = state, emit = [] }
| False \ (newLines = updateAt state.cursorRow (line \ | False \ (
before = slice line 0 (state.cursorCol - 1); { state = state, emit = [] } # todo, join with previous line
after = slice line state.cursorCol (len line); )
before & after) state.lines; )
{ | False \ (
state = state.{ lines = newLines, cursorCol = state.cursorCol - 1 }, newLines = updateAt state.cursorRow (line \
emit = [] 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 = unwrapOr "" (nth state.cursorRow state.lines); line = nth state.cursorRow state.lines ~ unwrapOr "";
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;
newLines2 = insertAt (state.cursorRow + 1) after newLines; 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 \ 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 (unwrapOr "" (nth row lines)) col; rest = strIndexOf query (nth row lines ~ unwrapOr "") col;
rest rest
| (Some c) \ { row = row, col = c } | Some c \ { row = row, col = c }
| None \ (findInLines = r \ | None \ (
r >= (len lines) findInLines = r \
| True \ findInLines 0 r >= len lines
| False \ (r == row | True \ findInLines 0 # wrap to top
| True \ (strIndexOf query (unwrapOr "" (nth row lines)) 0 | False \ (r == row
| (Some c) \ { row = row, col = c } | True \ (strIndexOf query (nth row lines ~ unwrapOr "") 0
| None \ { row = row, col = col }) | Some c \ { row = row, col = c }
| False \ (strIndexOf query (unwrapOr "" (nth r lines)) 0 | None \ { row = row, col = col }) # no match
| (Some c) \ { row = r, col = c } | False \ (strIndexOf query (nth r lines ~ unwrapOr "") 0
| None \ findInLines (r + 1))); | Some c \ { row = r, col = c }
findInLines (row + 1)); | None \ findInLines (r + 1)));
findInLines (row + 1));
findPrev = query lines row col \ 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 = strLastIndexOf query line;
rest rest
| (Some c) \ { row = row, col = c } | Some c \ { row = row, col = c }
| None \ (findInLines = r \ | None \ (
r < 0 findInLines = r \
| True \ findInLines ((len lines) - 1) r < 0
| False \ (r == row | True \ findInLines (len lines - 1) # wrap to bottom
| True \ (strLastIndexOf query (unwrapOr "" (nth row lines)) | False \ (r == row
| (Some c) \ { row = row, col = c } | True \ (strLastIndexOf query (nth row lines ~ unwrapOr "")
| None \ { row = row, col = col }) | Some c \ { row = row, col = c }
| False \ (strLastIndexOf query (unwrapOr "" (nth r lines)) | None \ { row = row, col = col }) # no match
| (Some c) \ { row = r, col = c } | False \ (strLastIndexOf query (nth r lines ~ unwrapOr "")
| None \ findInLines (r - 1))); | Some c \ { row = r, col = c }
findInLines (row - 1)); | None \ findInLines (r - 1)));
{ view = ctx \ findInLines (row - 1));
{
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);
newScrollX = cursorX < state.scrollX cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol;
| True \ cursorX newScrollX = (cursorX < state.scrollX
| False \ ((cursorX + (charW * scale)) > (state.scrollX + ctx.w) | True \ cursorX
| True \ (cursorX + (charW * scale)) - ctx.w | False \ (cursorX + charW * scale > state.scrollX + ctx.w
| False \ state.scrollX); | True \ cursorX + charW * scale - ctx.w
state.{ scrollY = newScrollY, scrollX = newScrollX }; | False \ state.scrollX));
withScroll = result \ { state = autoScroll result.state, emit = result.emit }; state.{ scrollY = newScrollY, scrollX = newScrollX };
withScroll = result \
{ state = autoScroll result.state, emit = result.emit };
# search stuff
initSearch = state \ initSearch = state \
newState = state.{ mode = Search, searchQuery = "", savedCursorCol = state.cursorCol, savedCursorRow = state.cursorRow }; newState = state.{
{ state = newState, emit = [] }; mode = Search,
searchQuery = "",
savedCursorCol = state.cursorCol,
savedCursorRow = state.cursorRow,
};
{ state = newState, emit = [] };
cancelSearch = state \ cancelSearch = state \
newState = state.{ mode = Normal, searchQuery = "", cursorCol = state.savedCursorCol, cursorRow = state.savedCursorRow }; newState = state.{
{ state = newState, emit = [] }; mode = Normal,
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.{ searchQuery = query, cursorCol = next.col, cursorRow = next.row }; newState = autoScroll state.{
{ state = newState, emit = [] }; searchQuery = query,
backspaceSearch = state \ updateSearchQuery (slice state.searchQuery 0 ((len state.searchQuery) - 1)) state; cursorCol = next.col,
addCharToSearchQuery = char state \ updateSearchQuery (state.searchQuery & char) state; cursorRow = next.row,
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.{ mode = Normal, cursorCol = next.col, cursorRow = next.row }; newState = autoScroll state.{
{ state = newState, emit = [] }; mode = Normal,
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.{ mode = Normal, cursorCol = prev.col, cursorRow = prev.row }; newState = autoScroll state.{
{ state = newState, emit = [] }; mode = Normal,
scrollHalfUp = state ctx \ cursorCol = prev.col,
diff = floor ((ctx.h / 2) / lineH); cursorRow = prev.row,
newRow = state.cursorRow - diff; };
withScroll {
state = clampCursor state.{ cursorRow = newRow }, { state = newState, emit = [] };
emit = []
}; scrollHalfUp = state ctx \ (
scrollHalfDown = state ctx \ diff = floor ((ctx.h / 2) / lineH);
diff = floor ((ctx.h / 2) / lineH); newRow = state.cursorRow - diff;
newRow = state.cursorRow + diff; withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
withScroll {
state = clampCursor state.{ cursorRow = newRow }, scrollHalfDown = state ctx \ (
emit = [] diff = floor ((ctx.h / 2) / lineH);
}; newRow = state.cursorRow + diff;
withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] });
handleMotion = motion state \ handleMotion = motion state \
state.pending state.pending
| None \ withScroll { | None \ withScroll { state = moveCursor (resolveMotion motion state) state, emit = [] }
state = moveCursor (resolveMotion motion state) state, | Some Delete \ withScroll (applyOperator Delete motion 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, mode = Normal, # Normal | Insert | Visual | Search
lines = lines, lines = lines,
cursorRow = 0, cursorRow = 0,
cursorCol = 0, cursorCol = 0,
scrollX = 0, scrollX = 0,
scrollY = 0, scrollY = 0,
pending = None,
lastAction = None, pending = None, # Some "d" | Some "g" | etc.
lastAction = None, # Some { operator, motion },
undoStack = [], undoStack = [],
redoStack = [], redoStack = [],
searchQuery = "", searchQuery = "",
searchIndex = 0 searchIndex = 0,
}, },
update = state event \
state.mode update = state event \ 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) \ (maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines; | Scrolled delta \ (
totalW = ((maxLineLen * charW) * scale) + (maxLineLen * charGap); maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines;
totalH = (len state.lines) * lineH; totalW = maxLineLen * charW * scale + maxLineLen * charGap;
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 },
emit = [] | Key { key = "ArrowDown" } \ withScroll (downArrow state)
}) | Key { key = "j" } \ withScroll (downArrow state)
| (Key {key = "W", ctrl = True, shift = True}) \ write state | Key { key = "ArrowUp" } \ withScroll (upArrow state)
| (Key {key = "A", ctrl = True, shift = True}) \ apply state | Key { key = "k" } \ withScroll(upArrow state)
| (Key {key = "ArrowDown"}) \ withScroll (downArrow state) | Key { key = "ArrowLeft" } \ withScroll (leftArrow state)
| (Key {key = "j"}) \ withScroll (downArrow state) | Key { key = "h" } \ withScroll(leftArrow state)
| (Key {key = "ArrowUp"}) \ withScroll (upArrow state) | Key { key = "ArrowRight" } \ withScroll (rightArrow state)
| (Key {key = "k"}) \ withScroll (upArrow state) | Key { key = "l" } \ withScroll(rightArrow state)
| (Key {key = "ArrowLeft"}) \ withScroll (leftArrow state) | Key { key = "a" } \ append state
| (Key {key = "h"}) \ withScroll (leftArrow state) | Key { key = "A" } \ appendEndLine state
| (Key {key = "ArrowRight"}) \ withScroll (rightArrow state) | Key { key = "i" } \ insertMode state
| (Key {key = "l"}) \ withScroll (rightArrow state) | Key { key = "o" } \ openLine state
| (Key {key = "a"}) \ append state | Key { key = "O" } \ openLineAbove state
| (Key {key = "A"}) \ appendEndLine state | Key { key = "d", ctrl = True } \ scrollHalfDown state ctx
| (Key {key = "i"}) \ insertMode state | Key { key = "u", ctrl = True } \ scrollHalfUp state ctx
| (Key {key = "o"}) \ openLine state | Key { key = "u" } \ undo state
| (Key {key = "O"}) \ openLineAbove state | Key { key = "r", ctrl = True } \ redo state
| (Key {key = "d", ctrl = True}) \ scrollHalfDown state ctx | Key { key = "r" } \ { state = state.{ mode = ReplaceChar }, emit = [] }
| (Key {key = "u", ctrl = True}) \ scrollHalfUp state ctx | Key { key = "x" } \ applyOperator Delete Cursor state
| (Key {key = "u"}) \ undo state | Key { key = "d" } \ (state.pending
| (Key {key = "r", ctrl = True}) \ redo state | Some Delete \ applyOperator Delete WholeLine 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)
| (Key {key = "/"}) \ initSearch state # Search
| (Key {key = "n"}) \ nextSearch state | Key { key = "/" } \ initSearch state
| (Key {key = "N"}) \ prevSearch state | Key { key = "n" } \ nextSearch state
| (Key {key = "Escape"}) \ escape state | Key { key = "N" } \ prevSearch 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 { child = ui.rect { w = charW * scale, h = charH * scale, color = "rgba(255,255,255,0.5)" }
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 {
x = ((c.col * charW) * scale) + (charGap * c.col), highlightsUi = map (c \
y = c.row * lineH, ui.positioned {
child = ui.rect { x = c.col * charW * scale + charGap * c.col,
w = (((len state.searchQuery) * charW) * scale) + (charGap * ((len state.searchQuery) - 1)), y = c.row * lineH,
h = charH * scale, child = ui.rect {
color = "rgba(255,200,0,0.5)" w = (len state.searchQuery) * charW * scale + charGap * (len state.searchQuery - 1),
} h = charH * scale,
}) highlights; color = "rgba(255,200,0,0.5)"
}
}
) 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 \ 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 { scrollable {
w = ctx.w, w = ctx.w,
h = ctx.h, h = ctx.h,
@ -469,8 +546,14 @@ 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 { 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) => { "saveModule!": (moduleName: string) => {
const moduleDefs = [...definitions.values()] const moduleDefs = [...definitions.values()]
.filter(d => d.module === moduleName); .filter(d => d.module === moduleName);