diff --git a/src/cg/textEditor.cg b/src/cg/textEditor.cg index 2431d4e..6c221cc 100644 --- a/src/cg/textEditor.cg +++ b/src/cg/textEditor.cg @@ -1,466 +1,543 @@ @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); - newRow = max 0 state.cursorRow; - newRow2 = min ((len state.lines) - 1) newRow; - maxCol = state.mode - | Insert \ len line - | Normal \ max 0 ((len line) - 1); - newCol2 = min maxCol (max 0 state.cursorCol); - state.{ cursorRow = newRow2, cursorCol = newCol2 }; + line = nth state.cursorRow state.lines ~ unwrapOr ""; + + newRow = max 0 state.cursorRow; + newRow2 = min (len state.lines - 1) newRow; + + maxCol = state.mode + | Insert \ len line + | Normal \ max 0 (len line - 1); + newCol2 = min maxCol (max 0 state.cursorCol); + + state.{ cursorRow = newRow2, cursorCol = newCol2 }; + charKind = ch \ - ch == " " - | True \ Space - | False \ (isWordChar ch - | True \ Word - | False \ Punct); + ch == " " | True \ Space + | False \ (isWordChar ch + | True \ Word + | False \ Punct); + skipWhile = pred lines row col \ - line = unwrapOr "" (nth row lines); - col >= (len line) - | True \ ((row + 1) < (len lines) - | True \ skipWhile pred lines (row + 1) 0 - | False \ { row = row, col = col }) - | False \ (pred (unwrapOr "" (nth col line)) - | True \ skipWhile pred lines row (col + 1) - | False \ { row = row, col = col }); + line = nth row lines ~ unwrapOr ""; + col >= len line + | True \ (row + 1 < len lines + | True \ skipWhile pred lines (row + 1) 0 + | False \ { row = row, col = col }) + | False \ (pred (nth col line ~ unwrapOr "") + | True \ skipWhile pred lines row (col + 1) + | False \ { row = row, col = col }); + nextWordStart = lines row col \ - line = unwrapOr "" (nth row lines); - kind = charKind (unwrapOr " " (nth col line)); - pos = skipWhile (ch \ (charKind ch) == kind) lines row col; - skipWhile (ch \ ch == " ") lines pos.row pos.col; + 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) - | False \ { row = 0, col = 0 - 1 }) - | False \ (pred (unwrapOr "" (nth col line)) - | True \ skipBack pred line row (col - 1) lines - | False \ { row = row, col = col }); + col < 0 + | True \ (row > 0 + | True \ ( + prevLine = nth (row - 1) lines ~ unwrapOr ""; + skipBack pred prevLine (row - 1) (len prevLine - 1) lines) + | False \ { row = 0, col = 0 - 1 }) + | False \ (pred (nth col line ~ unwrapOr "") + | True \ skipBack pred line row (col - 1) lines + | False \ { row = row, col = col }); + prevWordStart = lines row col \ - c = col - 1; - r = row; - r2 = c < 0 - | True \ r - 1 - | False \ r; - c2 = c < 0 - | True \ max 0 ((len (unwrapOr "" (nth (r - 1) lines))) - 1) - | False \ c; - r2 < 0 + # step back 1 + c = col - 1; + r = row; + r2 = c < 0 + | True \ r - 1 + | False \ r; + c2 = c < 0 + | True \ (max 0 (len (nth (r - 1) lines ~ unwrapOr "") - 1)) + | False \ c; + # no prev line + r2 < 0 | 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 - | Word \ nextWordStart state.lines from.row from.col - | BackWord \ prevWordStart state.lines from.row from.col - | WholeLine \ { row = from.row, col = 0, linewise = True } - | StartOfLine \ { row = from.row, col = 0 } - | StartOfLineChars \ nextWordStart state.lines from.row 0 - | EndOfLine \ (line = unwrapOr "" (nth state.cursorRow state.lines); - { row = state.cursorRow, col = len line }) - | FirstLine \ (line = unwrapOr "" (nth 0 state.lines); - { row = 0, col = (len line) - 1 }) - | LastLine \ (lastRow = (len state.lines) - 1; - line = unwrapOr "" (nth lastRow state.lines); - { row = lastRow, col = (len line) - 1 }) - | Cursor \ { row = state.cursorRow, col = state.cursorCol + 1 }; - { from = from, to = to }; + from = { row = state.cursorRow, col = state.cursorCol }; + to = motion + | Word \ nextWordStart state.lines from.row from.col + | BackWord \ prevWordStart state.lines from.row from.col + | WholeLine \ { row = from.row, col = 0, linewise = True } + | StartOfLine \ { row = from.row, col = 0 } + | StartOfLineChars \ nextWordStart state.lines from.row 0 + | EndOfLine \ ( + line = nth state.cursorRow state.lines ~ unwrapOr ""; + { row = state.cursorRow, col = (len line) }) + | FirstLine \ ( + line = nth 0 state.lines ~ unwrapOr ""; + { row = 0, col = len line - 1 }) + | LastLine \ ( + lastRow = len state.lines - 1; + line = nth lastRow state.lines ~ unwrapOr ""; + { row = lastRow, col = len line - 1 }) + | Cursor \ { row = state.cursorRow, col = state.cursorCol + 1 }; + { from = from, to = to }; + deleteLine = state \ - newLines = [ - ...take state.cursorRow state.lines, - ...drop (state.cursorRow + 1) state.lines - ]; - { state = state.{ lines = newLines }, emit = [] }; + newLines = [...(take (state.cursorRow) state.lines), ...(drop (state.cursorRow + 1) state.lines)]; + { state = state.{ lines = newLines }, emit = [] }; + deleteRange = state from to \ - line = unwrapOr "" (nth from.row state.lines); - startCol = min from.col to.col; - endCol = max from.col to.col; - newLine = (slice line 0 startCol) & (slice line endCol (len line)); - state.{ lines = updateAt from.row (_ \ newLine) state.lines, cursorCol = startCol }; + 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 - }; - newState = operator - | Delete \ (hasField "linewise" target.to - | True \ deleteLine state - | False \ { - state = deleteRange state target.from target.to, - emit = [] - }); - { - state = clampCursor newState.state.{ undoStack = [stateSnapshot, ...state.undoStack], pending = None, lastAction = Some action }, - emit = newState.emit - }; - moveCursor = motion state \ state.{ cursorCol = motion.to.col, cursorRow = motion.to.row }; + target = resolveMotion motion state; + action = { operator = operator, motion = motion }; + stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol }; + newState = operator + | Delete \ (hasField "linewise" target.to + | True \ deleteLine state + | False \ { state = deleteRange state target.from target.to, emit = [] }); + { state = clampCursor newState.state.{ undoStack = [stateSnapshot, ...state.undoStack], pending = None, lastAction = Some action }, emit = newState.emit }; + + moveCursor = motion state \ + state.{ cursorCol = motion.to.col, cursorRow = motion.to.row }; + write = state \ - content = join "\n" state.lines; - { - state = state, - emit = [rebindAt [buffersKey, name] content] - }; + content = join "\n" state.lines; + { state = state, emit = [rebindAt [buffersKey, name] content] }; + apply = state \ - content = join "\n" state.lines; - isModule = getModuleSource name - | (Some _) \ True - | None \ False; - result = isModule - | True \ applyModule! name content - | False \ eval! content; - _ = debug! "apply" result; - result - | (Defined _) \ { state = state, emit = [] } - | (Err msg) \ (_ = debug! "error applying" []; - { state = state, emit = [] }) - | _ \ { state = state, emit = [] }; + content = (join "\n" state.lines); + result = eval! content; + _ = debug! "apply" [content, result]; + result + | Defined _ \ { state = state, emit = [] } + | Err msg \ ( + _ = debug! "error applying" []; + { state = state, emit = [] } + ) + | _ \ { state = state, emit = [] }; + insertChar = char state \ - newLines = updateAt state.cursorRow (line \ insertCharAt line state.cursorCol char) state.lines; - { - state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 }, - emit = [] - }; + newLines = updateAt state.cursorRow (line \ + insertCharAt line state.cursorCol char + ) state.lines; + + { state = state.{ lines = newLines, cursorCol = state.cursorCol + 1 }, emit = [] }; + replaceChar = char state \ - newLines = updateAt state.cursorRow (line \ - newLine = deleteCharAt line state.cursorCol; - insertCharAt newLine state.cursorCol char) state.lines; - stateSnapshot = { - lines = state.lines, - cursorRow = state.cursorRow, - cursorCol = state.cursorCol - }; - newUndoStack = [stateSnapshot, ...state.undoStack]; - { - state = state.{ lines = newLines, mode = Normal, undoStack = newUndoStack }, - emit = [] - }; + 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 = [] - }; + 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 }; + 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 = [] - }; + 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 = [] - }); + + 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 = [] - }); + state.cursorCol == 0 + | True \ ( + state.cursorRow == 0 + | True \ { state = state, emit = [] } + | False \ ( + { state = state, emit = [] } # todo, join with previous line + ) + ) + | False \ ( + newLines = updateAt state.cursorRow (line \ + before = slice line 0 (state.cursorCol - 1); + after = slice line state.cursorCol (len line); + before & after) state.lines; + + { state = state.{ lines = newLines, cursorCol = state.cursorCol - 1 }, emit = [] }); + enter = state \ - line = unwrapOr "" (nth state.cursorRow state.lines); - before = slice line 0 state.cursorCol; - after = slice line state.cursorCol (len line); - newLines = updateAt state.cursorRow (_ \ before) state.lines; - newLines2 = insertAt (state.cursorRow + 1) after newLines; - { - state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 }, - emit = [] - }; + 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 = [] }; + upArrow = state \ - newState = clampCursor state.{ cursorRow = state.cursorRow - 1 }; - { state = newState, emit = [] }; + newState = clampCursor state.{ cursorRow = state.cursorRow - 1 }; + { state = newState, emit = [] }; + downArrow = state \ - newState = clampCursor state.{ cursorRow = state.cursorRow + 1 }; - { state = newState, emit = [] }; + newState = clampCursor state.{ cursorRow = state.cursorRow + 1 }; + { state = newState, emit = [] }; + leftArrow = state \ - newState = clampCursor state.{ cursorCol = state.cursorCol - 1 }; - { state = newState, emit = [] }; + newState = clampCursor state.{ cursorCol = state.cursorCol - 1 }; + { state = newState, emit = [] }; + rightArrow = state \ - newState = clampCursor state.{ cursorCol = state.cursorCol + 1 }; - { state = newState, emit = [] }; + newState = clampCursor state.{ cursorCol = state.cursorCol + 1 }; + { state = newState, emit = [] }; + findNext = query lines row col \ - rest = strIndexOf query (unwrapOr "" (nth row lines)) col; - rest - | (Some c) \ { row = row, col = c } - | None \ (findInLines = r \ - r >= (len lines) - | True \ findInLines 0 - | False \ (r == row - | True \ (strIndexOf query (unwrapOr "" (nth row lines)) 0 - | (Some c) \ { row = row, col = c } - | None \ { row = row, col = col }) - | False \ (strIndexOf query (unwrapOr "" (nth r lines)) 0 - | (Some c) \ { row = r, col = c } - | None \ findInLines (r + 1))); - findInLines (row + 1)); + rest = strIndexOf query (nth row lines ~ unwrapOr "") col; + rest + | Some c \ { row = row, col = c } + | None \ ( + findInLines = r \ + r >= len lines + | True \ findInLines 0 # wrap to top + | False \ (r == row + | True \ (strIndexOf query (nth row lines ~ unwrapOr "") 0 + | Some c \ { row = row, col = c } + | None \ { row = row, col = col }) # no match + | False \ (strIndexOf query (nth r lines ~ unwrapOr "") 0 + | Some c \ { row = r, col = c } + | None \ findInLines (r + 1))); + findInLines (row + 1)); + findPrev = query lines row col \ - line = slice (unwrapOr "" (nth row lines)) 0 col; - rest = strLastIndexOf query line; - rest - | (Some c) \ { row = row, col = c } - | None \ (findInLines = r \ - r < 0 - | True \ findInLines ((len lines) - 1) - | False \ (r == row - | True \ (strLastIndexOf query (unwrapOr "" (nth row lines)) - | (Some c) \ { row = row, col = c } - | None \ { row = row, col = col }) - | False \ (strLastIndexOf query (unwrapOr "" (nth r lines)) - | (Some c) \ { row = r, col = c } - | None \ findInLines (r - 1))); - findInLines (row - 1)); - { view = ctx \ + line = slice (nth row lines ~ unwrapOr "") 0 col; + rest = strLastIndexOf query line; + rest + | Some c \ { row = row, col = c } + | None \ ( + findInLines = r \ + r < 0 + | True \ findInLines (len lines - 1) # wrap to bottom + | False \ (r == row + | True \ (strLastIndexOf query (nth row lines ~ unwrapOr "") + | Some c \ { row = row, col = c } + | None \ { row = row, col = col }) # no match + | False \ (strLastIndexOf query (nth r lines ~ unwrapOr "") + | Some c \ { row = r, col = c } + | None \ findInLines (r - 1))); + findInLines (row - 1)); + + { + view = ctx \ + autoScroll = state \ - cursorY = state.cursorRow * lineH; - newScrollY = cursorY < state.scrollY - | True \ cursorY - | False \ ((cursorY + lineH) > (state.scrollY + ctx.h) - | True \ (cursorY + lineH) - ctx.h - | False \ state.scrollY); - cursorX = ((state.cursorCol * charW) * scale) + (charGap * state.cursorCol); - newScrollX = cursorX < state.scrollX - | True \ cursorX - | False \ ((cursorX + (charW * scale)) > (state.scrollX + ctx.w) - | True \ (cursorX + (charW * scale)) - ctx.w - | False \ state.scrollX); - state.{ scrollY = newScrollY, scrollX = newScrollX }; - withScroll = result \ { state = autoScroll result.state, emit = result.emit }; + cursorY = state.cursorRow * lineH; + newScrollY = (cursorY < state.scrollY + | True \ cursorY + | False \ (cursorY + lineH > state.scrollY + ctx.h + | True \ cursorY + lineH - ctx.h + | False \ state.scrollY)); + + cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol; + newScrollX = (cursorX < state.scrollX + | True \ cursorX + | False \ (cursorX + charW * scale > state.scrollX + ctx.w + | True \ cursorX + charW * scale - ctx.w + | False \ state.scrollX)); + state.{ scrollY = newScrollY, scrollX = newScrollX }; + + withScroll = result \ + { state = autoScroll result.state, emit = result.emit }; + + # search stuff initSearch = state \ - newState = state.{ mode = Search, searchQuery = "", savedCursorCol = state.cursorCol, savedCursorRow = state.cursorRow }; - { state = newState, emit = [] }; + newState = state.{ + mode = Search, + searchQuery = "", + savedCursorCol = state.cursorCol, + savedCursorRow = state.cursorRow, + }; + + { state = newState, emit = [] }; + cancelSearch = state \ - newState = state.{ mode = Normal, searchQuery = "", cursorCol = state.savedCursorCol, cursorRow = state.savedCursorRow }; - { state = newState, emit = [] }; + newState = state.{ + mode = Normal, + searchQuery = "", + cursorCol = state.savedCursorCol, + cursorRow = state.savedCursorRow, + }; + + { state = newState, emit = [] }; + updateSearchQuery = query state \ - next = findNext query state.lines state.cursorRow state.cursorCol; - newState = autoScroll state.{ searchQuery = query, cursorCol = next.col, cursorRow = next.row }; - { state = newState, emit = [] }; - backspaceSearch = state \ updateSearchQuery (slice state.searchQuery 0 ((len state.searchQuery) - 1)) state; - addCharToSearchQuery = char state \ updateSearchQuery (state.searchQuery & char) state; - endSearch = state \ { state = autoScroll state.{ mode = Normal }, emit = [] }; + next = findNext query state.lines state.cursorRow state.cursorCol; + newState = autoScroll state.{ + searchQuery = query, + cursorCol = next.col, + cursorRow = next.row, + }; + { state = newState, emit = [] }; + + backspaceSearch = state \ + updateSearchQuery (slice state.searchQuery 0 (len state.searchQuery - 1)) state; + + addCharToSearchQuery = char state \ + updateSearchQuery (state.searchQuery & char) state; + + endSearch = state \ + { state = autoScroll state.{ mode = Normal }, emit = [] }; + nextSearch = state \ - next = findNext state.searchQuery state.lines state.cursorRow (state.cursorCol + 1); - newState = autoScroll state.{ mode = Normal, cursorCol = next.col, cursorRow = next.row }; - { state = newState, emit = [] }; + next = findNext state.searchQuery state.lines state.cursorRow (state.cursorCol + 1); + newState = autoScroll state.{ + mode = Normal, + cursorCol = next.col, + cursorRow = next.row, + }; + + { state = newState, emit = [] }; + prevSearch = state \ - prev = findPrev state.searchQuery state.lines state.cursorRow state.cursorCol; - newState = autoScroll state.{ mode = Normal, cursorCol = prev.col, cursorRow = prev.row }; - { state = newState, emit = [] }; - scrollHalfUp = state ctx \ - diff = floor ((ctx.h / 2) / lineH); - newRow = state.cursorRow - diff; - withScroll { - state = clampCursor state.{ cursorRow = newRow }, - emit = [] - }; - scrollHalfDown = state ctx \ - diff = floor ((ctx.h / 2) / lineH); - newRow = state.cursorRow + diff; - withScroll { - state = clampCursor state.{ cursorRow = newRow }, - emit = [] - }; + prev = findPrev state.searchQuery state.lines state.cursorRow state.cursorCol; + newState = autoScroll state.{ + mode = Normal, + cursorCol = prev.col, + cursorRow = prev.row, + }; + + { state = newState, emit = [] }; + + scrollHalfUp = state ctx \ ( + diff = floor ((ctx.h / 2) / lineH); + newRow = state.cursorRow - diff; + withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] }); + + scrollHalfDown = state ctx \ ( + diff = floor ((ctx.h / 2) / lineH); + newRow = state.cursorRow + diff; + withScroll { state = clampCursor state.{ cursorRow = newRow }, emit = [] }); + handleMotion = motion state \ - state.pending - | None \ withScroll { - state = moveCursor (resolveMotion motion state) state, - emit = [] - } - | (Some Delete) \ withScroll (applyOperator Delete motion state); + state.pending + | None \ withScroll { state = moveCursor (resolveMotion motion state) state, emit = [] } + | Some Delete \ withScroll (applyOperator Delete motion state); + ui.stateful { focusable = True, autoFocus = True, + key = "textEditor-" & name, + init = { - mode = Normal, + 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 - | _ \ { state = state, emit = [] }) + | Key { key = "Escape" } \ cancelSearch state + | Key { key = "Enter" } \ endSearch state + | Key { key = "Backspace" } \ backspaceSearch state + | Key { key = k, printable = True } \ addCharToSearchQuery k state + | _ \ { state = state , emit = [] }) | Insert \ (event - | (Key {key = "Escape"}) \ escape state - | (Key {key = "Control"}) \ escape state - | (Key {key = "Backspace"}) \ backspace state - | (Key {key = "Enter"}) \ enter state - | (Key {key = k, printable = True}) \ insertChar k state - | _ \ { state = state, emit = [] }) + | Key { key = "Escape" } \ escape state + | Key { key = "Control" } \ escape state + | Key { key = "Backspace" } \ backspace state + | Key { key = "Enter" } \ enter state + | Key { key = k, printable = True } \ insertChar k state + | _ \ { state = state , emit = [] }) | ReplaceChar \ (event - | (Key {key = k, printable = True}) \ replaceChar k state + | Key { key = k, printable = True } \ replaceChar k state | _ \ { state = state.{ mode = Normal }, emit = [] }) | Normal \ (event - | (Scrolled delta) \ (maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines; - totalW = ((maxLineLen * charW) * scale) + (maxLineLen * charGap); - totalH = (len state.lines) * lineH; + | Scrolled delta \ ( + maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines; + totalW = maxLineLen * charW * scale + maxLineLen * charGap; + totalH = len state.lines * lineH; newX = max 0 (min (totalW - ctx.w) (state.scrollX + delta.deltaX)); newY = max 0 (min (totalH - ctx.h) (state.scrollY + delta.deltaY)); - { - state = state.{ scrollY = newY, scrollX = newX }, - emit = [] - }) - | (Key {key = "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)" - } - }; + x = cursorX, + y = cursorY, + child = ui.rect { w = charW * scale, h = charH * scale, color = "rgba(255,255,255,0.5)" } + }; + maxLineLen = fold (acc line \ max acc (len line)) 0 state.lines; - totalWidth = ((maxLineLen * charW) * scale) + (maxLineLen * charGap); - totalHeight = (len state.lines) * lineH; + totalWidth = maxLineLen * charW * scale + maxLineLen * charGap; + totalHeight = len state.lines * lineH; + highlights = state.searchQuery == "" | True \ [] | False \ findAll state.searchQuery state.lines; - highlightsUi = map (c \ ui.positioned { - x = ((c.col * charW) * scale) + (charGap * c.col), - y = c.row * lineH, - child = ui.rect { - w = (((len state.searchQuery) * charW) * scale) + (charGap * ((len state.searchQuery) - 1)), - h = charH * scale, - color = "rgba(255,200,0,0.5)" - } - }) highlights; + + highlightsUi = map (c \ + ui.positioned { + x = c.col * charW * scale + charGap * c.col, + y = c.row * lineH, + child = ui.rect { + w = (len state.searchQuery) * charW * scale + charGap * (len state.searchQuery - 1), + h = charH * scale, + color = "rgba(255,200,0,0.5)" + } + } + ) highlights; + + # buffer = map (l \ text l) state.lines; + # only render visible lines 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 + ] + } } - } }; - + } + }; @ diff --git a/src/runtime-js.ts b/src/runtime-js.ts index 6f0c505..94bd6c4 100644 --- a/src/runtime-js.ts +++ b/src/runtime-js.ts @@ -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(); - 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);