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