From f42b5d8abe21135eccfd773a8f56e48ceae09d67 Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Mon, 16 Feb 2026 16:13:24 -0700 Subject: [PATCH] tree leaves are now editable. cleaning. removing redefine, since i can just eval() with := --- src/cg/06-inspector.cg | 26 +++---------- src/cg/06-tree.cg | 88 +++++++++++++++++++++++++++++++++--------- src/runtime-js.ts | 12 +++--- 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/src/cg/06-inspector.cg b/src/cg/06-inspector.cg index 8c319a0..82bd87b 100644 --- a/src/cg/06-inspector.cg +++ b/src/cg/06-inspector.cg @@ -7,25 +7,9 @@ inspector = config \ textInputHeight = 40; - ui.column { - gap = 0, - children = [ - # tree - tree { - value = reflected, - path = config.name, - w = config.w, - h = config.h - textInputHeight - }, - - # definition bar - textInput { - key = "inspector-redefine-" & config.name, - color = "white", - backgroundColor = "rgba(255,255,255,0.2)", - w = config.w, - h = textInputHeight, - onSubmit = text \ redefine config.name text - } - ] + tree { + value = reflected, + path = config.name, + w = config.w, + h = config.h - textInputHeight }; diff --git a/src/cg/06-tree.cg b/src/cg/06-tree.cg index dcc9044..d2388ac 100644 --- a/src/cg/06-tree.cg +++ b/src/cg/06-tree.cg @@ -29,24 +29,24 @@ treeNodeHeight = value path expanded \ visiblePaths = value path expanded \ value | RecordValue entries \ ( - [path, ...(contains path expanded) + [{ path = path, leaf = False }, ...(contains path expanded) | True \ flatten (map (entry \ entryPath = path & "." & entry.key; (valueLabel entry.value) - | Some _ \ [entryPath] + | Some _ \ [{ path = entryPath, leaf = True }] | None \ visiblePaths entry.value entryPath expanded ) entries) | False \ []]) | ListValue items \ ( - [path, ...(contains path expanded) + [{ path = path, leaf = False }, ...(contains path expanded) | True \ flatten (mapWithIndex (item i \ itemPath = path & "." & (show i); (valueLabel item) - | Some _ \ [itemPath] + | Some _ \ [{ path = itemPath, leaf = True }] | None \ visiblePaths item itemPath expanded ) items) | False \ []]) - | _ \ [path]; + | _ \ [{ path = path, leaf = True }]; treeNode = config \ depth = config.depth; @@ -58,6 +58,8 @@ treeNode = config \ child = ui.text { content = content, color = (selected | True \ "white" | False \ color) } }; + isEditing = config.editing | Some p \ p == config.path | None \ False; + valueLabel = value \ value | NumberValue n \ Some (show n) | StringValue n \ Some ("\"" & n & "\"") @@ -73,11 +75,53 @@ treeNode = config \ isExp = contains config.path config.expanded; + onSubmit = text \ ( + (eval text) + | Value v \ batch [rebindAt config.path v, config.onDoneEditing {}] + | _ \ config.onDoneEditing {}); + config.value - | NumberValue n \ simple config.path (config.prefix & (show n)) "#6cf" - | StringValue n \ simple config.path (config.prefix & "\"" & n & "\"") "#f6a" - | ConstructorValue { tag = tag } \ simple config.path (config.prefix & tag) "#fc6" + | NumberValue n \ (isEditing + | True \ textInput { + key = "edit-" & config.path, + initialValue = show n, + autoFocus = True, + color = "white", + backgroundColor = "rgba(255,255,255,0.2)", + w = 200, + h = 30, + onSubmit = onSubmit + } + | False \ simple config.path (config.prefix & (show n)) "#6cf") + + | StringValue n \ (isEditing + | True \ textInput { + key = "edit-" & config.path, + initialValue = "\"" & n & "\"", + autoFocus = True, + color = "white", + backgroundColor = "rgba(255,255,255,0.2)", + w = 200, + h = 30, + onSubmit = onSubmit + } + | False \ simple config.path (config.prefix & "\"" & n & "\"") "#f6a") + + | ConstructorValue { tag = tag } \ (isEditing + | True \ textInput { + key = "edit-" & config.path, + initialValue = tag, + autoFocus = True, + color = "white", + backgroundColor = "rgba(255,255,255,0.2)", + w = 200, + h = 30, + onSubmit = onSubmit + } + | False \ simple config.path (config.prefix & tag) "#fc6") + | FunctionValue _ \ simple config.path (config.prefix & "") "#888" + | RecordValue entries \ ui.column { gap = 0, children = [ header isExp ((show (len entries)) & " fields") "#888", @@ -86,7 +130,7 @@ treeNode = config \ onClick = _ \ noOp, child = ui.column { gap = 0, children = map (entry \ pfx = (valueLabel entry.value) | Some _ \ " = " | None \ " "; - treeNode { value = entry.value, depth = depth + 1, path = config.path & "." & entry.key, expanded = config.expanded, onToggle = config.onToggle, selectedPath = config.selectedPath, prefix = entry.key & pfx } + treeNode { value = entry.value, depth = depth + 1, path = config.path & "." & entry.key, expanded = config.expanded, onToggle = config.onToggle, selectedPath = config.selectedPath, prefix = entry.key & pfx, editing = config.editing, onDoneEditing = config.onDoneEditing } ) entries } }] | False \ []) @@ -98,7 +142,7 @@ treeNode = config \ | True \ [ui.clickable { onClick = _ \ noOp, child = ui.column { gap = 0, children = mapWithIndex (item i \ - treeNode { value = item, depth = depth + 1, path = config.path & "." & (show i), expanded = config.expanded, onToggle = config.onToggle, selectedPath = config.selectedPath, prefix = (show i) & ": " } + treeNode { value = item, depth = depth + 1, path = config.path & "." & (show i), expanded = config.expanded, onToggle = config.onToggle, selectedPath = config.selectedPath, prefix = (show i) & ": ", editing = config.editing, onDoneEditing = config.onDoneEditing } ) items } }] | False \ []) @@ -111,7 +155,7 @@ tree = config \ autoFocus = True, key = "tree-" & config.path, - init = { expanded = [], scrollY = 0, selectedIndex = 0 }, + init = { expanded = [], scrollY = 0, selectedIndex = 0, editing = None }, update = state event \ event | Toggle path \ ((contains path state.expanded) @@ -136,22 +180,30 @@ tree = config \ { state = state.{ selectedIndex = newIndex }, emit = [] }) | Key { key = "Enter" } \ ( - paths = visiblePaths config.value config.path state.expanded; - selected = nth state.selectedIndex paths; + items = visiblePaths config.value config.path state.expanded; + selected = nth state.selectedIndex items; selected - | Some path \ ((contains path state.expanded) - | True \ { state = state.{ expanded = filter (p \ p != path) state.expanded }, emit = [] } - | False \ { state = state.{ expanded = [path, ...state.expanded] }, emit = [] } + | Some item \ (item.leaf + | True \ { state = state.{ editing = Some item.path }, emit = [] } + | False \ ((contains item.path state.expanded) + | True \ { state = state.{ expanded = filter (p \ p != item.path) state.expanded }, emit = [] } + | False \ { state = state.{ expanded = [item.path, ...state.expanded] }, emit = [] } + ) ) | None \ { state = state, emit = [] }) + | Key { key = "Escape" } \ { state = state.{ editing = None }, emit = [] } + + | DoneEditing \ { state = state.{ editing = None }, emit = [] } + | _ \ { state = state, emit = [] }, view = state emit \ onToggle = path \ emit (Toggle path); + onDoneEditing = _ \ emit DoneEditing; totalH = treeNodeHeight config.value config.path state.expanded; paths = visiblePaths config.value config.path state.expanded; - selectedPath = nth state.selectedIndex paths; + selectedPath = (nth state.selectedIndex paths) | Some item \ Some item.path | None \ None; scrollable { w = config.w, @@ -161,6 +213,6 @@ tree = config \ scrollX = 0, scrollY = state.scrollY, onScroll = delta \ emit (Scrolled delta), - child = treeNode { value = config.value, path = config.path, depth = 0, expanded = state.expanded, onToggle = onToggle, selectedPath = selectedPath, prefix = "" } + child = treeNode { value = config.value, path = config.path, depth = 0, expanded = state.expanded, onToggle = onToggle, selectedPath = selectedPath, prefix = "", editing = state.editing, onDoneEditing = onDoneEditing } } }; diff --git a/src/runtime-js.ts b/src/runtime-js.ts index 2ce788d..2ddfafc 100644 --- a/src/runtime-js.ts +++ b/src/runtime-js.ts @@ -113,12 +113,12 @@ export const _rt = { } syncToAst(name); }, - redefine: (name: string) => (code: string) => { - const tokens = tokenize(`_tmp = ${code};`); - const parser = new Parser(tokens, ""); - const defs = parser.parse(); - recompile(name, defs[0].body); - return { _tag: 'Ok' }; + rebindAt: (pathStr: string) => (value: string) => { + const parts = pathStr.split('.'); + const name = parts[0]; + const path = parts.slice(1); + + return { _tag: 'Rebind', _0: name, _1: path, _2: value }; }, undefine: (name: string) => { delete store[name];