subcommand and /abort fixes

- use AbortController in /abort instead of execption
- allow quiet abort
- allow loud abort
- allow abort reason
- abort when aborted in subcommand
- break out of loops when aborted inside
- fix parsing of subcommands with multiple commands
This commit is contained in:
LenAnderson
2024-05-18 14:48:31 -04:00
parent 909ec4191d
commit 87cc28ae28
5 changed files with 100 additions and 25 deletions

View File

@ -1,11 +1,13 @@
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js';
import { extension_settings, saveMetadataDebounced } from './extensions.js';
import { executeSlashCommands } from './slash-commands.js';
import { executeSlashCommands, executeSlashCommandsWithOptions } from './slash-commands.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
import { isFalseBoolean } from './utils.js';
@ -317,58 +319,101 @@ function listVariablesCallback() {
async function whileCallback(args, command) {
const isGuardOff = isFalseBoolean(args.guard);
const iterations = isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS;
if (command) {
if (command[0] instanceof SlashCommandClosure) {
command = command[0];
} else {
command = command.join(' ');
}
}
let commandResult;
for (let i = 0; i < iterations; i++) {
const { a, b, rule } = parseBooleanOperands(args);
const result = evalBoolean(rule, a, b);
if (result && command) {
if (command instanceof SlashCommandClosure) await command.execute();
else await executeSubCommands(command, args._scope, args._parserFlags);
if (command instanceof SlashCommandClosure) {
commandResult = await command.execute();
} else {
commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController);
if (commandResult.isAborted) {
args._abortController.abort(commandResult.abortReason, true);
}
}
break;
} else {
break;
}
}
if (commandResult) {
return commandResult.pipe;
}
return '';
}
/**
*
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value
* @returns
*/
async function timesCallback(args, value) {
let repeats;
let command;
if (Array.isArray(value)) {
[repeats, command] = value;
[repeats, ...command] = value;
if (command[0] instanceof SlashCommandClosure) {
command = command[0];
} else {
command = command.join(' ');
}
} else {
[repeats, ...command] = value.split(' ');
[repeats, ...command] = /**@type {string}*/(value).split(' ');
command = command.join(' ');
}
const isGuardOff = isFalseBoolean(args.guard);
const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS);
let result;
for (let i = 0; i < iterations; i++) {
/**@type {SlashCommandClosureResult}*/
if (command instanceof SlashCommandClosure) {
command.scope.setMacro('timesIndex', i);
await command.execute();
result = await command.execute();
}
else {
await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i), args._scope, args._parserFlags);
result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags);
if (result.isAborted) {
args._abortController.abort(result.abortReason, true);
break;
}
}
}
return '';
return result?.pipe ?? '';
}
async function ifCallback(args, command) {
const { a, b, rule } = parseBooleanOperands(args);
const result = evalBoolean(rule, a, b);
let commandResult;
if (result && command) {
if (command instanceof SlashCommandClosure) return (await command.execute()).pipe;
return await executeSubCommands(command, args._scope, args._parserFlags);
commandResult = await executeSubCommands(command, args._scope, args._parserFlags);
} else if (!result && args.else && ((typeof args.else === 'string' && args.else !== '') || args.else instanceof SlashCommandClosure)) {
if (args.else instanceof SlashCommandClosure) return (await args.else.execute(args._scope)).pipe;
return await executeSubCommands(args.else, args._scope, args._parserFlags);
commandResult = await executeSubCommands(args.else, args._scope, args._parserFlags);
}
if (commandResult) {
if (commandResult.isAborted) {
args._abortController.abort(commandResult.abortReason, true);
}
return commandResult.pipe;
}
return '';
}
@ -511,20 +556,25 @@ function evalBoolean(rule, a, b) {
/**
* Executes a slash command from a string (may be enclosed in quotes) and returns the result.
* @param {string} command Command to execute. May contain escaped macro and batch separators.
* @returns {Promise<string>} Pipe result
* @param {SlashCommandScope} [scope] The scope to use.
* @param {PARSER_FLAG[]} [parserFlags] The parser flags to use.
* @returns {Promise<SlashCommandClosureResult>} Closure execution result
*/
async function executeSubCommands(command, scope = null, parserFlags = null) {
if (command.startsWith('"') && command.endsWith('"')) {
command = command.slice(1, -1);
}
const result = await executeSlashCommands(command, true, scope, true, parserFlags);
const abortController = new SlashCommandAbortController();
const result = await executeSlashCommandsWithOptions(command, {
handleExecutionErrors: false,
handleParserErrors: false,
parserFlags,
scope,
abortController,
});
if (!result || typeof result !== 'object') {
return '';
}
return result?.pipe || '';
return result;
}
/**
@ -1132,6 +1182,7 @@ export function registerVariableCommands() {
'command to execute while true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], true,
),
],
splitUnnamedArgument: true,
helpString: `
<div>
Compares the value of the left operand <code>a</code> with the value of the right operand <code>b</code>,
@ -1184,6 +1235,7 @@ export function registerVariableCommands() {
true,
),
],
splitUnnamedArgument: true,
helpString: `
<div>
Execute any valid slash command enclosed in quotes <code>repeats</code> number of times.