diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html index d7dd85b00..80aab2386 100644 --- a/public/scripts/extensions/quick-reply/html/qrEditor.html +++ b/public/scripts/extensions/quick-reply/html/qrEditor.html @@ -116,17 +116,6 @@ -
- - - -
+ +
+ + + + +
diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index 83fa7acce..746e98af6 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -564,6 +564,11 @@ export class QuickReply { stepIntoBtn.addEventListener('click', ()=>{ this.debugController?.stepInto(); }); + /**@type {HTMLElement}*/ + const stepOutBtn = dom.querySelector('#qr--modal-stepOut'); + stepOutBtn.addEventListener('click', ()=>{ + this.debugController?.stepOut(); + }); await popupResult; @@ -653,7 +658,7 @@ export class QuickReply { 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 loc = this.getEditorPosition(c.executorList[0].start - 1, c.executorList.slice(-1)[0].end); const layer = this.editorPopup.dlg.getBoundingClientRect(); hi = document.createElement('div'); hi.style.position = 'fixed'; diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index fa6e423de..9098bcbf9 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -262,6 +262,9 @@ .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--isExecuting #qr--modal-debugButtons { + display: flex; +} .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main { flex: 1 1 auto; display: flex; @@ -376,6 +379,9 @@ border: 1px solid var(--SmartThemeBorderColor); border-radius: 5px; } +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor label:has(#qr--modal-executeHide) { + display: none; +} .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons { display: flex; gap: 1em; @@ -424,9 +430,21 @@ border-color: #d78872; } .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons { - display: flex; + display: none; gap: 1em; } +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton { + aspect-ratio: 1.25 / 1; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton.qr--glyph-combo { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; +} +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons .qr--modal-debugButton.qr--glyph-combo .qr--glyph { + grid-column: 1; + line-height: 0.8; +} .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress { --prog: 0; --progColor: #92befc; @@ -512,7 +530,7 @@ .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope { display: grid; grid-template-columns: 0fr 1fr; - column-gap: 1em; + column-gap: 0em; } .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--title { grid-column: 1 / 3; @@ -526,6 +544,14 @@ .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:nth-child(2n + 2) .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 2) .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 2) .qr--key, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--var:nth-child(2n + 2) .qr--val, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--macro:nth-child(2n + 2) .qr--val, +.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--pipe:nth-child(2n + 2) .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.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, @@ -536,6 +562,7 @@ } .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key { margin-left: 0.5em; + padding-right: 1em; } .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState .qr--scope .qr--key:after { content: ": "; diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less index 527dca2d6..a63fb4229 100644 --- a/public/scripts/extensions/quick-reply/style.less +++ b/public/scripts/extensions/quick-reply/style.less @@ -289,7 +289,9 @@ { display: none; } - + #qr--modal-debugButtons { + display: flex; + } } > #qr--main { @@ -403,6 +405,10 @@ } } + label:has(#qr--modal-executeHide) { + // hide editor is not working anyways + display: none; + } #qr--modal-executeButtons { display: flex; gap: 1em; @@ -451,8 +457,20 @@ } } #qr--modal-debugButtons { - display: flex; + display: none; gap: 1em; + .qr--modal-debugButton { + aspect-ratio: 1.25 / 1; + &.qr--glyph-combo { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; + .qr--glyph { + grid-column: 1; + line-height: 0.8; + } + } + } } #qr--modal-executeProgress { --prog: 0; @@ -538,7 +556,7 @@ .qr--scope { display: grid; grid-template-columns: 0fr 1fr; - column-gap: 1em; + column-gap: 0em; .qr--title { grid-column: 1 / 3; font-weight: bold; @@ -548,6 +566,11 @@ } .qr--var, .qr--macro, .qr--pipe { display: contents; + &:nth-child(2n + 2) { + .qr--key, .qr--val { + background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25); + } + } &.qr--isHidden { .qr--key, .qr--val { opacity: 0.5; @@ -556,6 +579,7 @@ } .qr--key { margin-left: 0.5em; + padding-right: 1em; &:after { content: ": "; } } .qr--pipe, .qr--macro { diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index 4a342076f..80902e60c 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -104,7 +104,7 @@ export class SlashCommandClosure { const gen = closure.executeDirect(); let step; while (!step?.done) { - step = await gen.next(this.debugController?.isStepping ?? false); + step = await gen.next(this.debugController?.testStepping(this) ?? false); if (!(step.value instanceof SlashCommandClosureResult) && this.debugController) { this.debugController.isStepping = await this.debugController.awaitBreakPoint(step.value.closure, step.value.executor); } @@ -117,7 +117,7 @@ export class SlashCommandClosure { const gen = closure.executeDirect(); let step; while (!step?.done) { - step = await gen.next(this.debugController?.isStepping); + step = await gen.next(this.debugController?.testStepping(this)); this.debugController.isStepping = yield step.value; } return step.value; @@ -177,111 +177,6 @@ export class SlashCommandClosure { if (this.executorList.length == 0) { this.scope.pipe = ''; } - for (const executor of [] ?? this.executorList) { - this.onProgress?.(done, this.commandCount); - if (executor instanceof SlashCommandClosureExecutor) { - const closure = this.scope.getVariable(executor.name); - if (!closure || !(closure instanceof SlashCommandClosure)) throw new Error(`${executor.name} is not a closure.`); - closure.scope.parent = this.scope; - closure.providedArgumentList = executor.providedArgumentList; - const result = await closure.execute(); - this.scope.pipe = result.pipe; - } else { - /**@type {import('./SlashCommand.js').NamedArguments} */ - let args = { - _scope: this.scope, - _parserFlags: executor.parserFlags, - _abortController: this.abortController, - _hasUnnamedArgument: executor.unnamedArgumentList.length > 0, - }; - let value; - // substitute named arguments - for (const arg of executor.namedArgumentList) { - if (arg.value instanceof SlashCommandClosure) { - /**@type {SlashCommandClosure}*/ - const closure = arg.value; - closure.scope.parent = this.scope; - if (closure.executeNow) { - args[arg.name] = (await closure.execute())?.pipe; - } else { - args[arg.name] = closure; - } - } else { - args[arg.name] = this.substituteParams(arg.value); - } - // unescape named argument - if (typeof args[arg.name] == 'string') { - args[arg.name] = args[arg.name] - ?.replace(/\\\{/g, '{') - ?.replace(/\\\}/g, '}') - ; - } - } - - // substitute unnamed argument - if (executor.unnamedArgumentList.length == 0) { - if (executor.injectPipe) { - value = this.scope.pipe; - args._hasUnnamedArgument = this.scope.pipe !== null && this.scope.pipe !== undefined; - } - } else { - value = []; - for (let i = 0; i < executor.unnamedArgumentList.length; i++) { - let v = executor.unnamedArgumentList[i].value; - if (v instanceof SlashCommandClosure) { - /**@type {SlashCommandClosure}*/ - const closure = v; - closure.scope.parent = this.scope; - if (closure.executeNow) { - v = (await closure.execute())?.pipe; - } else { - v = closure; - } - } else { - v = this.substituteParams(v); - } - value[i] = v; - } - if (!executor.command.splitUnnamedArgument) { - if (value.length == 1) { - value = value[0]; - } else if (!value.find(it=>it instanceof SlashCommandClosure)) { - value = value.join(''); - } - } - } - // unescape unnamed argument - if (typeof value == 'string') { - value = value - ?.replace(/\\\{/g, '{') - ?.replace(/\\\}/g, '}') - ; - } else if (Array.isArray(value)) { - value = value.map(v=>{ - if (typeof v == 'string') { - return v - ?.replace(/\\\{/g, '{') - ?.replace(/\\\}/g, '}'); - } - return v; - }); - } - - let abortResult = await this.testAbortController(); - if (abortResult) { - return abortResult; - } - executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount); - this.scope.pipe = await executor.command.callback(args, value ?? ''); - this.#lintPipe(executor.command); - done += executor.commandCount; - this.onProgress?.(done, this.commandCount); - abortResult = await this.testAbortController(); - if (abortResult) { - return abortResult; - } - } - } const stepper = this.executeStep(); let step; while (!step?.done) { @@ -296,7 +191,7 @@ export class SlashCommandClosure { step = await stepper.next(); this.debugController.isStepping = yield { closure:this, executor:step.value }; } - } else if (!step.done && this.debugController?.isStepping) { + } else if (!step.done && this.debugController?.testStepping(this)) { this.debugController.isSteppingInto = false; this.debugController.isStepping = yield { closure:this, executor:step.value }; } @@ -415,7 +310,7 @@ export class SlashCommandClosure { return abortResult; } executor.onProgress = (subDone, subTotal)=>this.onProgress?.(done + subDone, this.commandCount); - const isStepping = this.debugController?.isStepping; + const isStepping = this.debugController?.testStepping(this); if (this.debugController) { this.debugController.isStepping = false || this.debugController.isSteppingInto; } diff --git a/public/scripts/slash-commands/SlashCommandDebugController.js b/public/scripts/slash-commands/SlashCommandDebugController.js index af4df2614..fa47e3b9b 100644 --- a/public/scripts/slash-commands/SlashCommandDebugController.js +++ b/public/scripts/slash-commands/SlashCommandDebugController.js @@ -3,8 +3,10 @@ import { SlashCommandExecutor } from './SlashCommandExecutor.js'; export class SlashCommandDebugController { /**@type {SlashCommandClosure[]} */ stack = []; + /**@type {boolean[]} */ stepStack = []; /**@type {boolean} */ isStepping = false; /**@type {boolean} */ isSteppingInto = false; + /**@type {boolean} */ isSteppingOut = false; /**@type {Promise} */ continuePromise; /**@type {(boolean)=>void} */ continueResolver; @@ -14,11 +16,22 @@ export class SlashCommandDebugController { + testStepping(closure) { + return this.stepStack[this.stack.indexOf(closure)]; + } + + + + down(closure) { this.stack.push(closure); + if (this.stepStack.length < this.stack.length) { + this.stepStack.push(this.isSteppingInto); + } } up() { this.stack.pop(); + this.stepStack.pop(); } @@ -26,16 +39,25 @@ export class SlashCommandDebugController { resume() { this.continueResolver?.(false); this.continuePromise = null; + this.stepStack.forEach((_,idx)=>this.stepStack[idx] = false); } step() { + this.stepStack[this.stepStack.length - 1] = true; this.continueResolver?.(true); this.continuePromise = null; } stepInto() { this.isSteppingInto = true; + this.stepStack.forEach((_,idx)=>this.stepStack[idx] = true); this.continueResolver?.(true); this.continuePromise = null; } + stepOut() { + this.isSteppingOut = true; + this.stepStack[this.stepStack.length - 1] = false; + this.continueResolver?.(false); + this.continuePromise = null; + } async awaitContinue() { this.continuePromise ??= new Promise(resolve=>{