debugger basics rough
This commit is contained in:
parent
dcbadcb5f9
commit
eb02ca95f9
|
@ -116,6 +116,17 @@
|
|||
<i class="fa-solid fa-stop"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="qr--modal-debugButtons">
|
||||
<div id="qr--modal-resume" class="qr--modal-debugButton menu_button">
|
||||
Resume
|
||||
</div>
|
||||
<div id="qr--modal-step" class="qr--modal-debugButton menu_button">
|
||||
Step
|
||||
</div>
|
||||
<div id="qr--modal-stepInto" class="qr--modal-debugButton menu_button">
|
||||
StepInto
|
||||
</div>
|
||||
</div>
|
||||
<div id="qr--modal-executeProgress"></div>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-executeHide">
|
||||
|
@ -123,5 +134,6 @@
|
|||
</label>
|
||||
<div id="qr--modal-executeErrors"></div>
|
||||
<div id="qr--modal-executeResult"></div>
|
||||
<div id="qr--modal-debugState"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { POPUP_TYPE, Popup } from '../../../popup.js';
|
||||
import { setSlashCommandAutoComplete } from '../../../slash-commands.js';
|
||||
import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js';
|
||||
import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js';
|
||||
import { SlashCommandClosureResult } from '../../../slash-commands/SlashCommandClosureResult.js';
|
||||
import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js';
|
||||
import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecutor.js';
|
||||
import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js';
|
||||
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||
import { debounce, getSortableDelay } from '../../../utils.js';
|
||||
|
@ -38,6 +42,7 @@ export class QuickReply {
|
|||
/**@type {String}*/ automationId = '';
|
||||
|
||||
/**@type {Function}*/ onExecute;
|
||||
/**@type {(qr:QuickReply)=>AsyncGenerator<SlashCommandClosureResult|{closure:SlashCommandClosure, executor:SlashCommandExecutor|SlashCommandClosureResult}, SlashCommandClosureResult, boolean>}*/ onDebug;
|
||||
/**@type {Function}*/ onDelete;
|
||||
/**@type {Function}*/ onUpdate;
|
||||
|
||||
|
@ -56,9 +61,11 @@ export class QuickReply {
|
|||
/**@type {HTMLElement}*/ editorExecuteProgress;
|
||||
/**@type {HTMLElement}*/ editorExecuteErrors;
|
||||
/**@type {HTMLElement}*/ editorExecuteResult;
|
||||
/**@type {HTMLElement}*/ editorDebugState;
|
||||
/**@type {HTMLInputElement}*/ editorExecuteHide;
|
||||
/**@type {Promise}*/ editorExecutePromise;
|
||||
/**@type {SlashCommandAbortController}*/ abortController;
|
||||
/**@type {SlashCommandDebugController}*/ debugController;
|
||||
|
||||
|
||||
get hasContext() {
|
||||
|
@ -298,6 +305,7 @@ export class QuickReply {
|
|||
});
|
||||
/**@type {HTMLTextAreaElement}*/
|
||||
const message = dom.querySelector('#qr--modal-message');
|
||||
this.editorMessage = message;
|
||||
message.value = this.message;
|
||||
message.addEventListener('input', () => {
|
||||
updateSyntax();
|
||||
|
@ -506,6 +514,9 @@ export class QuickReply {
|
|||
/**@type {HTMLElement}*/
|
||||
const executeResult = dom.querySelector('#qr--modal-executeResult');
|
||||
this.editorExecuteResult = executeResult;
|
||||
/**@type {HTMLElement}*/
|
||||
const debugState = dom.querySelector('#qr--modal-debugState');
|
||||
this.editorDebugState = debugState;
|
||||
/**@type {HTMLInputElement}*/
|
||||
const executeHide = dom.querySelector('#qr--modal-executeHide');
|
||||
this.editorExecuteHide = executeHide;
|
||||
|
@ -536,6 +547,22 @@ export class QuickReply {
|
|||
this.abortController?.abort('Stop button clicked');
|
||||
});
|
||||
|
||||
/**@type {HTMLElement}*/
|
||||
const resumeBtn = dom.querySelector('#qr--modal-resume');
|
||||
resumeBtn.addEventListener('click', ()=>{
|
||||
this.debugController?.resume();
|
||||
});
|
||||
/**@type {HTMLElement}*/
|
||||
const stepBtn = dom.querySelector('#qr--modal-step');
|
||||
stepBtn.addEventListener('click', ()=>{
|
||||
this.debugController?.step();
|
||||
});
|
||||
/**@type {HTMLElement}*/
|
||||
const stepIntoBtn = dom.querySelector('#qr--modal-stepInto');
|
||||
stepIntoBtn.addEventListener('click', ()=>{
|
||||
this.debugController?.stepInto();
|
||||
});
|
||||
|
||||
await popupResult;
|
||||
|
||||
window.removeEventListener('resize', resizeListener);
|
||||
|
@ -544,6 +571,47 @@ export class QuickReply {
|
|||
}
|
||||
}
|
||||
|
||||
getEditorPosition(start, end) {
|
||||
const inputRect = this.editorMessage.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(this.editorMessage);
|
||||
if (!this.clone) {
|
||||
this.clone = document.createElement('div');
|
||||
for (const key of style) {
|
||||
this.clone.style[key] = style[key];
|
||||
}
|
||||
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))) {
|
||||
this.clone.remove();
|
||||
}
|
||||
});
|
||||
mo.observe(this.editorMessage.parentElement, { childList:true });
|
||||
}
|
||||
this.clone.style.height = `${inputRect.height}px`;
|
||||
this.clone.style.left = `${inputRect.left}px`;
|
||||
this.clone.style.top = `${inputRect.top}px`;
|
||||
this.clone.style.whiteSpace = style.whiteSpace;
|
||||
this.clone.style.tabSize = style.tabSize;
|
||||
const text = this.editorMessage.value;
|
||||
const before = text.slice(0, start);
|
||||
this.clone.textContent = before;
|
||||
const locator = document.createElement('span');
|
||||
locator.textContent = text.slice(start, end);
|
||||
this.clone.append(locator);
|
||||
this.clone.append(text.slice(end));
|
||||
this.clone.scrollTop = this.editorMessage.scrollTop;
|
||||
this.clone.scrollLeft = this.editorMessage.scrollLeft;
|
||||
const locatorRect = locator.getBoundingClientRect();
|
||||
const location = {
|
||||
left: locatorRect.left,
|
||||
right: locatorRect.right,
|
||||
top: locatorRect.top,
|
||||
bottom: locatorRect.bottom,
|
||||
};
|
||||
return location;
|
||||
}
|
||||
async executeFromEditor() {
|
||||
if (this.editorExecutePromise) return;
|
||||
this.editorExecuteBtn.classList.add('qr--busy');
|
||||
|
@ -560,8 +628,44 @@ export class QuickReply {
|
|||
this.editorPopup.dom.classList.add('qr--hide');
|
||||
}
|
||||
try {
|
||||
this.editorExecutePromise = this.execute({}, true);
|
||||
const result = await this.editorExecutePromise;
|
||||
// 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();
|
||||
return val;
|
||||
}, 2);
|
||||
this.editorDebugState.classList.add('qr--active');
|
||||
const loc = this.getEditorPosition(executor.start - 1, executor.end);
|
||||
const hi = document.createElement('div');
|
||||
hi.style.position = 'fixed';
|
||||
hi.style.left = `${loc.left}px`;
|
||||
hi.style.width = `${loc.right - loc.left}px`;
|
||||
hi.style.top = `${loc.top}px`;
|
||||
hi.style.height = `${loc.bottom - loc.top}px`;
|
||||
hi.style.zIndex = '50000';
|
||||
hi.style.pointerEvents = 'none';
|
||||
hi.style.backgroundColor = 'rgb(255 255 0 / 0.5)';
|
||||
document.body.append(hi);
|
||||
const isStepping = await this.debugController.awaitContinue();
|
||||
hi.remove();
|
||||
this.editorDebugState.textContent = '';
|
||||
this.editorDebugState.classList.remove('qr--active');
|
||||
return isStepping;
|
||||
};
|
||||
const result = await this.onDebug(this);
|
||||
if (this.abortController?.signal?.aborted) {
|
||||
this.editorExecuteProgress.classList.add('qr--aborted');
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { getRequestHeaders, substituteParams } from '../../../../script.js';
|
||||
import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
|
||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
|
||||
import { debounceAsync, warn } from '../index.js';
|
||||
import { QuickReply } from './QuickReply.js';
|
||||
|
@ -100,6 +101,26 @@ export class QuickReplySet {
|
|||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {QuickReply} qr
|
||||
*/
|
||||
async debug(qr) {
|
||||
const parser = new SlashCommandParser();
|
||||
const closure = parser.parse(qr.message, true, [], qr.abortController, qr.debugController);
|
||||
closure.onProgress = (done, total) => qr.updateEditorProgress(done, total);
|
||||
// closure.abortController = qr.abortController;
|
||||
// closure.debugController = qr.debugController;
|
||||
// const stepper = closure.executeGenerator();
|
||||
// let step;
|
||||
// let isStepping = false;
|
||||
// while (!step?.done) {
|
||||
// step = await stepper.next(isStepping);
|
||||
// isStepping = yield(step.value);
|
||||
// }
|
||||
// return step.value;
|
||||
return (await closure.execute())?.pipe;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {QuickReply} qr The QR to execute.
|
||||
|
@ -195,7 +216,12 @@ export class QuickReplySet {
|
|||
return qr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {QuickReply} qr
|
||||
*/
|
||||
hookQuickReply(qr) {
|
||||
qr.onDebug = ()=>this.debug(qr);
|
||||
qr.onExecute = (_, options)=>this.executeWithOptions(qr, options);
|
||||
qr.onDelete = ()=>this.removeQuickReply(qr);
|
||||
qr.onUpdate = ()=>this.save();
|
||||
|
|
|
@ -301,19 +301,19 @@
|
|||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-messageSyntax {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-messageSyntax {
|
||||
display: none;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message {
|
||||
background-color: var(--ac-style-color-background);
|
||||
color: var(--ac-style-color-text);
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||
color: unset;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
}
|
||||
@supports (color: rgb(from white r g b / 0.25)) {
|
||||
.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||
}
|
||||
}
|
||||
|
@ -343,12 +343,12 @@
|
|||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
|
||||
color: transparent;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
}
|
||||
@supports (color: rgb(from white r g b / 0.25)) {
|
||||
.dialogue_popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
|
||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||
}
|
||||
}
|
||||
|
@ -410,6 +410,10 @@
|
|||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||
border-color: #d78872;
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugButtons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
|
||||
--prog: 0;
|
||||
--progColor: #92befc;
|
||||
|
@ -469,6 +473,7 @@
|
|||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
||||
display: block;
|
||||
|
@ -476,6 +481,20 @@
|
|||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
||||
content: 'Result: ';
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState {
|
||||
display: none;
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-debugState.qr--active {
|
||||
display: block;
|
||||
}
|
||||
@keyframes qr--progressPulse {
|
||||
0%,
|
||||
100% {
|
||||
|
|
|
@ -431,6 +431,10 @@
|
|||
border-color: rgb(215, 136, 114);
|
||||
}
|
||||
}
|
||||
#qr--modal-debugButtons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
#qr--modal-executeProgress {
|
||||
--prog: 0;
|
||||
--progColor: rgb(146, 190, 252);
|
||||
|
@ -494,6 +498,22 @@
|
|||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#qr--modal-debugState {
|
||||
display: none;
|
||||
&.qr--active {
|
||||
display: block;
|
||||
}
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
// background-color: rgb(146, 190, 252);
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
|
|||
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
||||
import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
|
||||
export {
|
||||
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
||||
};
|
||||
|
@ -3004,6 +3005,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
|
|||
* @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
|
||||
* @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply
|
||||
* @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
|
||||
* @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
|
||||
* @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
|
||||
*/
|
||||
|
||||
|
@ -3096,6 +3098,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||
handleExecutionErrors: false,
|
||||
parserFlags: null,
|
||||
abortController: null,
|
||||
debugController: null,
|
||||
onProgress: null,
|
||||
}, options);
|
||||
|
||||
|
@ -3104,6 +3107,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
|||
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
||||
closure.scope.parent = options.scope;
|
||||
closure.onProgress = options.onProgress;
|
||||
closure.debugController = options.debugController;
|
||||
} catch (e) {
|
||||
if (options.handleParserErrors && e instanceof SlashCommandParserError) {
|
||||
/**@type {SlashCommandParserError}*/
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
|
||||
export class SlashCommandBreakPoint extends SlashCommandExecutor {}
|
|
@ -2,8 +2,10 @@ import { substituteParams } from '../../script.js';
|
|||
import { delay, escapeRegex } from '../utils.js';
|
||||
import { SlashCommand } from './SlashCommand.js';
|
||||
import { SlashCommandAbortController } from './SlashCommandAbortController.js';
|
||||
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||
import { SlashCommandClosureExecutor } from './SlashCommandClosureExecutor.js';
|
||||
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
|
||||
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
|
||||
import { SlashCommandScope } from './SlashCommandScope.js';
|
||||
|
@ -17,6 +19,7 @@ export class SlashCommandClosure {
|
|||
/**@type {SlashCommandNamedArgumentAssignment[]}*/ providedArgumentList = [];
|
||||
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
||||
/**@type {SlashCommandAbortController}*/ abortController;
|
||||
/**@type {SlashCommandDebugController}*/ debugController;
|
||||
/**@type {(done:number, total:number)=>void}*/ onProgress;
|
||||
/**@type {string}*/ rawText;
|
||||
|
||||
|
@ -87,6 +90,7 @@ export class SlashCommandClosure {
|
|||
closure.providedArgumentList = this.providedArgumentList;
|
||||
closure.executorList = this.executorList;
|
||||
closure.abortController = this.abortController;
|
||||
closure.debugController = this.debugController;
|
||||
closure.onProgress = this.onProgress;
|
||||
return closure;
|
||||
}
|
||||
|
@ -97,10 +101,29 @@ export class SlashCommandClosure {
|
|||
*/
|
||||
async execute() {
|
||||
const closure = this.getCopy();
|
||||
return await closure.executeDirect();
|
||||
const gen = closure.executeDirect();
|
||||
let step;
|
||||
while (!step?.done) {
|
||||
step = await gen.next(this.debugController?.isStepping ?? false);
|
||||
if (!(step.value instanceof SlashCommandClosureResult) && this.debugController) {
|
||||
this.debugController.isStepping = await this.debugController.awaitBreakPoint(step.value.closure, step.value.executor);
|
||||
}
|
||||
}
|
||||
return step.value;
|
||||
}
|
||||
|
||||
async executeDirect() {
|
||||
async * executeGenerator() {
|
||||
const closure = this.getCopy();
|
||||
const gen = closure.executeDirect();
|
||||
let step;
|
||||
while (!step?.done) {
|
||||
step = await gen.next(this.debugController?.isStepping);
|
||||
this.debugController.isStepping = yield step.value;
|
||||
}
|
||||
return step.value;
|
||||
}
|
||||
|
||||
async * executeDirect() {
|
||||
// closure arguments
|
||||
for (const arg of this.argumentList) {
|
||||
let v = arg.value;
|
||||
|
@ -153,7 +176,7 @@ export class SlashCommandClosure {
|
|||
if (this.executorList.length == 0) {
|
||||
this.scope.pipe = '';
|
||||
}
|
||||
for (const executor of this.executorList) {
|
||||
for (const executor of [] ?? this.executorList) {
|
||||
this.onProgress?.(done, this.commandCount);
|
||||
if (executor instanceof SlashCommandClosureExecutor) {
|
||||
const closure = this.scope.getVariable(executor.name);
|
||||
|
@ -258,10 +281,156 @@ export class SlashCommandClosure {
|
|||
}
|
||||
}
|
||||
}
|
||||
const stepper = this.executeStep();
|
||||
let step;
|
||||
while (!step?.done) {
|
||||
// get executor before execution
|
||||
step = await stepper.next();
|
||||
if (step.value instanceof SlashCommandBreakPoint) {
|
||||
console.log('encountered SlashCommandBreakPoint');
|
||||
if (this.debugController) {
|
||||
// "execute" breakpoint
|
||||
step = await stepper.next();
|
||||
// get next executor
|
||||
step = await stepper.next();
|
||||
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||
}
|
||||
} else if (!step.done && this.debugController?.isStepping) {
|
||||
this.debugController.isSteppingInto = false;
|
||||
this.debugController.isStepping = yield { closure:this, executor:step.value };
|
||||
}
|
||||
// execute executor
|
||||
step = await stepper.next();
|
||||
}
|
||||
|
||||
// if execution has returned a closure result, return that (should only happen on abort)
|
||||
if (step.value instanceof SlashCommandClosureResult) {
|
||||
return step.value;
|
||||
}
|
||||
/**@type {SlashCommandClosureResult} */
|
||||
const result = Object.assign(new SlashCommandClosureResult(), { pipe: this.scope.pipe });
|
||||
return result;
|
||||
}
|
||||
async * executeStep() {
|
||||
let done = 0;
|
||||
for (const executor of this.executorList) {
|
||||
this.onProgress?.(done, this.commandCount);
|
||||
yield executor;
|
||||
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 if (executor instanceof SlashCommandBreakPoint) {
|
||||
// no execution for breakpoints, just raise counter
|
||||
done++;
|
||||
} 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);
|
||||
const isStepping = this.debugController?.isStepping;
|
||||
if (this.debugController) {
|
||||
this.debugController.isStepping = false || this.debugController.isSteppingInto;
|
||||
}
|
||||
this.scope.pipe = await executor.command.callback(args, value ?? '');
|
||||
if (this.debugController) {
|
||||
this.debugController.isStepping = isStepping;
|
||||
}
|
||||
this.#lintPipe(executor.command);
|
||||
done += executor.commandCount;
|
||||
this.onProgress?.(done, this.commandCount);
|
||||
abortResult = await this.testAbortController();
|
||||
if (abortResult) {
|
||||
return abortResult;
|
||||
}
|
||||
}
|
||||
yield executor;
|
||||
}
|
||||
}
|
||||
|
||||
async testPaused() {
|
||||
while (!this.abortController?.signal?.aborted && this.abortController?.signal?.paused) {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
|
||||
export class SlashCommandDebugController {
|
||||
/**@type {boolean} */ isStepping = false;
|
||||
/**@type {boolean} */ isSteppingInto = false;
|
||||
|
||||
/**@type {Promise<boolean>} */ continuePromise;
|
||||
/**@type {(boolean)=>void} */ continueResolver;
|
||||
|
||||
/**@type {(closure:SlashCommandClosure, executor:SlashCommandExecutor)=>Promise<boolean>} */ onBreakPoint;
|
||||
|
||||
|
||||
|
||||
resume() {
|
||||
this.continueResolver?.(false);
|
||||
this.continuePromise = null;
|
||||
}
|
||||
step() {
|
||||
this.continueResolver?.(true);
|
||||
this.continuePromise = null;
|
||||
}
|
||||
stepInto() {
|
||||
this.isSteppingInto = true;
|
||||
this.continueResolver?.(true);
|
||||
this.continuePromise = null;
|
||||
}
|
||||
|
||||
async awaitContinue() {
|
||||
this.continuePromise ??= new Promise(resolve=>{
|
||||
this.continueResolver = resolve;
|
||||
});
|
||||
this.isStepping = await this.continuePromise;
|
||||
return this.isStepping;
|
||||
}
|
||||
|
||||
async awaitBreakPoint(closure, executor) {
|
||||
this.isStepping = await this.onBreakPoint(closure, executor);
|
||||
return this.isStepping;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNa
|
|||
import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
|
||||
import { SlashCommandEnumValue } from './SlashCommandEnumValue.js';
|
||||
import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js';
|
||||
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {Number}*/
|
||||
|
@ -85,6 +87,7 @@ export class SlashCommandParser {
|
|||
/**@type {string}*/ text;
|
||||
/**@type {number}*/ index;
|
||||
/**@type {SlashCommandAbortController}*/ abortController;
|
||||
/**@type {SlashCommandDebugController}*/ debugController;
|
||||
/**@type {SlashCommandScope}*/ scope;
|
||||
/**@type {SlashCommandClosure}*/ closure;
|
||||
|
||||
|
@ -560,12 +563,13 @@ export class SlashCommandParser {
|
|||
}
|
||||
|
||||
|
||||
parse(text, verifyCommandNames = true, flags = null, abortController = null) {
|
||||
parse(text, verifyCommandNames = true, flags = null, abortController = null, debugController = null) {
|
||||
this.verifyCommandNames = verifyCommandNames;
|
||||
for (const key of Object.keys(PARSER_FLAG)) {
|
||||
this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false;
|
||||
}
|
||||
this.abortController = abortController;
|
||||
this.debugController = debugController;
|
||||
this.text = text;
|
||||
this.index = 0;
|
||||
this.scope = null;
|
||||
|
@ -601,6 +605,7 @@ export class SlashCommandParser {
|
|||
const textStart = this.index;
|
||||
let closure = new SlashCommandClosure(this.scope);
|
||||
closure.abortController = this.abortController;
|
||||
closure.debugController = this.debugController;
|
||||
this.scope = closure.scope;
|
||||
this.closure = closure;
|
||||
this.discardWhitespace();
|
||||
|
@ -619,6 +624,11 @@ export class SlashCommandParser {
|
|||
const cmd = this.parseRunShorthand();
|
||||
closure.executorList.push(cmd);
|
||||
injectPipe = true;
|
||||
} else if (this.testBreakPoint()) {
|
||||
const bp = this.parseBreakPoint();
|
||||
if (this.debugController) {
|
||||
closure.executorList.push(bp);
|
||||
}
|
||||
} else if (this.testCommand()) {
|
||||
const cmd = this.parseCommand();
|
||||
cmd.injectPipe = injectPipe;
|
||||
|
@ -650,6 +660,17 @@ export class SlashCommandParser {
|
|||
return closure;
|
||||
}
|
||||
|
||||
testBreakPoint() {
|
||||
return this.testSymbol(/\/breakpoint\s*\|/);
|
||||
}
|
||||
parseBreakPoint() {
|
||||
const bp = new SlashCommandBreakPoint();
|
||||
bp.start = this.index;
|
||||
this.take('/breakpoint'.length);
|
||||
bp.end = this.index;
|
||||
return bp;
|
||||
}
|
||||
|
||||
testComment() {
|
||||
return this.testSymbol(/\/[/#]/);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue