From 3267d5bc39655c2844ef630f394ed3f9792a1cf1 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Thu, 26 Feb 2026 21:03:08 -0700 Subject: [PATCH] textEditor scrolling. only rendering the lines that are in the viewport. smooth. better prettyPrinting of match inside lambda --- src/ast.ts | 9 +- src/cg/06-textEditor.cg | 290 ++++++++++++++++++++++------------------ 2 files changed, 163 insertions(+), 136 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 8085c52..028c8c8 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -252,9 +252,12 @@ export function prettyPrint(ast: AST, indent = 0): string { case 'match': const expr = prettyPrint(ast.expr, indent); const cases = ast.cases - .map(c => `${i}| ${prettyPrintPattern(c.pattern)} \\ ${prettyPrint(c.result, indent + 1)}`) - .join('\n'); - return `${expr}\n${cases}`; + .map(c => { + const result = prettyPrint(c.result, indent + 1); + const needsParens = c.result.kind === 'match' || c.result.kind === 'let'; + return `${i}| ${prettyPrintPattern(c.pattern)} \\ ${needsParens ? '(' : ''}${result}${needsParens ? ')' : ''}`; + }).join('\n'); + return `${i}${expr}\n${cases}`; case 'rebind': return `${prettyPrint(ast.target, indent)} := ${prettyPrint(ast.value, indent)}`; diff --git a/src/cg/06-textEditor.cg b/src/cg/06-textEditor.cg index 113b82c..7238a7e 100644 --- a/src/cg/06-textEditor.cg +++ b/src/cg/06-textEditor.cg @@ -85,9 +85,6 @@ textEditor = name \ newLines = updateAt state.cursorRow (_ \ before) state.lines; newLines2 = insertAt (state.cursorRow + 1) after newLines; - _ = debug! "enter" { before = before, after = after, newLines2 = newLines2, newRow = - state.cursorRow + 1 }; - { state = state.{ lines = newLines2, cursorCol = 0, cursorRow = state.cursorRow + 1 }, emit = [] }; clampCursor = state \ @@ -103,157 +100,184 @@ textEditor = name \ state.{ cursorRow = newRow2, cursorCol = newCol2 }; - upArrow = state \ ( + upArrow = state \ newState = clampCursor state.{ cursorRow = state.cursorRow - 1 }; - { state = newState, emit = [] }); + { state = newState, emit = [] }; - downArrow = state \ ( + downArrow = state \ newState = clampCursor state.{ cursorRow = state.cursorRow + 1 }; - { state = newState, emit = [] }); + { state = newState, emit = [] }; - leftArrow = state \ ( + leftArrow = state \ newState = clampCursor state.{ cursorCol = state.cursorCol - 1 }; - { state = newState, emit = [] }); + { state = newState, emit = [] }; - rightArrow = state \ ( + rightArrow = state \ newState = clampCursor state.{ cursorCol = state.cursorCol + 1 }; - { state = newState, emit = [] }); + { state = newState, emit = [] }; + + # todo: scrollHalfDown = state \ (); + # todo: scrollHalfUp = state \ (); { - view = ctx \ ui.stateful { - focusable = True, - autoFocus = True, - - key = "textEditor-" & name, - - init = { - lines = lines, - cursorRow = 0, - cursorCol = 0, - scrollX = 0, - scrollY = 0, - undoStack = [], - redoStack = [], - mode = Normal # Normal | Insert | Visual - }, + view = ctx \ - update = state event \ event - | Scrolled delta \ ( - newY = max 0 (state.scrollY + delta.deltaY); - newX = max 0 (state.scrollX + delta.deltaX); - { state = state.{ scrollY = newY, scrollX = newX }, emit = [] }) - - | Key { key = "ArrowDown" } \ downArrow state - | Key { key = "j" } \ (state.mode - | Insert \ insertChar "j" state - | Normal \ downArrow state) - - | Key { key = "ArrowUp" } \ upArrow state - | Key { key = "k" } \ (state.mode - | Insert \ insertChar "k" state - | Normal \ upArrow state) - - | Key { key = "ArrowLeft" } \ leftArrow state - | Key { key = "h" } \ (state.mode - | Insert \ insertChar "h" state - | Normal \ leftArrow state) - - | Key { key = "ArrowRight" } \ rightArrow state - | Key { key = "l" } \ (state.mode - | Insert \ insertChar "l" state - | Normal \ rightArrow state) - - | Key { key = "i" } \ (state.mode - | Insert \ insertChar "i" state - | Normal \ insertMode state) - - | Key { key = "u" } \ (state.mode - | Insert \ insertChar "u" state - | Normal \ undo state) - - | Key { key = "r", ctrl = True } \ (state.mode - | Insert \ { state = state, emit = [] } - | Normal \ redo state) - - | Key { key = "W", shift = True } \ (state.mode - | Insert \ insertChar "W" state - | Normal \ write state) - - | Key { key = "W", shift = True } \ (state.mode - | Insert \ insertChar "W" state - | Normal \ write state) - - | Key { key = "A", shift = True } \ (state.mode - | Insert \ insertChar "A" state - | Normal \ apply state) - - | Key { key = "Escape" } \ escape state - - | Key { key = "Backspace" } \ (state.mode - | Insert \ backspace state - | _ \ { state = state, emit = [] }) - - | Key { key = "Enter" } \ (state.mode - | Insert \ enter state - | _ \ { state = state, emit = [] }) - - # any other key - | Key { key = key, printable = True } \ (state.mode - | Insert \ insertChar key state - | _ \ { state = state, emit = [] }) - - | _ \ { state = state, emit = [] }, - - view = state emit \ - scale = 2; - charH = 12; - charW = 5; - lineGap = 1; - charGap = 2; - lineH = charH * scale + lineGap; + scale = 2; + charH = 12; + charW = 5; + lineGap = 1; + charGap = 2; + lineH = charH * scale + lineGap; + autoScroll = state \ cursorY = state.cursorRow * lineH; - scrollY = (cursorY < state.scrollY + newScrollY = (cursorY < state.scrollY | True \ cursorY | False \ (cursorY + lineH > state.scrollY + ctx.h | True \ cursorY + lineH - ctx.h | False \ state.scrollY)); cursorX = state.cursorCol * charW * scale + charGap * state.cursorCol; - scrollX = (cursorX < state.scrollX + newScrollX = (cursorX < state.scrollX | True \ cursorX | False \ (cursorX + charW * scale > state.scrollX + ctx.w | True \ cursorX + charW * scale - ctx.w | False \ state.scrollX)); - - buffer = map (l \ text l) state.lines; - - cursor = ui.positioned { - 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; - - scrollable { - w = ctx.w, - h = ctx.h, - totalWidth = totalWidth, - totalHeight = totalHeight, - scrollX = scrollX, - scrollY = scrollY, - child = ui.stack { - children = [ - ui.column { - gap = 1, - children = buffer - }, - cursor - ] + state.{ scrollY = newScrollY, cursorY = cursorY, scrollX = newScrollX, cursorX = cursorX }; + + withScroll = result \ + { state = autoScroll result.state, emit = result.emit }; + + ui.stateful { + focusable = True, + autoFocus = True, + + key = "textEditor-" & name, + + init = { + lines = lines, + cursorRow = 0, + cursorCol = 0, + scrollX = 0, + scrollY = 0, + undoStack = [], + redoStack = [], + mode = Normal # Normal | Insert | Visual + }, + + update = state event \ 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; + 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" } \ (state.mode + | Insert \ insertChar "j" state + | Normal \ withScroll (downArrow state)) + + | Key { key = "ArrowUp" } \ withScroll (upArrow state) + | Key { key = "k" } \ (state.mode + | Insert \ insertChar "k" state + | Normal \ withScroll(upArrow state)) + + | Key { key = "ArrowLeft" } \ withScroll (leftArrow state) + | Key { key = "h" } \ (state.mode + | Insert \ insertChar "h" state + | Normal \ withScroll(leftArrow state)) + + | Key { key = "ArrowRight" } \ withScroll (rightArrow state) + | Key { key = "l" } \ (state.mode + | Insert \ insertChar "l" state + | Normal \ withScroll(rightArrow state)) + + | Key { key = "i" } \ (state.mode + | Insert \ insertChar "i" state + | Normal \ insertMode state) + + | Key { key = "u" } \ (state.mode + | Insert \ insertChar "u" state + | Normal \ undo state) + + | Key { key = "r", ctrl = True } \ (state.mode + | Insert \ { state = state, emit = [] } + | Normal \ redo state) + + | Key { key = "W", shift = True } \ (state.mode + | Insert \ insertChar "W" state + | Normal \ write state) + + | Key { key = "W", ctrl = True, shift = True } \ (state.mode + | Insert \ insertChar "W" state + | Normal \ write state) + + | Key { key = "A", ctrl = True, shift = True } \ (state.mode + | Insert \ insertChar "A" state + | Normal \ apply state) + + | Key { key = "d", ctrl = True } \ scrollHalfDown state + | Key { key = "u", ctrl = True } \ scrollHalfUp state + + | Key { key = "Escape" } \ escape state + + | Key { key = "Backspace" } \ (state.mode + | Insert \ backspace state + | _ \ { state = state, emit = [] }) + + | Key { key = "Enter" } \ (state.mode + | Insert \ enter state + | _ \ { state = state, emit = [] }) + + # any other key + | Key { key = key, printable = True } \ (state.mode + | Insert \ insertChar key state + | _ \ { state = state, emit = [] }) + + | _ \ { state = state, emit = [] }, + + view = state emit \ + _ = debug! "here" state; + cursor = ui.positioned { + x = state.cursorX, + y = state.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; + + # buffer = map (l \ text l) state.lines; + # only render visible lines + startLine = max 0 (floor (state.scrollY / lineH)); + 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; + + scrollable { + w = ctx.w, + h = ctx.h, + totalWidth = totalWidth, + totalHeight = totalHeight, + scrollX = state.scrollX, + scrollY = state.scrollY, + onScroll = delta \ emit (Scrolled delta), + child = ui.stack { + children = [ + ...buffer, + cursor + ] + } } - } - } + } };