From ef5d4e394b93621c0e022d21314e309ad1826ab4 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Sun, 23 Jun 2024 11:31:07 -0400 Subject: [PATCH] debugger --- .../extensions/quick-reply/src/QuickReply.js | 165 +++++++++++++++++- .../scripts/extensions/quick-reply/style.css | 53 +++++- .../scripts/extensions/quick-reply/style.less | 46 ++++- .../slash-commands/SlashCommandClosure.js | 28 ++- .../SlashCommandDebugController.js | 3 + 5 files changed, 286 insertions(+), 9 deletions(-) diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index fe6da8bc4..70f3f1fbf 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -618,14 +618,15 @@ export class QuickReply { } this.clone.style.position = 'fixed'; this.clone.style.visibility = 'hidden'; - document.body.append(this.clone); const mo = new MutationObserver(muts=>{ - if (muts.find(it=>Array.from(it.removedNodes).includes(this.editorMessage))) { + if (muts.find(it=>[...it.removedNodes].includes(this.editorMessage) || [...it.removedNodes].find(n=>n.contains(this.editorMessage)))) { this.clone.remove(); + this.clone = null; } }); - mo.observe(this.editorMessage.parentElement, { childList:true }); + mo.observe(document.body, { childList:true }); } + document.body.append(this.clone); this.clone.style.width = `${inputRect.width}px`; this.clone.style.height = `${inputRect.height}px`; this.clone.style.left = `${inputRect.left}px`; @@ -648,6 +649,7 @@ export class QuickReply { top: locatorRect.top, bottom: locatorRect.bottom, }; + // this.clone.remove(); return location; } async executeFromEditor() { @@ -689,6 +691,157 @@ export class QuickReply { const c = this.debugController.stack.slice(ci)[0]; const wrap = document.createElement('div'); { wrap.classList.add('qr--scope'); + if (isCurrent) { + const executor = this.debugController.cmdStack.slice(-1)[0]; + { + const namedTitle = document.createElement('div'); { + namedTitle.classList.add('qr--title'); + namedTitle.textContent = `Named Args - /${executor.name}`; + if (executor.command.name == 'run') { + namedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; + } + wrap.append(namedTitle); + } + const keys = new Set([...Object.keys(this.debugController.namedArguments ?? {}), ...(executor.namedArgumentList ?? []).map(it=>it.name)]); + for (const key of keys) { + if (key[0] == '_') continue; + const item = document.createElement('div'); { + item.classList.add('qr--var'); + const k = document.createElement('div'); { + k.classList.add('qr--key'); + k.textContent = key; + item.append(k); + } + const vUnresolved = document.createElement('div'); { + vUnresolved.classList.add('qr--val'); + vUnresolved.classList.add('qr--singleCol'); + const val = executor.namedArgumentList.find(it=>it.name == key)?.value; + if (val instanceof SlashCommandClosure) { + vUnresolved.classList.add('qr--closure'); + vUnresolved.title = val.rawText; + vUnresolved.textContent = val.toString(); + } else if (val === undefined) { + vUnresolved.classList.add('qr--undefined'); + vUnresolved.textContent = 'undefined'; + } else { + let jsonVal; + try { jsonVal = JSON.parse(val); } catch { /* empty */ } + if (jsonVal && typeof jsonVal == 'object') { + vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); + } else { + vUnresolved.textContent = val; + vUnresolved.classList.add('qr--simple'); + } + } + item.append(vUnresolved); + } + const vResolved = document.createElement('div'); { + vResolved.classList.add('qr--val'); + vResolved.classList.add('qr--singleCol'); + const val = this.debugController.namedArguments[key]; + if (val instanceof SlashCommandClosure) { + vResolved.classList.add('qr--closure'); + vResolved.title = val.rawText; + vResolved.textContent = val.toString(); + } else if (val === undefined) { + vResolved.classList.add('qr--undefined'); + vResolved.textContent = 'undefined'; + } else { + let jsonVal; + try { jsonVal = JSON.parse(val); } catch { /* empty */ } + if (jsonVal && typeof jsonVal == 'object') { + vResolved.textContent = JSON.stringify(jsonVal, null, 2); + } else { + vResolved.textContent = val; + vResolved.classList.add('qr--simple'); + } + } + item.append(vResolved); + } + wrap.append(item); + } + } + } + { + const unnamedTitle = document.createElement('div'); { + unnamedTitle.classList.add('qr--title'); + unnamedTitle.textContent = `Unnamed Args - /${executor.name}`; + if (executor.command.name == 'run') { + unnamedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; + } + wrap.append(unnamedTitle); + } + let i = 0; + let unnamed = this.debugController.unnamedArguments ?? []; + if (!Array.isArray(unnamed)) unnamed = [unnamed]; + while (unnamed.length < executor.unnamedArgumentList?.length ?? 0) unnamed.push(undefined); + unnamed = unnamed.map((it,idx)=>[executor.unnamedArgumentList?.[idx], it]); + for (const arg of unnamed) { + i++; + const item = document.createElement('div'); { + item.classList.add('qr--var'); + const k = document.createElement('div'); { + k.classList.add('qr--key'); + k.textContent = i.toString(); + item.append(k); + } + const vUnresolved = document.createElement('div'); { + vUnresolved.classList.add('qr--val'); + vUnresolved.classList.add('qr--singleCol'); + const val = arg[0]?.value; + if (val instanceof SlashCommandClosure) { + vUnresolved.classList.add('qr--closure'); + vUnresolved.title = val.rawText; + vUnresolved.textContent = val.toString(); + } else if (val === undefined) { + vUnresolved.classList.add('qr--undefined'); + vUnresolved.textContent = 'undefined'; + } else { + let jsonVal; + try { jsonVal = JSON.parse(val); } catch { /* empty */ } + if (jsonVal && typeof jsonVal == 'object') { + vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); + } else { + vUnresolved.textContent = val; + vUnresolved.classList.add('qr--simple'); + } + } + item.append(vUnresolved); + } + const vResolved = document.createElement('div'); { + vResolved.classList.add('qr--val'); + vResolved.classList.add('qr--singleCol'); + if (this.debugController.unnamedArguments === undefined) { + vResolved.classList.add('qr--unresolved'); + } else if ((Array.isArray(this.debugController.unnamedArguments) ? this.debugController.unnamedArguments : [this.debugController.unnamedArguments]).length < i) { + // do nothing + } else { + const val = arg[1]; + if (val instanceof SlashCommandClosure) { + vResolved.classList.add('qr--closure'); + vResolved.title = val.rawText; + vResolved.textContent = val.toString(); + } else if (val === undefined) { + vResolved.classList.add('qr--undefined'); + vResolved.textContent = 'undefined'; + } else { + let jsonVal; + try { jsonVal = JSON.parse(val); } catch { /* empty */ } + if (jsonVal && typeof jsonVal == 'object') { + vResolved.textContent = JSON.stringify(jsonVal, null, 2); + } else { + vResolved.textContent = val; + vResolved.classList.add('qr--simple'); + } + } + } + item.append(vResolved); + } + wrap.append(item); + } + } + } + } const title = document.createElement('div'); { title.classList.add('qr--title'); title.textContent = isCurrent ? 'Current Scope' : 'Parent Scope'; @@ -735,6 +888,7 @@ export class QuickReply { v.textContent = JSON.stringify(jsonVal, null, 2); } else { v.textContent = val; + v.classList.add('qr--simple'); } } item.append(v); @@ -770,6 +924,7 @@ export class QuickReply { v.textContent = JSON.stringify(jsonVal, null, 2); } else { v.textContent = val; + v.classList.add('qr--simple'); } } item.append(v); @@ -801,6 +956,7 @@ export class QuickReply { v.textContent = JSON.stringify(jsonVal, null, 2); } else { v.textContent = val; + v.classList.add('qr--simple'); } } pipeItem.append(v); @@ -854,6 +1010,9 @@ export class QuickReply { const layer = this.editorPopup.dlg.getBoundingClientRect(); const hi = document.createElement('div'); hi.classList.add('qr--highlight'); + if (this.debugController.namedArguments === undefined) { + hi.classList.add('qr--unresolved'); + } hi.style.left = `${loc.left - layer.left}px`; hi.style.width = `${loc.right - loc.left}px`; hi.style.top = `${loc.top - layer.top}px`; diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index eb8abc9d3..7a3455fd3 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -245,6 +245,9 @@ position: fixed; z-index: 50000; pointer-events: none; + background-color: rgba(47, 150, 180, 0.5); +} +.popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight.qr--unresolved { background-color: rgba(255, 255, 0, 0.5); } .popup:has(#qr--modalEditor):has(.qr--isExecuting) .qr--highlight-secondary { @@ -559,11 +562,11 @@ } .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope { display: grid; - grid-template-columns: 0fr 1fr; + grid-template-columns: 0fr 1fr 1fr; column-gap: 0em; } .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title { - grid-column: 1 / 3; + grid-column: 1 / 4; font-weight: bold; font-family: var(--mainFontFamily); background-color: var(--black50a); @@ -583,6 +586,26 @@ .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val { background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); } +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:nth-child(2n), +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:nth-child(2n), +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:nth-child(2n) { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 1) .qr--val:hover, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 1) .qr--val:hover, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 1) .qr--val:hover { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:nth-child(2n), +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:nth-child(2n), +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:nth-child(2n) { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n) .qr--val:hover, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n) .qr--val:hover, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n) .qr--val:hover { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); +} .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, @@ -591,6 +614,32 @@ .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--var .qr--val, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val { + grid-column: 2 / 4; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--singleCol, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--singleCol, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--singleCol { + grid-column: unset; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:before, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:before, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:before, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--simple:after, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--simple:after, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--simple:after { + content: '"'; + color: var(--SmartThemeQuoteColor); +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var .qr--val.qr--unresolved:after, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro .qr--val.qr--unresolved:after, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe .qr--val.qr--unresolved:after { + content: '-UNRESOLVED-'; + font-style: italic; + color: var(--SmartThemeQuoteColor); +} .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key { margin-left: 0.5em; padding-right: 1em; diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less index 09ad2a00e..f66206265 100644 --- a/public/scripts/extensions/quick-reply/style.less +++ b/public/scripts/extensions/quick-reply/style.less @@ -271,7 +271,10 @@ position: fixed; z-index: 50000; pointer-events: none; - background-color: rgba(255, 255, 0, 0.5); + background-color: rgb(47 150 180 / 0.5); + &.qr--unresolved { + background-color: rgb(255 255 0 / 0.5); + } } .qr--highlight-secondary { position: fixed; @@ -588,10 +591,10 @@ .qr--scope { display: grid; - grid-template-columns: 0fr 1fr; + grid-template-columns: 0fr 1fr 1fr; column-gap: 0em; .qr--title { - grid-column: 1 / 3; + grid-column: 1 / 4; font-weight: bold; font-family: var(--mainFontFamily); background-color: var(--black50a); @@ -604,12 +607,49 @@ .qr--key, .qr--val { background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); } + .qr--val { + &:nth-child(2n) { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125); + } + &:hover { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); + } + } + } + &:nth-child(2n) { + .qr--val { + &:nth-child(2n) { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625); + } + &:hover { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5); + } + } } &.qr--isHidden { .qr--key, .qr--val { opacity: 0.5; } } + .qr--val { + grid-column: 2 / 4; + &.qr--singleCol { + grid-column: unset; + } + &.qr--simple { + &:before, &:after { + content: '"'; + color: var(--SmartThemeQuoteColor); + } + } + &.qr--unresolved { + &:after { + content: '-UNRESOLVED-'; + font-style: italic; + color: var(--SmartThemeQuoteColor); + } + } + } } .qr--key { margin-left: 0.5em; diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index c79be25cb..fff437d1d 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -187,11 +187,29 @@ export class SlashCommandClosure { if (this.debugController) { // "execute" breakpoint step = await stepper.next(); + step = await stepper.next(); // get next executor step = await stepper.next(); - this.debugController.isStepping = yield { closure:this, executor:step.value }; + const hasImmediateClosureInNamedArgs = step.value.namedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.executeNow); + const hasImmediateClosureInUnnamedArgs = step.value.unnamedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.executeNow); + if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) { + this.debugController.isStepping = yield { closure:this, executor:step.value }; + } else { + this.debugController.isStepping = true; + this.debugController.stepStack[this.debugController.stepStack.length - 1] = true; + } } } else if (!step.done && this.debugController?.testStepping(this)) { + this.debugController.isSteppingInto = false; + const hasImmediateClosureInNamedArgs = step.value.namedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); + const hasImmediateClosureInUnnamedArgs = step.value.unnamedArgumentList.find(it=>it.value instanceof SlashCommandClosure && it.value.executeNow); + if (hasImmediateClosureInNamedArgs || hasImmediateClosureInUnnamedArgs) { + this.debugController.isStepping = yield { closure:this, executor:step.value }; + } + } + // resolve args + step = await stepper.next(); + if (!step.done && this.debugController?.testStepping(this)) { this.debugController.isSteppingInto = false; this.debugController.isStepping = yield { closure:this, executor:step.value }; } @@ -225,6 +243,7 @@ export class SlashCommandClosure { } else if (executor instanceof SlashCommandBreakPoint) { // no execution for breakpoints, just raise counter done++; + yield executor; } else { /**@type {import('./SlashCommand.js').NamedArguments} */ let args = { @@ -310,6 +329,11 @@ export class SlashCommandClosure { if (abortResult) { return abortResult; } + if (this.debugController) { + this.debugController.namedArguments = args; + this.debugController.unnamedArguments = value ?? ''; + } + yield executor; executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount); const isStepping = this.debugController?.testStepping(this); if (this.debugController) { @@ -317,6 +341,8 @@ export class SlashCommandClosure { } this.scope.pipe = await executor.command.callback(args, value ?? ''); if (this.debugController) { + this.debugController.namedArguments = undefined; + this.debugController.unnamedArguments = undefined; this.debugController.isStepping = isStepping; } this.#lintPipe(executor.command); diff --git a/public/scripts/slash-commands/SlashCommandDebugController.js b/public/scripts/slash-commands/SlashCommandDebugController.js index f6ae407c2..11754a8a6 100644 --- a/public/scripts/slash-commands/SlashCommandDebugController.js +++ b/public/scripts/slash-commands/SlashCommandDebugController.js @@ -9,6 +9,9 @@ export class SlashCommandDebugController { /**@type {boolean} */ isSteppingInto = false; /**@type {boolean} */ isSteppingOut = false; + /**@type {object} */ namedArguments; + /**@type {string|SlashCommandClosure|(string|SlashCommandClosure)[]} */ unnamedArguments; + /**@type {Promise} */ continuePromise; /**@type {(boolean)=>void} */ continueResolver;