mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
throwing shit at the wall for /: autocomplete
This commit is contained in:
@ -2393,7 +2393,7 @@ async function processCommands(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const previousText = String($('#send_textarea').val());
|
const previousText = String($('#send_textarea').val());
|
||||||
const result = await executeSlashCommands(message);
|
const result = await executeSlashCommands(message, true, null, true);
|
||||||
|
|
||||||
if (!result || typeof result !== 'object') {
|
if (!result || typeof result !== 'object') {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1757,7 +1757,7 @@ function modelCallback(_, model) {
|
|||||||
* @param {SlashCommandScope} scope The scope to be used when executing the commands.
|
* @param {SlashCommandScope} scope The scope to be used when executing the commands.
|
||||||
* @returns {Promise<SlashCommandClosureResult>}
|
* @returns {Promise<SlashCommandClosureResult>}
|
||||||
*/
|
*/
|
||||||
async function executeSlashCommands(text, handleParserErrors = true, scope = null) {
|
async function executeSlashCommands(text, handleParserErrors = true, scope = null, handleExecutionErrors = false) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1780,13 +1780,26 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul
|
|||||||
'SlashCommandParserError',
|
'SlashCommandParserError',
|
||||||
{ escapeHtml:false, timeOut: 10000, onclick:()=>callPopup(toast, 'text') },
|
{ escapeHtml:false, timeOut: 10000, onclick:()=>callPopup(toast, 'text') },
|
||||||
);
|
);
|
||||||
return;
|
const result = new SlashCommandClosureResult();
|
||||||
|
result.interrupt = true;
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
return await closure.execute();
|
return await closure.execute();
|
||||||
|
} catch (e) {
|
||||||
|
if (handleExecutionErrors) {
|
||||||
|
toastr.error(e.message);
|
||||||
|
const result = new SlashCommandClosureResult();
|
||||||
|
result.interrupt = true;
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1794,7 +1807,7 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul
|
|||||||
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete
|
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete
|
||||||
* @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor)
|
* @param {Boolean} isFloating Whether to show the auto complete as a floating window (e.g., large QR editor)
|
||||||
*/
|
*/
|
||||||
export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
export async function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
||||||
const dom = document.createElement('ul'); {
|
const dom = document.createElement('ul'); {
|
||||||
dom.classList.add('slashCommandAutoComplete');
|
dom.classList.add('slashCommandAutoComplete');
|
||||||
}
|
}
|
||||||
@ -1803,13 +1816,16 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
let selectedItem = null;
|
let selectedItem = null;
|
||||||
let isActive = false;
|
let isActive = false;
|
||||||
let text;
|
let text;
|
||||||
|
/**@type {SlashCommandExecutor}*/
|
||||||
let executor;
|
let executor;
|
||||||
let clone;
|
let clone;
|
||||||
|
let startQuote;
|
||||||
|
let endQuote;
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
dom?.remove();
|
dom?.remove();
|
||||||
isActive = false;
|
isActive = false;
|
||||||
};
|
};
|
||||||
const show = (isInput = false, isForced = false) => {
|
const show = async(isInput = false, isForced = false) => {
|
||||||
//TODO check if isInput and isForced are both required
|
//TODO check if isInput and isForced are both required
|
||||||
text = textarea.value;
|
text = textarea.value;
|
||||||
// only show with textarea in focus
|
// only show with textarea in focus
|
||||||
@ -1819,16 +1835,47 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
|
|
||||||
// request parser to get command executor (potentially "incomplete", i.e. not an actual existing command) for
|
// request parser to get command executor (potentially "incomplete", i.e. not an actual existing command) for
|
||||||
// cursor position
|
// cursor position
|
||||||
executor = parser.getCommandAt(text, textarea.selectionStart);
|
const parserResult = parser.getCommandAt(text, textarea.selectionStart);
|
||||||
let slashCommand = executor?.name?.toLowerCase() ?? '';
|
let run;
|
||||||
|
let options;
|
||||||
|
if (Array.isArray(parserResult)) {
|
||||||
|
executor = null;
|
||||||
|
run = parserResult[0];
|
||||||
|
options = parserResult[1];
|
||||||
|
startQuote = text[run.start - 2] == '"';
|
||||||
|
endQuote = startQuote && text[run.start - 2 + run.value.length + 1] == '"';
|
||||||
|
try {
|
||||||
|
const qrApi = (await import('./extensions/quick-reply/index.js')).quickReplyApi;
|
||||||
|
options.push(...qrApi.listSets().map(set=>qrApi.listQuickReplies(set).map(qr=>`${set}.${qr}`)).flat());
|
||||||
|
} catch { /* empty */ }
|
||||||
|
} else {
|
||||||
|
executor = parserResult;
|
||||||
|
}
|
||||||
|
let slashCommand = run ? run.value?.toLowerCase() : executor?.name?.toLowerCase() ?? '';
|
||||||
// do autocomplete if triggered by a user input and we either don't have an executor or the cursor is at the end
|
// do autocomplete if triggered by a user input and we either don't have an executor or the cursor is at the end
|
||||||
// of the name part of the command
|
// of the name part of the command
|
||||||
isReplacable = isInput && (!executor ? true : textarea.selectionStart == executor.start - 2 + executor.name.length + 1);
|
if (options) {
|
||||||
|
isReplacable = isInput && (!run.value ? true : textarea.selectionStart == run.start - 2 + run.value.length + (startQuote ? 1 : 0));
|
||||||
|
} else {
|
||||||
|
isReplacable = isInput && (!executor ? true : textarea.selectionStart == executor.start - 2 + executor.name.length);
|
||||||
|
}
|
||||||
// if forced (ctrl+space) or user input and cursor is in the middle of the name part (not at the end)
|
// if forced (ctrl+space) or user input and cursor is in the middle of the name part (not at the end)
|
||||||
if ((isForced || isInput) && executor && textarea.selectionStart > executor.start - 2 && textarea.selectionStart <= executor.start - 2 + executor.name.length + 1) {
|
if ((isForced || isInput)
|
||||||
slashCommand = slashCommand.slice(0, textarea.selectionStart - (executor.start - 2) - 1);
|
&& (
|
||||||
|
((executor) && textarea.selectionStart >= executor.start - 2 && textarea.selectionStart <= executor.start - 2 + executor.name.length)
|
||||||
|
||
|
||||||
|
((run) && textarea.selectionStart >= run.start - 2 && textarea.selectionStart <= run.start - 2 + run.value.length + (startQuote ? 1 : 0))
|
||||||
|
)
|
||||||
|
){
|
||||||
|
if (run) {
|
||||||
|
slashCommand = slashCommand.slice(0, textarea.selectionStart - (run.start - 2) - (startQuote ? 1 : 0));
|
||||||
|
run.value = slashCommand;
|
||||||
|
run.end = run.start + slashCommand.length;
|
||||||
|
} else {
|
||||||
|
slashCommand = slashCommand.slice(0, textarea.selectionStart - (executor.start - 2));
|
||||||
executor.name = slashCommand;
|
executor.name = slashCommand;
|
||||||
executor.end = executor.start + slashCommand.length;
|
executor.end = executor.start + slashCommand.length;
|
||||||
|
}
|
||||||
isReplacable = true;
|
isReplacable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1874,14 +1921,14 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
if (a.score.longestConsecutive < b.score.longestConsecutive) return 1;
|
if (a.score.longestConsecutive < b.score.longestConsecutive) return 1;
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
};
|
};
|
||||||
const buildHelpStringName = (name) => {
|
const buildHelpStringName = (name, noSlash=false) => {
|
||||||
switch (matchType) {
|
switch (matchType) {
|
||||||
case 'strict': {
|
case 'strict': {
|
||||||
return `<span class="monospace">/<span class="matched">${name.slice(0, slashCommand.length)}</span>${name.slice(slashCommand.length)}</span> `;
|
return `<span class="monospace">${noSlash?'':'/'}<span class="matched">${name.slice(0, slashCommand.length)}</span>${name.slice(slashCommand.length)}</span> `;
|
||||||
}
|
}
|
||||||
case 'includes': {
|
case 'includes': {
|
||||||
const start = name.toLowerCase().search(slashCommand);
|
const start = name.toLowerCase().search(slashCommand);
|
||||||
return `<span class="monospace">/${name.slice(0, start)}<span class="matched">${name.slice(start, start + slashCommand.length)}</span>${name.slice(start + slashCommand.length)}</span> `;
|
return `<span class="monospace">${noSlash?'':'/'}${name.slice(0, start)}<span class="matched">${name.slice(start, start + slashCommand.length)}</span>${name.slice(start + slashCommand.length)}</span> `;
|
||||||
}
|
}
|
||||||
case 'fuzzy': {
|
case 'fuzzy': {
|
||||||
const matched = name.replace(fuzzyRegex, (_, ...parts)=>{
|
const matched = name.replace(fuzzyRegex, (_, ...parts)=>{
|
||||||
@ -1897,21 +1944,29 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
return it;
|
return it;
|
||||||
}).join('');
|
}).join('');
|
||||||
});
|
});
|
||||||
return `<span class="monospace">/${matched}</span> `;
|
return `<span class="monospace">${noSlash?'':'/'}${matched}</span> `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't show if no executor found, i.e. cursor's area is not a command
|
// don't show if no executor found, i.e. cursor's area is not a command
|
||||||
if (!executor) return hide();
|
if (!executor && !options) return hide();
|
||||||
else {
|
else {
|
||||||
const helpStrings = Object
|
const helpStrings = (options ?? Object.keys(parser.commands)) // Get all slash commands
|
||||||
.keys(parser.commands) // Get all slash commands
|
.filter(it => run?.value == '' || executor?.name == '' || isReplacable ? matchers[matchType](it) : it.toLowerCase() == slashCommand) // Filter by the input
|
||||||
.filter(it => executor.name == '' || isReplacable ? matchers[matchType](it) : it.toLowerCase() == slashCommand) // Filter by the input
|
|
||||||
;
|
;
|
||||||
result = helpStrings
|
result = helpStrings
|
||||||
.filter((it,idx)=>[idx, -1].includes(helpStrings.indexOf(parser.commands[it].name.toLowerCase()))) // remove duplicates
|
.filter((it,idx)=>options || [idx, -1].includes(helpStrings.indexOf(parser.commands[it].name.toLowerCase()))) // remove duplicates
|
||||||
.map(name => {
|
.map(name => {
|
||||||
|
if (options) {
|
||||||
|
return {
|
||||||
|
name: name,
|
||||||
|
label: `<span class="type monospace">${name.includes('.')?'QR':'𝑥'}</span> ${buildHelpStringName(name, true)}`,
|
||||||
|
value: name.includes(' ') || startQuote || endQuote ? `"${name}"` : `${name}`,
|
||||||
|
score: matchType == 'fuzzy' ? fuzzyScore(name) : null,
|
||||||
|
li: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
const cmd = parser.commands[name];
|
const cmd = parser.commands[name];
|
||||||
let aliases = '';
|
let aliases = '';
|
||||||
if (cmd.aliases?.length > 0) {
|
if (cmd.aliases?.length > 0) {
|
||||||
@ -1926,7 +1981,7 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
label: `${buildHelpStringName(name)}${cmd.helpString}${aliases}`,
|
label: `${buildHelpStringName(name)}${cmd.helpString}${aliases}`,
|
||||||
value: `/${name}`,
|
value: `${name}`,
|
||||||
score: matchType == 'fuzzy' ? fuzzyScore(name) : null,
|
score: matchType == 'fuzzy' ? fuzzyScore(name) : null,
|
||||||
li: null,
|
li: null,
|
||||||
};
|
};
|
||||||
@ -1941,16 +1996,30 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
return hide();
|
return hide();
|
||||||
}
|
}
|
||||||
// otherwise add "no match" notice
|
// otherwise add "no match" notice
|
||||||
|
if (options) {
|
||||||
result.push({
|
result.push({
|
||||||
name: '',
|
name: '',
|
||||||
label: `No matching commands for "/${slashCommand}"`,
|
label: slashCommand.length ?
|
||||||
value:'',
|
`No matching variables in scope for "${slashCommand}"`
|
||||||
|
: 'No variables in scope.',
|
||||||
|
value: null,
|
||||||
score: null,
|
score: null,
|
||||||
li: null,
|
li: null,
|
||||||
});
|
});
|
||||||
} else if (result.length == 1 && result[0].value == `/${executor.name}`) {
|
} else {
|
||||||
|
result.push({
|
||||||
|
name: '',
|
||||||
|
label: `No matching commands for "/${slashCommand}"`,
|
||||||
|
value: null,
|
||||||
|
score: null,
|
||||||
|
li: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (result.length == 1 && ((executor && result[0].value == `/${executor.name}`) || (options && result[0].value == run.value))) {
|
||||||
// only one result that is exactly the current value? just show hint, no autocomplete
|
// only one result that is exactly the current value? just show hint, no autocomplete
|
||||||
isReplacable = false;
|
isReplacable = false;
|
||||||
|
} else if (!isReplacable && result.length > 1) {
|
||||||
|
return hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// render autocomplete list
|
// render autocomplete list
|
||||||
@ -2001,6 +2070,9 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
updatePosition();
|
updatePosition();
|
||||||
document.body.append(dom);
|
document.body.append(dom);
|
||||||
isActive = true;
|
isActive = true;
|
||||||
|
if (options) {
|
||||||
|
executor = {start:run.start, name:`${slashCommand}`};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const updatePosition = () => {
|
const updatePosition = () => {
|
||||||
if (isFloating) {
|
if (isFloating) {
|
||||||
@ -2074,8 +2146,8 @@ export function setSlashCommandAutoComplete(textarea, isFloating = false) {
|
|||||||
};
|
};
|
||||||
let pointerup = Promise.resolve();
|
let pointerup = Promise.resolve();
|
||||||
const select = async() => {
|
const select = async() => {
|
||||||
if (isReplacable) {
|
if (isReplacable && selectedItem.value !== null) {
|
||||||
textarea.value = `${text.slice(0, executor.start - 2)}${selectedItem.value}${text.slice(executor.start - 2 + executor.name.length + 1)}`;
|
textarea.value = `${text.slice(0, executor.start - 2)}${selectedItem.value}${text.slice(executor.start - 2 + executor.name.length + (startQuote ? 1 : 0) + (endQuote ? 1 : 0))}`;
|
||||||
await pointerup;
|
await pointerup;
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
textarea.selectionStart = executor.start - 2 + selectedItem.value.length;
|
textarea.selectionStart = executor.start - 2 + selectedItem.value.length;
|
||||||
|
@ -8,9 +8,9 @@ export class SlashCommandClosure {
|
|||||||
/**@type {SlashCommandScope}*/ scope;
|
/**@type {SlashCommandScope}*/ scope;
|
||||||
/**@type {Boolean}*/ executeNow = false;
|
/**@type {Boolean}*/ executeNow = false;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {Map<string,string|SlashCommandClosure>}*/ arguments = {};
|
/**@type {Object.<string,string|SlashCommandClosure>}*/ arguments = {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {Map<string,string|SlashCommandClosure>}*/ providedArguments = {};
|
/**@type {Object.<string,string|SlashCommandClosure>}*/ providedArguments = {};
|
||||||
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
/**@type {SlashCommandExecutor[]}*/ executorList = [];
|
||||||
/**@type {String}*/ keptText;
|
/**@type {String}*/ keptText;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export class SlashCommandClosureExecutor {
|
export class SlashCommandClosureExecutor {
|
||||||
/**@type {String}*/ name = '';
|
/**@type {String}*/ name = '';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {Map<string,string|SlashCommandClosure>}*/ providedArguments = {};
|
/**@type {Object.<string,string|SlashCommandClosure>}*/ providedArguments = {};
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ export class SlashCommandExecutor {
|
|||||||
/**@type {String}*/ name = '';
|
/**@type {String}*/ name = '';
|
||||||
/**@type {SlashCommand}*/ command;
|
/**@type {SlashCommand}*/ command;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {Map<String,String|SlashCommandClosure>}*/ args = {};
|
/**@type {Object.<string,String|SlashCommandClosure>}*/ args = {};
|
||||||
/**@type {String|SlashCommandClosure|(String|SlashCommandClosure)[]}*/ value;
|
/**@type {String|SlashCommandClosure|(String|SlashCommandClosure)[]}*/ value;
|
||||||
|
|
||||||
constructor(start) {
|
constructor(start) {
|
||||||
|
@ -18,6 +18,7 @@ export class SlashCommandParser {
|
|||||||
/**@type {SlashCommandScope}*/ scope;
|
/**@type {SlashCommandScope}*/ scope;
|
||||||
|
|
||||||
/**@type {SlashCommandExecutor[]}*/ commandIndex;
|
/**@type {SlashCommandExecutor[]}*/ commandIndex;
|
||||||
|
/**@type {SlashCommandScope[]}*/ scopeIndex;
|
||||||
|
|
||||||
get ahead() {
|
get ahead() {
|
||||||
return this.text.slice(this.index + 1);
|
return this.text.slice(this.index + 1);
|
||||||
@ -167,6 +168,12 @@ export class SlashCommandParser {
|
|||||||
<li>This will remove the first message in chat, send a system message that starts with 'Hello,', and then ask the AI to continue the message.</li></ul>`;
|
<li>This will remove the first message in chat, send a system message that starts with 'Hello,', and then ask the AI to continue the message.</li></ul>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} text
|
||||||
|
* @param {*} index
|
||||||
|
* @returns {SlashCommandExecutor|String[]}
|
||||||
|
*/
|
||||||
getCommandAt(text, index) {
|
getCommandAt(text, index) {
|
||||||
try {
|
try {
|
||||||
this.parse(text, false);
|
this.parse(text, false);
|
||||||
@ -180,7 +187,8 @@ export class SlashCommandParser {
|
|||||||
.slice(-1)[0]
|
.slice(-1)[0]
|
||||||
?? null
|
?? null
|
||||||
;
|
;
|
||||||
if (executor && executor.name == ':') return null;
|
const scope = this.scopeIndex[this.commandIndex.indexOf(executor)];
|
||||||
|
if (executor && executor.name == ':') return [executor, scope?.allVariableNames];
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +213,7 @@ export class SlashCommandParser {
|
|||||||
this.index = 0;
|
this.index = 0;
|
||||||
this.scope = null;
|
this.scope = null;
|
||||||
this.commandIndex = [];
|
this.commandIndex = [];
|
||||||
|
this.scopeIndex = [];
|
||||||
const closure = this.parseClosure();
|
const closure = this.parseClosure();
|
||||||
closure.keptText = this.keptText;
|
closure.keptText = this.keptText;
|
||||||
return closure;
|
return closure;
|
||||||
@ -226,6 +235,7 @@ export class SlashCommandParser {
|
|||||||
while (this.testNamedArgument()) {
|
while (this.testNamedArgument()) {
|
||||||
const arg = this.parseNamedArgument();
|
const arg = this.parseNamedArgument();
|
||||||
closure.arguments[arg.key] = arg.value;
|
closure.arguments[arg.key] = arg.value;
|
||||||
|
this.scope.variableNames.push(arg.key);
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
}
|
}
|
||||||
while (!this.testClosureEnd()) {
|
while (!this.testClosureEnd()) {
|
||||||
@ -269,12 +279,13 @@ export class SlashCommandParser {
|
|||||||
return this.testCommandEnd();
|
return this.testCommandEnd();
|
||||||
}
|
}
|
||||||
parseRunShorthand() {
|
parseRunShorthand() {
|
||||||
const start = this.index;
|
const start = this.index + 2;
|
||||||
const cmd = new SlashCommandExecutor(start);
|
const cmd = new SlashCommandExecutor(start);
|
||||||
cmd.name = ':';
|
cmd.name = ':';
|
||||||
cmd.value = '';
|
cmd.value = '';
|
||||||
cmd.command = this.commands[cmd.name];
|
cmd.command = this.commands['run'];
|
||||||
this.commandIndex.push(cmd);
|
this.commandIndex.push(cmd);
|
||||||
|
this.scopeIndex.push(this.scope.getCopy());
|
||||||
this.take(2); //discard "/:"
|
this.take(2); //discard "/:"
|
||||||
if (this.testQuotedValue()) cmd.value = this.parseQuotedValue();
|
if (this.testQuotedValue()) cmd.value = this.parseQuotedValue();
|
||||||
else cmd.value = this.parseValue();
|
else cmd.value = this.parseValue();
|
||||||
@ -303,9 +314,10 @@ export class SlashCommandParser {
|
|||||||
return this.testClosureEnd() || this.endOfText || (this.char == '|' && this.behind.slice(-1) != '\\');
|
return this.testClosureEnd() || this.endOfText || (this.char == '|' && this.behind.slice(-1) != '\\');
|
||||||
}
|
}
|
||||||
parseCommand() {
|
parseCommand() {
|
||||||
const start = this.index;
|
const start = this.index + 1;
|
||||||
const cmd = new SlashCommandExecutor(start);
|
const cmd = new SlashCommandExecutor(start);
|
||||||
this.commandIndex.push(cmd);
|
this.commandIndex.push(cmd);
|
||||||
|
this.scopeIndex.push(this.scope.getCopy());
|
||||||
this.take(); // discard "/"
|
this.take(); // discard "/"
|
||||||
while (!/\s/.test(this.char) && !this.testCommandEnd()) cmd.name += this.take(); // take chars until whitespace or end
|
while (!/\s/.test(this.char) && !this.testCommandEnd()) cmd.name += this.take(); // take chars until whitespace or end
|
||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
@ -319,6 +331,15 @@ export class SlashCommandParser {
|
|||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
if (this.testUnnamedArgument()) {
|
if (this.testUnnamedArgument()) {
|
||||||
cmd.value = this.parseUnnamedArgument();
|
cmd.value = this.parseUnnamedArgument();
|
||||||
|
if (cmd.name == 'let') {
|
||||||
|
if (Array.isArray(cmd.value)) {
|
||||||
|
if (typeof cmd.value[0] == 'string') {
|
||||||
|
this.scope.variableNames.push(cmd.value[0]);
|
||||||
|
}
|
||||||
|
} else if (typeof cmd.value == 'string') {
|
||||||
|
this.scope.variableNames.push(cmd.value.split(/\s+/)[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.testCommandEnd()) {
|
if (this.testCommandEnd()) {
|
||||||
cmd.end = this.index;
|
cmd.end = this.index;
|
||||||
@ -380,7 +401,7 @@ export class SlashCommandParser {
|
|||||||
if (listValues.length == 1) return listValues[0];
|
if (listValues.length == 1) return listValues[0];
|
||||||
return listValues;
|
return listValues;
|
||||||
}
|
}
|
||||||
return value.trim();
|
return value.trim().replace(/\\([\s{:])/g, '$1');
|
||||||
}
|
}
|
||||||
|
|
||||||
testQuotedValue() {
|
testQuotedValue() {
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
export class SlashCommandScope {
|
export class SlashCommandScope {
|
||||||
|
/**@type {String[]}*/ variableNames = [];
|
||||||
|
get allVariableNames() {
|
||||||
|
const names = [...this.variableNames, ...(this.parent?.allVariableNames ?? [])];
|
||||||
|
return names.filter((it,idx)=>idx == names.indexOf(it));
|
||||||
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {Map<String, Object>}*/ variables = {};
|
/**@type {Object.<string, Object>}*/ variables = {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/**@type {Map<String, Object>}*/ macros = {};
|
/**@type {Object.<string, Object>}*/ macros = {};
|
||||||
get macroList() {
|
get macroList() {
|
||||||
return [...Object.keys(this.macros).map(key=>({ key, value:this.macros[key] })), ...(this.parent?.macroList ?? [])];
|
return [...Object.keys(this.macros).map(key=>({ key, value:this.macros[key] })), ...(this.parent?.macroList ?? [])];
|
||||||
}
|
}
|
||||||
@ -22,6 +27,7 @@ export class SlashCommandScope {
|
|||||||
|
|
||||||
getCopy() {
|
getCopy() {
|
||||||
const scope = new SlashCommandScope(this.parent);
|
const scope = new SlashCommandScope(this.parent);
|
||||||
|
scope.variableNames = [...this.variableNames];
|
||||||
scope.variables = Object.assign({}, this.variables);
|
scope.variables = Object.assign({}, this.variables);
|
||||||
scope.macros = this.macros;
|
scope.macros = this.macros;
|
||||||
scope.#pipe = this.#pipe;
|
scope.#pipe = this.#pipe;
|
||||||
|
@ -1129,6 +1129,15 @@ select {
|
|||||||
background-color: var(--ac-color-selected-background);
|
background-color: var(--ac-color-selected-background);
|
||||||
color: var(--ac-color-selected-text);
|
color: var(--ac-color-selected-text);
|
||||||
}
|
}
|
||||||
|
> .type {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2.75em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.6;
|
||||||
|
&:before { content: "["; }
|
||||||
|
&:after { content: "]"; }
|
||||||
|
}
|
||||||
.matched {
|
.matched {
|
||||||
background-color: var(--ac-color-matched-background);
|
background-color: var(--ac-color-matched-background);
|
||||||
color: var(--ac-color-matched-text);
|
color: var(--ac-color-matched-text);
|
||||||
|
Reference in New Issue
Block a user