minercon 3.0.3 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,445 +0,0 @@
1
- "use strict";
2
- // src/helpTextParsing.ts
3
- //
4
- // Pure parsing of Minecraft's Brigadier-shaped `/help` output (flat
5
- // `/cmd <args>` blobs, as returned by `minecraft:help` and vanilla's plain
6
- // `help`) into a `Parameter` tree, plus the root-listing parser
7
- // (`parseHelpResponse`) and the one-time namespace-support probe
8
- // (`isUnsupportedNamespaceError`). No state, no IO — every export here is a
9
- // deterministic function of its arguments, which is what makes them directly
10
- // unit-testable without constructing a `LocalCommandTree`.
11
- //
12
- // Bukkit's hand-written `Description:`/`Usage:`/`Aliases:` `/help <command>`
13
- // pages are a different grammar entirely - their extraction lives in
14
- // `bukkitHelpParsing.ts`.
15
- //
16
- // `stripColors` (used throughout to normalize input before parsing) and the
17
- // `formatMinecraftColors`/ANSI rendering side live in ansi.ts.
18
- //
19
- // The `Parameter`/`ParameterType` tree this file builds is defined in
20
- // `commandTree.ts`.
21
- Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.tokenizeParameterString = tokenizeParameterString;
23
- exports.parseParameter = parseParameter;
24
- exports.parseCommandHelp = parseCommandHelp;
25
- exports.classifyParameterTokens = classifyParameterTokens;
26
- exports.splitConcatenatedHelpLines = splitConcatenatedHelpLines;
27
- exports.parseAliasRedirect = parseAliasRedirect;
28
- exports.parseHelpLines = parseHelpLines;
29
- exports.isGenericArgsPlaceholder = isGenericArgsPlaceholder;
30
- exports.hasUsableArguments = hasUsableArguments;
31
- exports.hasRealUsage = hasRealUsage;
32
- exports.parseHelpResponse = parseHelpResponse;
33
- exports.isUnsupportedNamespaceError = isUnsupportedNamespaceError;
34
- exports.buildParameterStructureFromVariants = buildParameterStructureFromVariants;
35
- const ansi_1 = require("./ansi");
36
- const commandTree_1 = require("./commandTree");
37
- /**
38
- * Tokenize parameter string handling nested structures
39
- */
40
- function tokenizeParameterString(str) {
41
- const tokens = [];
42
- let current = '';
43
- let depth = 0;
44
- let inBrackets = false;
45
- for (let i = 0; i < str.length; i++) {
46
- const char = str[i];
47
- if ((char === '<' || char === '[' || char === '(')) {
48
- if (depth === 0) {
49
- if (current.trim()) {
50
- // This is a literal
51
- tokens.push(current.trim());
52
- current = '';
53
- }
54
- inBrackets = true;
55
- }
56
- depth++;
57
- current += char;
58
- }
59
- else if ((char === '>' || char === ']' || char === ')')) {
60
- depth--;
61
- current += char;
62
- if (depth === 0) {
63
- tokens.push(current.trim());
64
- current = '';
65
- inBrackets = false;
66
- }
67
- }
68
- else if (char === ' ' && depth === 0 && !inBrackets) {
69
- if (current.trim()) {
70
- tokens.push(current.trim());
71
- current = '';
72
- }
73
- }
74
- else {
75
- current += char;
76
- }
77
- }
78
- if (current.trim()) {
79
- tokens.push(current.trim());
80
- }
81
- return tokens;
82
- }
83
- /**
84
- * Parse a single parameter token
85
- */
86
- function parseParameter(token, position) {
87
- // Check for choice list (option1|option2|...)
88
- if (token.startsWith('(') && token.endsWith(')')) {
89
- const choicesStr = token.slice(1, -1);
90
- const choices = choicesStr.split('|').map((choice, idx) => ({
91
- type: commandTree_1.ParameterType.LITERAL,
92
- literal: choice.trim(),
93
- optional: false,
94
- position: idx
95
- }));
96
- return {
97
- type: commandTree_1.ParameterType.CHOICE_LIST,
98
- choices,
99
- optional: false,
100
- position
101
- };
102
- }
103
- // Check for optional argument [name] or [<name>]
104
- if (token.startsWith('[') && token.endsWith(']')) {
105
- let name = token.slice(1, -1); // Remove [ and ]
106
- // Also remove inner angle brackets if present
107
- if (name.startsWith('<') && name.endsWith('>')) {
108
- name = name.slice(1, -1); // Remove < and >
109
- return {
110
- type: commandTree_1.ParameterType.ARGUMENT,
111
- name,
112
- optional: true,
113
- position
114
- };
115
- }
116
- // For [literal] without angle brackets, this could be an optional subcommand
117
- // Return as LITERAL but we'll handle it specially in loadCommandDetails
118
- return {
119
- type: commandTree_1.ParameterType.LITERAL,
120
- literal: name, // Store WITHOUT the brackets
121
- optional: true,
122
- position
123
- };
124
- }
125
- // Check for required argument <name>
126
- if (token.startsWith('<') && token.endsWith('>')) {
127
- const name = token.slice(1, -1);
128
- return {
129
- type: commandTree_1.ParameterType.ARGUMENT,
130
- name,
131
- optional: false,
132
- position
133
- };
134
- }
135
- // Otherwise it's a literal (could be a subcommand name)
136
- // We'll determine if it's actually a subcommand later when we see it has members
137
- return {
138
- type: commandTree_1.ParameterType.LITERAL,
139
- literal: token,
140
- optional: false,
141
- position
142
- };
143
- }
144
- /**
145
- * Parse command help output to extract parameters
146
- */
147
- function parseCommandHelp(helpText) {
148
- const parameters = [];
149
- const stripped = (0, ansi_1.stripColors)(helpText).trim();
150
- // Remove the command name from the beginning if present
151
- const syntaxMatch = stripped.match(/^\/?\w+\s+(.*)/);
152
- const paramString = syntaxMatch ? syntaxMatch[1] : stripped;
153
- // Split into tokens - handle nested brackets/parens
154
- const tokens = tokenizeParameterString(paramString);
155
- tokens.forEach((token, index) => {
156
- const param = parseParameter(token, index);
157
- if (param) {
158
- parameters.push(param);
159
- }
160
- });
161
- return parameters;
162
- }
163
- /**
164
- * Classify a tokenized parameter string as either a named subcommand variant
165
- * or a direct parameter list, parsing the relevant tokens along the way.
166
- */
167
- function classifyParameterTokens(tokens) {
168
- if (tokens.length === 0) {
169
- return null;
170
- }
171
- const firstToken = tokens[0];
172
- // Determine if first token is a literal/subcommand or an argument
173
- // FIXED: Better detection for optional subcommands vs optional arguments
174
- let isArgument = false;
175
- if (firstToken.startsWith('<')) {
176
- // <arg> - required argument
177
- isArgument = true;
178
- }
179
- else if (firstToken.startsWith('[') && firstToken.endsWith(']')) {
180
- // Could be [<arg>] or [subcommand]
181
- const inner = firstToken.slice(1, -1);
182
- if (inner.startsWith('<') && inner.endsWith('>')) {
183
- // [<arg>] - optional argument
184
- isArgument = true;
185
- }
186
- else {
187
- // [subcommand] - optional subcommand, treat as subcommand
188
- isArgument = false;
189
- }
190
- }
191
- else if (firstToken.startsWith('(') && firstToken.endsWith(')')) {
192
- // (choice1|choice2) - choice list, treat as argument
193
- isArgument = true;
194
- }
195
- if (isArgument) {
196
- // Every token is a direct parameter of the command/subcommand itself
197
- const parameters = [];
198
- for (let i = 0; i < tokens.length; i++) {
199
- const param = parseParameter(tokens[i], i);
200
- if (param) {
201
- parameters.push(param);
202
- }
203
- }
204
- return { kind: 'direct', parameters };
205
- }
206
- // First token is a literal/subcommand name introducing a variant
207
- let name = firstToken;
208
- let optional = false;
209
- // Strip optional brackets if present
210
- if (name.startsWith('[') && name.endsWith(']')) {
211
- name = name.slice(1, -1); // Remove [ and ]
212
- optional = true;
213
- }
214
- const parameters = [];
215
- for (let i = 1; i < tokens.length; i++) {
216
- const param = parseParameter(tokens[i], i - 1);
217
- if (param) {
218
- parameters.push(param);
219
- }
220
- }
221
- return { kind: 'variant', name, optional, parameters };
222
- }
223
- /**
224
- * Re-split a help response that packs multiple `/cmd ...` entries onto one
225
- * line with no separators (e.g. a `minecraft:help` blob, or vanilla's `help
226
- * <path>` for a multi-variant command like `gamerule`/`team`/`debug`) into
227
- * one entry per line, ready for `parseHelpResponse`/`parseHelpLines`.
228
- *
229
- * Header/separator (`---...`) and blank lines are dropped first, so a
230
- * `§e--------- §fHelp: /<cmd> §e----...` banner line - which itself contains
231
- * a `/` - doesn't get re-split into a bogus `/<cmd> ----...` entry.
232
- */
233
- function splitConcatenatedHelpLines(text) {
234
- return text.split('\n')
235
- .filter(line => {
236
- const stripped = (0, ansi_1.stripColors)(line).trim();
237
- return stripped && !stripped.startsWith('---');
238
- })
239
- .join('\n')
240
- .replace(/\//g, '\n/');
241
- }
242
- /**
243
- * Parse a single (already `stripColors`'d and trimmed) help line as a
244
- * vanilla `minecraft:help` alias redirect of the form `/<alias> -> <target>`
245
- * (e.g. `/tp -> teleport`, `/minecraft:xp -> experience`). Either side may
246
- * carry a namespace prefix (e.g. `minecraft:`); namespace prefixes are
247
- * preserved verbatim on both sides - per "ingest everything", a namespaced
248
- * alias like `minecraft:tp` is its own root command, not folded into `tp`.
249
- * Returns `null` if `line` doesn't match this shape.
250
- */
251
- function parseAliasRedirect(line) {
252
- const match = line.match(/^\/([a-zA-Z0-9_:-]+)\s*->\s*([a-zA-Z0-9_:-]+)$/);
253
- if (!match) {
254
- return null;
255
- }
256
- return { alias: match[1], target: match[2] };
257
- }
258
- /**
259
- * Parse every line of `text` that describes `commandPath`'s syntax (an
260
- * optional leading `/`, then `commandPath` with internal spaces matching
261
- * runs of whitespace, then the argument tokens), collecting subcommand
262
- * variants and/or a direct parameter list. Lines for other commands, alias
263
- * redirects (`-> target`), and lines with no arguments at all are ignored.
264
- *
265
- * `text` is matched line-by-line (split on `\n`) — callers whose source may
266
- * pack multiple commands' syntax onto one line without separators (e.g. a
267
- * `minecraft:help` blob, where consecutive commands are simply concatenated)
268
- * must first replace `/` with `\n/` to re-split it into one command per line.
269
- */
270
- function parseHelpLines(text, commandPath) {
271
- const escaped = commandPath
272
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
273
- .replace(/ /g, '\\s+');
274
- const pattern = new RegExp(`^/?${escaped}(?::)?\\s*(.*)$`, 'i');
275
- const variants = new Map();
276
- let direct = null;
277
- for (const rawLine of text.split('\n')) {
278
- const stripped = (0, ansi_1.stripColors)(rawLine).trim();
279
- if (!stripped || stripped.startsWith('---')) {
280
- continue;
281
- }
282
- const match = stripped.match(pattern);
283
- if (!match) {
284
- continue;
285
- }
286
- const afterCommand = match[1] || '';
287
- if (!afterCommand || afterCommand.startsWith('->')) {
288
- continue;
289
- }
290
- const tokens = tokenizeParameterString(afterCommand);
291
- const classified = classifyParameterTokens(tokens);
292
- if (classified?.kind === 'variant') {
293
- variants.set(classified.name, { optional: classified.optional, members: classified.parameters });
294
- }
295
- else if (classified?.kind === 'direct') {
296
- direct = classified.parameters;
297
- }
298
- }
299
- return { variants, direct };
300
- }
301
- /**
302
- * True iff `parameters` is exactly a bare `<args>`/`[<args>]` placeholder -
303
- * the generic stand-in Bukkit emits (e.g. for `minecraft:help <cmd>` on
304
- * commands that aren't Brigadier-backed, like `version`/`reload`/`plugins`)
305
- * when it has no real argument info, regardless of whether it's optional.
306
- */
307
- function isArgsPlaceholder(parameters) {
308
- return parameters.length === 1
309
- && parameters[0].type === commandTree_1.ParameterType.ARGUMENT
310
- && parameters[0].name === 'args';
311
- }
312
- /**
313
- * True iff `parameters` is exactly the generic `[<args>]` placeholder Bukkit
314
- * emits for `minecraft:help <cmd>` on commands that aren't Brigadier-backed
315
- * (e.g. `version`, `reload`, `plugins`) — i.e. no real argument info.
316
- */
317
- function isGenericArgsPlaceholder(parameters) {
318
- return isArgsPlaceholder(parameters) && parameters[0].optional === true;
319
- }
320
- /**
321
- * True iff `members` carries real, usable argument info for a subcommand
322
- * variant - i.e. it's non-empty and not just an `<args>`/`[<args>]`
323
- * placeholder (which means "no info available", whether or not it's
324
- * optional). Used to decide whether the usage already parsed from a parent
325
- * command's help output is trustworthy enough to skip a further per-subcommand
326
- * help round trip.
327
- */
328
- function hasUsableArguments(members) {
329
- return members.length > 0 && !isArgsPlaceholder(members);
330
- }
331
- /**
332
- * True iff `result` carries real, usable syntax info - either a non-placeholder
333
- * `direct` parameter list or at least one subcommand variant. Used to decide
334
- * whether a help source already answers a command's usage, or whether the
335
- * caller needs to try another source.
336
- */
337
- function hasRealUsage(result) {
338
- return (result.direct !== null && !isGenericArgsPlaceholder(result.direct)) || result.variants.size > 0;
339
- }
340
- /**
341
- * Parse a `/help` or `minecraft:help` response into the root commands and
342
- * alias redirects it describes.
343
- *
344
- * `response` is first run through `splitConcatenatedHelpLines` so that a
345
- * `minecraft:help` blob (multiple `/cmd ...` entries packed onto one line)
346
- * is split one-per-line. Each resulting line is then matched against one of:
347
- *
348
- * - an alias redirect (`/<alias> -> <target>`, via `parseAliasRedirect`)
349
- * - `/command ...` or `/namespace:command ...`
350
- * - `command: ...` or `command-with-hyphens: ...`
351
- * - `- command ...` or `* command ...`
352
- * - `command <args>` (command name followed by `-`/`<`/`[`/`(`)
353
- *
354
- * Namespace prefixes (`minecraft:`, `bukkit:`, ...) are part of the command
355
- * name - per "ingest everything", `minecraft:advancement` is its own root
356
- * command, not just `minecraft`, so `:` is included in every pattern's
357
- * character class. Common non-command words that appear in descriptions
358
- * (`usage`, `example`, `description`, `syntax`) are skipped.
359
- *
360
- * For each command found, `isPlaceholder` reflects whether its summary line
361
- * already carries real syntax info (`hasRealUsage`), so callers can decide
362
- * which help source to try first for that command's full details.
363
- */
364
- function parseHelpResponse(response) {
365
- const lines = splitConcatenatedHelpLines(response).split('\n');
366
- const commands = [];
367
- const aliases = [];
368
- for (const line of lines) {
369
- const stripped = (0, ansi_1.stripColors)(line).trim();
370
- // Skip empty lines and headers
371
- if (!stripped || stripped.startsWith('---') || stripped.startsWith('===')) {
372
- continue;
373
- }
374
- // Alias redirect lines (`/tp -> teleport`) describe an alias, not a
375
- // root command in their own right.
376
- const redirect = parseAliasRedirect(stripped);
377
- if (redirect) {
378
- aliases.push(redirect);
379
- continue;
380
- }
381
- const patterns = [
382
- /^\/([a-zA-Z0-9_:-]+)/, // /command or /namespace:command
383
- /^([a-zA-Z0-9_:-]+):\s/, // command: or command-with-hyphens:
384
- /^[-*]\s*([a-zA-Z0-9_:-]+)/, // - command or * command
385
- /^([a-zA-Z0-9_:-]+)\s+[-<[(]/ // command followed by args
386
- ];
387
- for (const pattern of patterns) {
388
- const match = stripped.match(pattern);
389
- if (!match) {
390
- continue;
391
- }
392
- const commandName = match[1];
393
- // Skip common non-command words that appear in descriptions
394
- if (['usage', 'example', 'description', 'syntax'].includes(commandName.toLowerCase())) {
395
- continue;
396
- }
397
- const summary = parseHelpLines(stripped, commandName);
398
- commands.push({ name: commandName, isPlaceholder: !hasRealUsage(summary) });
399
- break;
400
- }
401
- }
402
- return { commands, aliases };
403
- }
404
- /**
405
- * True iff `response` is the Brigadier "unknown namespace" syntax error
406
- * returned for `minecraft:help` on servers where the `minecraft:` command
407
- * namespace prefix isn't registered (vanilla/fabric) — distinct from the
408
- * normal "Unknown command or insufficient permissions" not-found message.
409
- */
410
- function isUnsupportedNamespaceError(response) {
411
- return /^Unknown or incomplete command/i.test((0, ansi_1.stripColors)(response).trim());
412
- }
413
- /**
414
- * Build the parameter structure representing a set of collected variants:
415
- * a single SUBCOMMAND if there's only one, or a CHOICE_LIST wrapping a
416
- * SUBCOMMAND for each variant (in encounter order) otherwise.
417
- */
418
- function buildParameterStructureFromVariants(variants) {
419
- if (variants.size === 0) {
420
- return [];
421
- }
422
- const subcommandChoices = [];
423
- for (const [name, { optional, members }] of variants) {
424
- subcommandChoices.push({
425
- type: commandTree_1.ParameterType.SUBCOMMAND,
426
- name,
427
- literal: name,
428
- optional,
429
- position: subcommandChoices.length,
430
- members,
431
- isComplete: false
432
- });
433
- }
434
- // If there's only one variant, use it directly; otherwise wrap in a choice list
435
- if (subcommandChoices.length === 1) {
436
- return [subcommandChoices[0]];
437
- }
438
- return [{
439
- type: commandTree_1.ParameterType.CHOICE_LIST,
440
- optional: false,
441
- position: 0,
442
- choices: subcommandChoices
443
- }];
444
- }
445
- //# sourceMappingURL=helpTextParsing.js.map
@@ -1,46 +0,0 @@
1
- "use strict";
2
- // src/historySearch.ts
3
- //
4
- // Pure state and matching logic for Ctrl+R reverse history search. Unlike
5
- // completionEngine.ts, there's no network round trip involved — searching is
6
- // just filtering an in-memory array — so this is plain synchronous state
7
- // transitions, no reducer/effect machinery needed.
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.searchHistory = searchHistory;
10
- exports.startHistorySearch = startHistorySearch;
11
- exports.setHistorySearchQuery = setHistorySearchQuery;
12
- exports.cycleHistorySearch = cycleHistorySearch;
13
- /** Entries from `history` (oldest-first) containing `query` (case-insensitive), most-recently-used first, deduplicated. An empty query matches everything, so the list starts as "recent history, newest first". */
14
- function searchHistory(history, query) {
15
- const lowerQuery = query.toLowerCase();
16
- const seen = new Set();
17
- const results = [];
18
- for (let i = history.length - 1; i >= 0; i--) {
19
- const entry = history[i];
20
- if (seen.has(entry)) {
21
- continue;
22
- }
23
- seen.add(entry);
24
- if (entry.toLowerCase().includes(lowerQuery)) {
25
- results.push(entry);
26
- }
27
- }
28
- return results;
29
- }
30
- /** Enters search mode: empty query, recent history shown newest-first. */
31
- function startHistorySearch(history, originalLine) {
32
- return { query: '', items: searchHistory(history, ''), selectedIndex: 0, originalLine };
33
- }
34
- /** Re-filters as the query changes (typing or backspacing), resetting to the best (first) match. */
35
- function setHistorySearchQuery(history, state, query) {
36
- return { ...state, query, items: searchHistory(history, query), selectedIndex: 0 };
37
- }
38
- /** Moves the selection by `delta`, wrapping; a no-op when there's nothing to select. */
39
- function cycleHistorySearch(state, delta) {
40
- if (state.items.length === 0) {
41
- return state;
42
- }
43
- const selectedIndex = ((state.selectedIndex + delta) % state.items.length + state.items.length) % state.items.length;
44
- return { ...state, selectedIndex };
45
- }
46
- //# sourceMappingURL=historySearch.js.map