add a little more details to execution exceptions

This commit is contained in:
LenAnderson 2024-07-05 18:05:22 -04:00
parent b1412d3bce
commit 91ffd141ef
3 changed files with 101 additions and 3 deletions

View File

@ -66,6 +66,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
};
@ -3405,7 +3406,23 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
result.isError = true;
result.errorMessage = e.message || 'An unknown error occurred';
if (e.cause !== 'abort') {
toastr.error(result.errorMessage);
if (e instanceof SlashCommandExecutionError) {
/**@type {SlashCommandExecutionError}*/
const ex = e;
const toast = `
<div>${ex.message}</div>
<div>Line: ${ex.line} Column: ${ex.column}</div>
<pre style="text-align:left;">${ex.hint}</pre>
`;
const clickHint = '<p>Click to see details</p>';
toastr.error(
`${toast}${clickHint}`,
'SlashCommandExecutionError',
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
);
} else {
toastr.error(result.errorMessage);
}
}
} finally {
delay(1000).then(() => clearCommandProgressDebounced());
@ -3474,7 +3491,23 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
return result;
} catch (e) {
if (options.handleExecutionErrors) {
toastr.error(e.message);
if (e instanceof SlashCommandExecutionError) {
/**@type {SlashCommandExecutionError}*/
const ex = e;
const toast = `
<div>${ex.message}</div>
<div>Line: ${ex.line} Column: ${ex.column}</div>
<pre style="text-align:left;">${ex.hint}</pre>
`;
const clickHint = '<p>Click to see details</p>';
toastr.error(
`${toast}${clickHint}`,
'SlashCommandExecutionError',
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
);
} else {
toastr.error(e.message);
}
const result = new SlashCommandClosureResult();
result.isError = true;
result.errorMessage = e.message;

View File

@ -7,6 +7,7 @@ import { SlashCommandBreakController } from './SlashCommandBreakController.js';
import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
import { SlashCommandClosureResult } from './SlashCommandClosureResult.js';
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
import { SlashCommandExecutionError } from './SlashCommandExecutionError.js';
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
import { SlashCommandScope } from './SlashCommandScope.js';
@ -370,7 +371,11 @@ export class SlashCommandClosure {
if (this.debugController) {
this.debugController.isStepping = false || this.debugController.isSteppingInto;
}
this.scope.pipe = await executor.command.callback(args, value ?? '');
try {
this.scope.pipe = await executor.command.callback(args, value ?? '');
} catch (ex) {
throw new SlashCommandExecutionError(ex, ex.message, executor.name, executor.start, executor.end, this.fullText.slice(executor.start, executor.end), this.fullText);
}
if (this.debugController) {
this.debugController.namedArguments = undefined;
this.debugController.unnamedArguments = undefined;

View File

@ -0,0 +1,60 @@
export class SlashCommandExecutionError extends Error {
/**@type {string} */ commandName;
/**@type {number} */ start;
/**@type {number} */ end;
/**@type {string} */ commandText;
/**@type {string} */ text;
get index() { return this.start; }
get line() {
return this.text.slice(0, this.index).replace(/[^\n]/g, '').length;
}
get column() {
return this.text.slice(0, this.index).split('\n').pop().length;
}
get hint() {
let lineOffset = this.line.toString().length;
let lineStart = this.index;
let start = this.index;
let end = this.index;
let offset = 0;
let lineCount = 0;
while (offset < 10000 && lineCount < 3 && start >= 0) {
if (this.text[start] == '\n') lineCount++;
if (lineCount == 0) lineStart--;
offset++;
start--;
}
if (this.text[start + 1] == '\n') start++;
offset = 0;
while (offset < 10000 && this.text[end] != '\n') {
offset++;
end++;
}
let hint = [];
let lines = this.text.slice(start + 1, end - 1).split('\n');
let lineNum = this.line - lines.length + 1;
let tabOffset = 0;
for (const line of lines) {
const num = `${' '.repeat(lineOffset - lineNum.toString().length)}${lineNum}`;
lineNum++;
const untabbedLine = line.replace(/\t/g, ' '.repeat(4));
tabOffset = untabbedLine.length - line.length;
hint.push(`${num}: ${untabbedLine}`);
}
hint.push(`${' '.repeat(this.index - lineStart + lineOffset + 1 + tabOffset)}^^^^^`);
return hint.join('\n');
}
constructor(cause, message, commandName, start, end, commandText, fullText) {
super(message, { cause });
this.commandName = commandName;
this.start = start;
this.end = end;
this.commandText = commandText;
this.text = fullText;
}
}