Blame Raw
Cohee · e3f41666 · · 191 lines (8.7 KB)
1 contributor
1import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
2import { AutoCompleteSecondaryNameResult } from '../autocomplete/AutoCompleteSecondaryNameResult.js';
3import { SlashCommand } from './SlashCommand.js';
4import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAutoCompleteOption.js';
5import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
6import { SlashCommandExecutor } from './SlashCommandExecutor.js';
7import { SlashCommandNamedArgumentAutoCompleteOption } from './SlashCommandNamedArgumentAutoCompleteOption.js';
8import { SlashCommandScope } from './SlashCommandScope.js';
9
10export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
11 /**@type {SlashCommandExecutor}*/ executor;
12 /**@type {SlashCommandScope}*/ scope;
13
14 /**
15 * @param {SlashCommandExecutor} executor
16 * @param {SlashCommandScope} scope
17 * @param {Object.<string,SlashCommand>} commands
18 */
19 constructor(executor, scope, commands) {
20 super(
21 executor.name,
22 executor.start,
23 Object
24 .keys(commands)
25 .map(key => new SlashCommandCommandAutoCompleteOption(commands[key], key))
26 ,
27 false,
28 () => `No matching slash commands for "/${this.name}"`,
29 () => 'No slash commands found!',
30 );
31 this.executor = executor;
32 this.scope = scope;
33 }
34
35 getSecondaryNameAt(text, index, isSelect) {
36 const namedResult = this.getNamedArgumentAt(text, index, isSelect);
37 if (!namedResult || namedResult.optionList.length == 0 || !namedResult.isRequired) {
38 const unnamedResult = this.getUnnamedArgumentAt(text, index, isSelect);
39 if (!namedResult) return unnamedResult;
40 if (namedResult && unnamedResult) {
41 const combinedResult = new AutoCompleteSecondaryNameResult(
42 namedResult.name,
43 namedResult.start,
44 [...namedResult.optionList, ...unnamedResult.optionList],
45 );
46 combinedResult.isRequired = namedResult.isRequired || unnamedResult.isRequired;
47 combinedResult.forceMatch = namedResult.forceMatch && unnamedResult.forceMatch;
48 return combinedResult;
49 }
50 }
51 return namedResult;
52 }
53
54 getNamedArgumentAt(text, index, isSelect) {
55 function getSplitRegex() {
56 try {
57 return new RegExp('(?<==)');
58 } catch {
59 // For browsers that don't support lookbehind
60 return new RegExp('=(.*)');
61 }
62 }
63 if (!Array.isArray(this.executor.command?.namedArgumentList)) {
64 return null;
65 }
66 const notProvidedNamedArguments = this.executor.command.namedArgumentList.filter(arg => !this.executor.namedArgumentList.find(it => it.name == arg.name));
67 let name;
68 let value;
69 let start;
70 let cmdArg;
71 let argAssign;
72 const unamedArgLength = this.executor.endUnnamedArgs - this.executor.startUnnamedArgs;
73 const namedArgsFollowedBySpace = text[this.executor.endNamedArgs] == ' ';
74 if (this.executor.startNamedArgs <= index && this.executor.endNamedArgs + (namedArgsFollowedBySpace ? 1 : 0) >= index) {
75 // cursor is somewhere within the named arguments (including final space)
76 argAssign = this.executor.namedArgumentList.find(it => it.start <= index && it.end >= index);
77 if (argAssign) {
78 const [argName, ...v] = text.slice(argAssign.start, index).split(getSplitRegex());
79 name = argName;
80 value = v.join('');
81 start = argAssign.start;
82 cmdArg = this.executor.command.namedArgumentList.find(it => [it.name, `${it.name}=`].includes(argAssign.name));
83 if (cmdArg) notProvidedNamedArguments.push(cmdArg);
84 } else {
85 name = '';
86 start = index;
87 }
88 } else if (unamedArgLength > 0 && index >= this.executor.startUnnamedArgs && index <= this.executor.endUnnamedArgs) {
89 // cursor is somewhere within the unnamed arguments
90 // if index is in first array item and that is a string, treat it as an unfinished named arg
91 if (typeof this.executor.unnamedArgumentList[0]?.value == 'string') {
92 if (index <= this.executor.startUnnamedArgs + this.executor.unnamedArgumentList[0].value.length) {
93 name = this.executor.unnamedArgumentList[0].value.slice(0, index - this.executor.startUnnamedArgs);
94 start = this.executor.startUnnamedArgs;
95 } else {
96 return null;
97 }
98 } else {
99 return null;
100 }
101 } else {
102 return null;
103 }
104
105 if (name.includes('=') && cmdArg) {
106 // if cursor is already behind "=" check for enums
107 const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
108 if (cmdArg && enumList?.length) {
109 if (isSelect && enumList.find(it => it.value == value) && argAssign && argAssign.end == index) {
110 return null;
111 }
112 const result = new AutoCompleteSecondaryNameResult(
113 value,
114 start + name.length,
115 enumList.map(it => SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
116 true,
117 );
118 result.isRequired = true;
119 result.forceMatch = cmdArg.forceEnum;
120 return result;
121 }
122 }
123
124 if (notProvidedNamedArguments.length > 0) {
125 const result = new AutoCompleteSecondaryNameResult(
126 name,
127 start,
128 notProvidedNamedArguments.map(it => new SlashCommandNamedArgumentAutoCompleteOption(it, this.executor.command)),
129 false,
130 );
131 result.isRequired = notProvidedNamedArguments.find(it => it.isRequired) != null;
132 return result;
133 }
134
135 return null;
136 }
137
138 getUnnamedArgumentAt(text, index, isSelect) {
139 if (!Array.isArray(this.executor.command?.unnamedArgumentList)) {
140 return null;
141 }
142 const lastArgIsBlank = this.executor.unnamedArgumentList.slice(-1)[0]?.value == '';
143 const notProvidedArguments = this.executor.command.unnamedArgumentList.slice(this.executor.unnamedArgumentList.length - (lastArgIsBlank ? 1 : 0));
144 let value;
145 let start;
146 let cmdArg;
147 let argAssign;
148 if (this.executor.startUnnamedArgs <= index && this.executor.endUnnamedArgs + 1 >= index) {
149 // cursor is somwehere in the unnamed args
150 const idx = this.executor.unnamedArgumentList.findIndex(it => it.start <= index && it.end >= index);
151 if (idx > -1) {
152 argAssign = this.executor.unnamedArgumentList[idx];
153 cmdArg = this.executor.command.unnamedArgumentList[idx];
154 if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
155 cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
156 }
157 const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
158 if (cmdArg && enumList.length > 0) {
159 value = argAssign.value.toString().slice(0, index - argAssign.start);
160 start = argAssign.start;
161 } else {
162 return null;
163 }
164 } else {
165 value = '';
166 start = index;
167 cmdArg = notProvidedArguments[0];
168 if (cmdArg === undefined && this.executor.command.unnamedArgumentList.slice(-1)[0]?.acceptsMultiple) {
169 cmdArg = this.executor.command.unnamedArgumentList.slice(-1)[0];
170 }
171 }
172 } else {
173 return null;
174 }
175
176 const enumList = cmdArg?.enumProvider?.(this.executor, this.scope) ?? cmdArg?.enumList;
177 if (cmdArg == null || enumList.length == 0) return null;
178
179 const result = new AutoCompleteSecondaryNameResult(
180 value,
181 start,
182 enumList.map(it => SlashCommandEnumAutoCompleteOption.from(this.executor.command, it)),
183 false,
184 );
185 const isCompleteValue = enumList.find(it => it.value == value);
186 const isSelectedValue = isSelect && isCompleteValue;
187 result.isRequired = cmdArg.isRequired && !isSelectedValue;
188 result.forceMatch = cmdArg.forceEnum;
189 return result;
190 }
191}