From 538724739be3df0c4de91e7c2680f3a9ceb6f0fa Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Thu, 20 Jun 2024 13:06:58 -0400 Subject: [PATCH] debugger stuff --- .../extensions/quick-reply/src/QuickReply.js | 145 ++++++++++++++++-- .../scripts/extensions/quick-reply/style.css | 62 +++++++- .../scripts/extensions/quick-reply/style.less | 61 +++++++- .../slash-commands/SlashCommandClosure.js | 5 +- .../SlashCommandDebugController.js | 11 ++ 5 files changed, 264 insertions(+), 20 deletions(-) diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index 0b034d385..83fa7acce 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -54,6 +54,7 @@ export class QuickReply { /**@type {HTMLTextAreaElement}*/ settingsDomMessage; /**@type {Popup}*/ editorPopup; + /**@type {HTMLElement}*/ editorDom; /**@type {HTMLElement}*/ editorExecuteBtn; /**@type {HTMLElement}*/ editorExecuteBtnPause; @@ -215,6 +216,7 @@ export class QuickReply { /**@type {HTMLElement} */ // @ts-ignore const dom = this.template.cloneNode(true); + this.editorDom = dom; this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); const popupResult = this.editorPopup.show(); @@ -614,6 +616,7 @@ export class QuickReply { } async executeFromEditor() { if (this.editorExecutePromise) return; + this.editorDom.classList.add('qr--isExecuting'); this.editorExecuteBtn.classList.add('qr--busy'); this.editorExecuteProgress.style.setProperty('--prog', '0'); this.editorExecuteErrors.classList.remove('qr--hasErrors'); @@ -628,26 +631,133 @@ export class QuickReply { this.editorPopup.dlg.classList.add('qr--hide'); } try { - // this.editorExecutePromise = this.execute({}, true); - // const result = await this.editorExecutePromise; this.abortController = new SlashCommandAbortController(); this.debugController = new SlashCommandDebugController(); this.debugController.onBreakPoint = async(closure, executor)=>{ - const vars = closure.scope.variables; - vars['#pipe'] = closure.scope.pipe; - let v = vars; - let s = closure.scope.parent; - while (s) { - v['#parent'] = s.variables; - v = v['#parent']; - v['#pipe'] = s.pipe; - s = s.parent; - } - this.editorDebugState.textContent = JSON.stringify(closure.scope.variables, (key, val)=>{ - if (val instanceof SlashCommandClosure) return val.toString(); - if (val === undefined) return null; - return val; - }, 2); + this.editorDebugState.innerHTML = ''; + let ci = -1; + const varNames = []; + const macroNames = []; + /** + * @param {SlashCommandScope} scope + */ + const buildVars = (scope, isCurrent = false)=>{ + if (!isCurrent) { + ci--; + } + const c = this.debugController.stack.slice(ci)[0]; + const wrap = document.createElement('div'); { + wrap.classList.add('qr--scope'); + const title = document.createElement('div'); { + title.classList.add('qr--title'); + title.textContent = isCurrent ? 'Current Scope' : 'Parent Scope'; + let hi; + title.addEventListener('pointerenter', ()=>{ + const loc = this.getEditorPosition(c.executorList[0].start, c.executorList.slice(-1)[0].end); + const layer = this.editorPopup.dlg.getBoundingClientRect(); + hi = document.createElement('div'); + hi.style.position = 'fixed'; + hi.style.left = `${loc.left - layer.left}px`; + hi.style.width = `${loc.right - loc.left}px`; + hi.style.top = `${loc.top - layer.top}px`; + hi.style.height = `${loc.bottom - loc.top}px`; + hi.style.zIndex = '50000'; + hi.style.pointerEvents = 'none'; + hi.style.border = '3px solid red'; + this.editorPopup.dlg.append(hi); + }); + title.addEventListener('pointerleave', ()=>hi?.remove()); + wrap.append(title); + } + for (const key of Object.keys(scope.variables)) { + const isHidden = varNames.includes(key); + if (!isHidden) varNames.push(key); + const item = document.createElement('div'); { + item.classList.add('qr--var'); + if (isHidden) item.classList.add('qr--isHidden'); + const k = document.createElement('div'); { + k.classList.add('qr--key'); + k.textContent = key; + item.append(k); + } + const v = document.createElement('div'); { + v.classList.add('qr--val'); + const val = scope.variables[key]; + if (val instanceof SlashCommandClosure) { + v.classList.add('qr--closure'); + v.title = val.rawText; + v.textContent = val.toString(); + } else if (val === undefined) { + v.classList.add('qr--undefined'); + v.textContent = 'undefined'; + } else { + v.textContent = val; + } + item.append(v); + } + wrap.append(item); + } + } + for (const key of Object.keys(scope.macros)) { + const isHidden = macroNames.includes(key); + if (!isHidden) macroNames.push(key); + const item = document.createElement('div'); { + item.classList.add('qr--macro'); + if (isHidden) item.classList.add('qr--isHidden'); + const k = document.createElement('div'); { + k.classList.add('qr--key'); + k.textContent = key; + item.append(k); + } + const v = document.createElement('div'); { + v.classList.add('qr--val'); + const val = scope.macros[key]; + if (val instanceof SlashCommandClosure) { + v.classList.add('qr--closure'); + v.title = val.rawText; + v.textContent = val.toString(); + } else if (val === undefined) { + v.classList.add('qr--undefined'); + v.textContent = 'undefined'; + } else { + v.textContent = val; + } + item.append(v); + } + wrap.append(item); + } + } + const pipeItem = document.createElement('div'); { + pipeItem.classList.add('qr--pipe'); + const k = document.createElement('div'); { + k.classList.add('qr--key'); + k.textContent = 'pipe'; + pipeItem.append(k); + } + const v = document.createElement('div'); { + v.classList.add('qr--val'); + const val = scope.pipe; + if (val instanceof SlashCommandClosure) { + v.classList.add('qr--closure'); + v.title = val.rawText; + v.textContent = val.toString(); + } else if (val === undefined) { + v.classList.add('qr--undefined'); + v.textContent = 'undefined'; + } else { + v.textContent = val; + } + pipeItem.append(v); + } + wrap.append(pipeItem); + } + if (scope.parent) { + wrap.append(buildVars(scope.parent)); + } + } + return wrap; + }; + this.editorDebugState.append(buildVars(closure.scope, true)); this.editorDebugState.classList.add('qr--active'); const loc = this.getEditorPosition(executor.start - 1, executor.end); const layer = this.editorPopup.dlg.getBoundingClientRect(); @@ -695,6 +805,7 @@ export class QuickReply { this.editorExecutePromise = null; this.editorExecuteBtn.classList.remove('qr--busy'); this.editorPopup.dlg.classList.remove('qr--hide'); + this.editorDom.classList.remove('qr--isExecuting'); } updateEditorProgress(done, total) { diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index 95f81396e..fa6e423de 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -238,6 +238,9 @@ .popup:has(#qr--modalEditor) { aspect-ratio: unset; } +.popup:has(#qr--modalEditor):has(.qr--isExecuting) .popup-controls { + display: none; +} .popup:has(#qr--modalEditor) .popup-content { display: flex; flex-direction: column; @@ -249,6 +252,16 @@ gap: 1em; overflow: hidden; } +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > h3:first-child, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--labels, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > h3:first-child, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > #qr--ctxEditor, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--qrOptions > .qr--ctxEditorActions + h3 + div { + display: none; +} .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main { flex: 1 1 auto; display: flex; @@ -485,8 +498,9 @@ display: none; text-align: left; font-size: smaller; + font-family: var(--monoFontFamily); color: white; - padding: 0.5em; + padding: 0.5em 0; overflow: auto; min-width: 100%; width: 0; @@ -495,6 +509,52 @@ .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState.qr--active { display: block; } +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope { + display: grid; + grid-template-columns: 0fr 1fr; + column-gap: 1em; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title { + grid-column: 1 / 3; + font-weight: bold; + background-color: var(--black50a); + padding: 0.25em; + margin-top: 0.5em; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe { + display: contents; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var.qr--isHidden .qr--val, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro.qr--isHidden .qr--val, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe.qr--isHidden .qr--val { + opacity: 0.5; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key { + margin-left: 0.5em; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key:after { + content: ": "; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:before, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:before { + content: "{{"; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe > .qr--key:after, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro > .qr--key:after { + content: "}}: "; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope { + grid-column: 1 / 3; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--scope .qr--pipe .qr--val { + opacity: 0.5; +} @keyframes qr--progressPulse { 0%, 100% { diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less index 8c7a78ec3..527dca2d6 100644 --- a/public/scripts/extensions/quick-reply/style.less +++ b/public/scripts/extensions/quick-reply/style.less @@ -262,6 +262,10 @@ .popup:has(#qr--modalEditor) { aspect-ratio: unset; + &:has(.qr--isExecuting) .popup-controls { + display: none; + } + .popup-content { display: flex; flex-direction: column; @@ -273,6 +277,21 @@ gap: 1em; overflow: hidden; + &.qr--isExecuting { + #qr--main > h3:first-child, + #qr--main > .qr--labels, + #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings, + #qr--qrOptions > h3:first-child, + #qr--qrOptions > #qr--ctxEditor, + #qr--qrOptions > .qr--ctxEditorActions, + #qr--qrOptions > .qr--ctxEditorActions + h3, + #qr--qrOptions > .qr--ctxEditorActions + h3 + div + { + display: none; + } + + } + > #qr--main { flex: 1 1 auto; display: flex; @@ -507,13 +526,53 @@ } text-align: left; font-size: smaller; + font-family: var(--monoFontFamily); // background-color: rgb(146, 190, 252); color: white; - padding: 0.5em; + padding: 0.5em 0; overflow: auto; min-width: 100%; width: 0; white-space: pre-wrap; + + .qr--scope { + display: grid; + grid-template-columns: 0fr 1fr; + column-gap: 1em; + .qr--title { + grid-column: 1 / 3; + font-weight: bold; + background-color: var(--black50a); + padding: 0.25em; + margin-top: 0.5em; + } + .qr--var, .qr--macro, .qr--pipe { + display: contents; + &.qr--isHidden { + .qr--key, .qr--val { + opacity: 0.5; + } + } + } + .qr--key { + margin-left: 0.5em; + &:after { content: ": "; } + } + .qr--pipe, .qr--macro { + > .qr--key { + &:before { content: "{{"; } + &:after { content: "}}: "; } + } + } + .qr--scope { + grid-column: 1 / 3; + .qr--pipe { + .qr--key, .qr--val { + opacity: 0.5; + } + } + } + } } } } diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index 0b0826c05..4a342076f 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -97,7 +97,7 @@ export class SlashCommandClosure { /** * - * @returns Promise + * @returns {Promise} */ async execute() { const closure = this.getCopy(); @@ -124,6 +124,7 @@ export class SlashCommandClosure { } async * executeDirect() { + this.debugController?.down(this); // closure arguments for (const arg of this.argumentList) { let v = arg.value; @@ -305,10 +306,12 @@ export class SlashCommandClosure { // if execution has returned a closure result, return that (should only happen on abort) if (step.value instanceof SlashCommandClosureResult) { + this.debugController?.up(); return step.value; } /**@type {SlashCommandClosureResult} */ const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe }); + this.debugController?.up(); return result; } async * executeStep() { diff --git a/public/scripts/slash-commands/SlashCommandDebugController.js b/public/scripts/slash-commands/SlashCommandDebugController.js index 54d778ee4..af4df2614 100644 --- a/public/scripts/slash-commands/SlashCommandDebugController.js +++ b/public/scripts/slash-commands/SlashCommandDebugController.js @@ -2,6 +2,7 @@ import { SlashCommandClosure } from './SlashCommandClosure.js'; import { SlashCommandExecutor } from './SlashCommandExecutor.js'; export class SlashCommandDebugController { + /**@type {SlashCommandClosure[]} */ stack = []; /**@type {boolean} */ isStepping = false; /**@type {boolean} */ isSteppingInto = false; @@ -12,6 +13,16 @@ export class SlashCommandDebugController { + + down(closure) { + this.stack.push(closure); + } + up() { + this.stack.pop(); + } + + + resume() { this.continueResolver?.(false); this.continuePromise = null;