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=>{