diff options
author | Dougal <d.maclaurin@gmail.com> | 2023-12-12 12:58:48 -0500 |
---|---|---|
committer | Dougal <d.maclaurin@gmail.com> | 2023-12-12 12:58:48 -0500 |
commit | 5f284cd99150723cb1798f0f9a6ece186f283670 (patch) | |
tree | 1f8dab13df6d30cc9cc440cafd440851f4ad7d78 /static | |
parent | c36f36220df77837c4eaa4c7ddc7384066f6ad5d (diff) |
Organize source information by pass and do more of the work on the Haskell side.
This way we keep the serialized data type more stable since that's the only part
of the system that isn't type checked.
Diffstat (limited to 'static')
-rw-r--r-- | static/index.ts | 184 | ||||
-rw-r--r-- | static/style.css | 23 |
2 files changed, 130 insertions, 77 deletions
diff --git a/static/index.ts b/static/index.ts index 176bb8cd..6b0320a0 100644 --- a/static/index.ts +++ b/static/index.ts @@ -13,15 +13,24 @@ const minimap : Element = document.getElementById("minimap") ?? oops() type CellId = number type SrcId = number +type LexemeId = number type HTMLString = string type Div = Element type Status = "Waiting" | "Running" | "Complete" | "CompleteWithErrors" | "Inert" -type HighlightType = "HighlightGroup" | "HighlightLeaf" -type Span = [Div, Div] -type SpanMap = Map<SrcId, Span> -type HighlightMap = Map<SrcId, [HighlightType, SrcId]> -type HoverMap = Map<SrcId, HTMLString> +type HighlightType = "HighlightGroup" | "HighlightLeaf" | "HighlightError" +type Highlight = [SrcId, HighlightType] + +type HsMaybe<T> = {tag:"Just"; contents:T} | {tag: "Nothing"} +type HsOverwrite<T> = {tag:"OverwriteWith"; contents:T} | {tag: "NoChange"} + +type FocusMap = Map<LexemeId, SrcId> +type TreeMap = Map<SrcId, TreeNode> +type TreeNode = { + srcId : SrcId + span : [Div, Div] | null ; + highlights : Highlight[]; + text : HTMLString} type Cell = { cellId : CellId; root : Div; @@ -31,12 +40,8 @@ type Cell = { status : Div; curStatus : Status | null ; sourceText : string; - spanMap : SpanMap; - highlightMap : HighlightMap; - hoverMap : HoverMap} -type HsSourceInfo = - {tag: "SIGroupTree"; contents: HsMaybe<HsGroupTree>} | - {tag: "SITypeInfo" ; contents: [SrcId, string][]} + focusMap : FocusMap; + treeMap : TreeMap} type HsRenderedSourceBlock = { rsbLine : number; rsbNumLines : number; @@ -47,22 +52,25 @@ type HsRenderedSourceBlock = { type HsRenderedOutput = {tag: "RenderedTextOut" ; contents: string } | {tag: "RenderedHtmlOut" ; contents: HTMLString } | - {tag: "RenderedSourceInfo"; contents: HsSourceInfo } | - {tag: "RenderedPassResult"; contents: PassName } | + {tag: "RenderedPassResult"; contents: string } | {tag: "RenderedMiscLog" ; contents: string } | - {tag: "RenderedError" ; contents: [HsMaybe<SrcId>, string]} -type PassName = string -type PassInfo = string | null -type LexemeId = SrcId -type LexemeSpan = [LexemeId, LexemeId] -type HsGroupTree = { - gtSrcId : SrcId; - gtSpan : LexemeSpan; - gtChildren : [HsGroupTree]; - gtIsAtomicLexeme : boolean } + {tag: "RenderedError" ; contents: [HsMaybe<SrcId>, string]} | + {tag: "RenderedTreeNodeUpdate" ; contents: [SrcId, HsTreeNodeMapUpdate][]} | + {tag: "RenderedFocusUpdate" ; contents: [LexemeId, SrcId][]} -type HsMaybe<T> = {tag:"Just"; contents:T} | {tag: "Nothing"} -type HsOverwrite<T> = {tag:"OverwriteWith"; contents:T} | {tag: "NoChange"} +type HsFocusMap = Map<LexemeId, SrcId> +type HsTreeNodeState = { + tnSpan : [LexemeId, LexemeId] + tnHighlights : Highlight[] + tnText : HTMLString} +type HsTreeNodeUpdate = { + tnuHighlights : HsOverwrite<Highlight[]>; + tnuText : HsOverwrite<HTMLString>} +type HsTreeNodeMapUpdate = + {tag: "Create" ; contents: HsTreeNodeState} | + {tag: "Replace"; contents: HsTreeNodeState} | + {tag: "Update" ; contents: HsTreeNodeUpdate} | + {tag: "Delete"} type HsCellState = [HsRenderedSourceBlock, Status, HsRenderedOutput[]] type HsCellUpdate = [HsOverwrite<Status>, HsRenderedOutput[]] type HsCellMapUpdate = @@ -147,9 +155,8 @@ function createCell(cellId: CellId) : Cell { status : status, curStatus : null, sourceText : "", - spanMap : new Map<SrcId, Span>(), - highlightMap : new Map<SrcId, [HighlightType, SrcId]>(), - hoverMap : new Map<SrcId, HTMLString>()} + focusMap : new Map<LexemeId, SrcId>(), + treeMap : new Map<SrcId, TreeNode>()} cells.set(cellId, cell) return cell } @@ -173,7 +180,7 @@ function updateCellContents(cell:Cell, update:HsCellUpdate) { function removeHover() { hoverInfoDiv.innerHTML = "" curHighlights.map(function (element) { - element.classList.remove("highlighted", "highlighted-leaf")}) + element.classList.remove("highlight-group", "highlight-leaf")}) curHighlights.length = 0 } function attachStatusHovertip(cell:Cell) { @@ -186,12 +193,13 @@ function attachStatusHovertip(cell:Cell) { } function attachHovertip(cell:Cell, srcId:SrcId) { let span = selectSpan(cell, srcId) - span.addEventListener("mouseover", function (event:Event) { - event.stopPropagation() - applyCellHover(cell, srcId)}) - span.addEventListener("mouseout" , function (event:Event) { - event.stopPropagation() - removeHover()}) + if (span !== null) { + span.addEventListener("mouseover", function (event:Event) { + event.stopPropagation() + applyCellHover(cell, srcId)}) + span.addEventListener("mouseout" , function (event:Event) { + event.stopPropagation() + removeHover()})} } function addChild(div:Div, className:string) : Div { const child = document.createElement("div") @@ -202,18 +210,9 @@ function addChild(div:Div, className:string) : Div { function appendText(div:Div, s:string) { div.appendChild(document.createTextNode(s)) } -function selectSpan(cell:Cell, srcId:SrcId) : Div { +function selectSpan(cell:Cell, srcId:SrcId) : Div | null { const spanId : string = "#span_".concat(cell.cellId.toString(), "_", srcId.toString()) - return cell.source.querySelector(spanId) ?? oops() -} -function addClassToSrcNode(cell: Cell, srcId:SrcId, className:string) { - // let span = getSpan(cellId, srcId) - // if (span !== undefined) { - // let [l, r] = span - // let spans = spansBetween(selectSpan(cellId, l), selectSpan(cellId, r)); - // spans.map(function (span) { - // span.classList.add(className) - // curHighlights.push(span)})} + return cell.source.querySelector(spanId) } function spansBetween(l:Div, r:Div) : Div[] { let spans : Div[] = [] @@ -269,9 +268,6 @@ function extendCellOutput(cell: Cell, outputs:HsRenderedOutput[]) { case "RenderedHtmlOut": addHTMLResult(cell, output.contents) break - case "RenderedSourceInfo": - extendSourceInfo(cell, output.contents) - break case "RenderedPassResult": // TODO: show passes break @@ -281,37 +277,93 @@ function extendCellOutput(cell: Cell, outputs:HsRenderedOutput[]) { case "RenderedError": const [maybeSrcId, errMsg] = output.contents if (maybeSrcId.tag == "Just") { - addClassToSrcNode(cell, maybeSrcId.contents, "err-span")} + const node : TreeNode = cell.treeMap.get(maybeSrcId.contents) ?? oops() + highlightTreeNode(false, node, "HighlightError")} addErrResult(cell, errMsg) break + case "RenderedTreeNodeUpdate": + output.contents.forEach(function (elt:[SrcId, HsTreeNodeMapUpdate]) { + const [srcId, update] = elt + applyTreeNodeUpdate(cell, srcId, update)}) + break + case "RenderedFocusUpdate": + output.contents.forEach(function (elt:[LexemeId, SrcId]) { + const [lexemeId, srcId] = elt + cell.focusMap.set(lexemeId, srcId)}) + break default: // nothing }}) } -function extendSourceInfo(cell: Cell, info: HsSourceInfo) { - switch (info.tag) { - case "SITypeInfo": - // TODO: this should really merge with previous rather than - // clobbering it completely but for now we only expect to do this - // once per cell so it's ok. - cell.hoverMap = new Map(info.contents) +function applyTreeNodeUpdate(cell:Cell, srcId:SrcId, update:HsTreeNodeMapUpdate) { + switch (update.tag) { + case "Create": // deliberate fallthrough + case "Replace": + const s : HsTreeNodeState = update.contents + const [l, r] = s.tnSpan + const range = computeRange(cell, l, r) + const treeNode : TreeNode = { + srcId : srcId, + span : range, + highlights : s.tnHighlights, + text : s.tnText} + cell.treeMap.set(srcId, treeNode) break - default: - // nothing + case "Update": + const nodeUpdate : HsTreeNodeUpdate = update.contents + const curTreeNode : TreeNode = cell.treeMap.get(srcId) ?? oops() + const text = nodeUpdate.tnuText + if (text.tag == "OverwriteWith") { + curTreeNode.text = text.contents} + const hl = nodeUpdate.tnuHighlights + if (hl.tag == "OverwriteWith") { + curTreeNode.highlights = hl.contents} + break} +} +function computeRange(cell:Cell, l:SrcId, r:SrcId) : [Div, Div] | null { + const lDiv = selectSpan(cell, l) + const rDiv = selectSpan(cell, r) + if (lDiv !== null && rDiv !== null) { + return [lDiv, rDiv] + } else { + return null} +} +function applyCellHover(cell:Cell, srcId:LexemeId) { + const focus : undefined | SrcId = cell.focusMap.get(srcId) + if (focus !== undefined) { + applyFocus(cell, focus) } } -function applyCellHover(cell:Cell, srcId:SrcId) { - applyHoverHighlights(cell, srcId) - const hoverInfo : undefined | HTMLString = cell.hoverMap.get(srcId) - if (hoverInfo !== undefined) { - setHoverInfo(hoverInfo)} +function applyFocus(cell:Cell, focus:SrcId) { + const focusNode : TreeNode = cell.treeMap.get(focus) ?? oops() + focusNode.highlights.forEach((h:Highlight) => { + const [sid, ty] = h + const node : TreeNode = cell.treeMap.get(sid) ?? oops() + highlightTreeNode(true, node, ty)}) + setHoverInfo(focusNode.text) } function setHoverInfo(s:string) { hoverInfoDiv.innerHTML = "" appendText(hoverInfoDiv, s) } -function applyHoverHighlights(cell:Cell, srcId:SrcId) { - // TODO +function computeHighlightClass(h:HighlightType) : string { + switch (h) { + case "HighlightGroup": + return "highlight-group" + case "HighlightLeaf": + return "highlight-leaf" + case "HighlightError": + return "highlight-error" + } +} +function highlightTreeNode(isTemporary: boolean, node: TreeNode, highlightType:HighlightType) { + const highlightClass : string = computeHighlightClass(highlightType) + if (node.span !== null) { + let [l, r] = node.span + let spans = spansBetween(l, r) + spans.map(function (span) { + span.classList.add(highlightClass) + if (isTemporary) {curHighlights.push(span)}})} } type RenderMode = "Static" | "Dynamic" function render(renderMode:RenderMode) { diff --git a/static/style.css b/static/style.css index cafc2f5d..a2860b23 100644 --- a/static/style.css +++ b/static/style.css @@ -43,25 +43,25 @@ body { font-family: monospace; white-space: pre; } - /* cell structure */ .cell { margin-left: 5px; display: flex; } -.line-nums, .contents { - font-family: monospace; - white-space: pre; -} .line-nums { flex: 0 0 3em; height: 100%; text-align: right; color: #808080; + font-family: monospace; + white-space: pre; } .contents { margin-left: 1em; + font-family: monospace; + white-space: pre; } + /* special results */ .err-result { font-weight: bold; @@ -69,19 +69,20 @@ body { } /* status colors */ +.status-inert {} .status-waiting {background-color: gray;} .status-running {background-color: lightblue;} .status-err {background-color: red;} -.status-inert {} .status-success {background-color: white;} -/* error highlighting */ -.err-span { +/* span highlighting */ +.highlight-error { text-decoration: red wavy underline; - text-decoration-skip-ink: none; -} + text-decoration-skip-ink: none;} +.highlight-group { background-color: yellow; } +.highlight-leaf { background-color: lightgray; } -/* Lexeme colors */ +/* lexeme colors */ .comment {color: gray;} .keyword {color: #0000DD;} .command {color: #A80000;} |