From 91ffd141ef5305a4c68efbced613382af5f11292 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Fri, 5 Jul 2024 18:05:22 -0400 Subject: [PATCH] add a little more details to execution exceptions --- public/scripts/slash-commands.js | 37 +++++++++++- .../slash-commands/SlashCommandClosure.js | 7 ++- .../SlashCommandExecutionError.js | 60 +++++++++++++++++++ 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 public/scripts/slash-commands/SlashCommandExecutionError.js diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index fcdf7afc2..37a392ecb 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -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 = ` +
${ex.message}
+
Line: ${ex.line} Column: ${ex.column}
+
${ex.hint}
+ `; + const clickHint = '

Click to see details

'; + 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 = ` +
${ex.message}
+
Line: ${ex.line} Column: ${ex.column}
+
${ex.hint}
+ `; + const clickHint = '

Click to see details

'; + 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; diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index faf7440c1..6be5ac204 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -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; diff --git a/public/scripts/slash-commands/SlashCommandExecutionError.js b/public/scripts/slash-commands/SlashCommandExecutionError.js new file mode 100644 index 000000000..a8f9df0eb --- /dev/null +++ b/public/scripts/slash-commands/SlashCommandExecutionError.js @@ -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; + } +}