diff --git a/src/cg/06-textEditor.cg b/src/cg/06-textEditor.cg index d503538..95efb93 100644 --- a/src/cg/06-textEditor.cg +++ b/src/cg/06-textEditor.cg @@ -31,6 +31,59 @@ textEditor = name \ state.{ cursorRow = newRow2, cursorCol = newCol2 }; + charKind = ch \ + 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 }); + + 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; + + resolveMotion = motion state \ + from = { row = state.cursorRow, col = state.cursorCol }; + to = motion + | Word \ nextWordStart state.lines from.row from.col + | WholeLine \ from; + { from = from, to = to }; + + deleteLine = state \ + 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 ""; + toCol = to.row > from.row + | True \ len line + | False \ to.col; + newLine = (slice line 0 from.col) & (slice line toCol (len line)); + state.{ lines = updateAt from.row (_ \ newLine) state.lines, cursorCol = from.col }; + + applyOperator = operator motion state \ + target = resolveMotion motion state; + stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol }; + newState = operator + | Delete \ (motion + | WholeLine \ deleteLine state + | _ \ { state = deleteRange state target.from target.to, emit = [] }); + { state = clampCursor newState.state.{ undoStack = [stateSnapshot, ...state.undoStack], pending = None }, 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] }; @@ -82,19 +135,6 @@ textEditor = name \ emit = [] }; - deleteLine = state \ - stateSnapshot = { lines = state.lines, cursorRow = state.cursorRow, cursorCol = state.cursorCol }; - newUndoStack = [stateSnapshot, ...state.undoStack]; - newLines = [...(take (state.cursorRow) state.lines), ...(drop (state.cursorRow + 1) state.lines)]; - { - state = clampCursor state.{ - lines = newLines, - undoStack = newUndoStack, - pending = None - }, - emit = [] - }; - escape = state \ { state = state.{ mode = Normal }, emit = [] }; undo = state \ state.undoStack @@ -233,10 +273,13 @@ textEditor = name \ | Key { key = "d", ctrl = True } \ scrollHalfDown state ctx | Key { key = "u", ctrl = True } \ scrollHalfUp state ctx | Key { key = "u" } \ undo state - | Key { key = "d" } \ (state.pending - | Some "d" \ deleteLine state - | _ \ { state = state.{ pending = Some "d" }, emit = [] }) | Key { key = "r", ctrl = True } \ redo state + | Key { key = "d" } \ (state.pending + | Some Delete \ applyOperator Delete WholeLine state + | _ \ { state = state.{ pending = Some Delete }, emit = [] }) + | Key { key = "w" } \ (state.pending + | None \ withScroll { state = moveCursor (resolveMotion Word state) state, emit = [] } + | Some Delete \ withScroll (applyOperator Delete Word state)) | Key { key = "W", ctrl = True, shift = True } \ write state | Key { key = "A", ctrl = True, shift = True } \ apply state # any other key or event diff --git a/src/cg/10-os.cg b/src/cg/10-os.cg index 6b8305c..f116341 100644 --- a/src/cg/10-os.cg +++ b/src/cg/10-os.cg @@ -107,7 +107,7 @@ os = ui.rect { w = titleBarHeight, h = titleBarHeight, color = "rgba(255,255,255,0.2)" }, # button text ui.positioned { - x = 10, y = 5, child = text "x" + x = 12, y = 5, child = text "x" } ] }