@asm-agent/tui 0.8.2

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.
Files changed (130) hide show
  1. package/README.md +795 -0
  2. package/dist/autocomplete.d.ts +61 -0
  3. package/dist/autocomplete.d.ts.map +1 -0
  4. package/dist/autocomplete.js +601 -0
  5. package/dist/autocomplete.js.map +1 -0
  6. package/dist/components/box.d.ts +24 -0
  7. package/dist/components/box.d.ts.map +1 -0
  8. package/dist/components/box.js +114 -0
  9. package/dist/components/box.js.map +1 -0
  10. package/dist/components/cancellable-loader.d.ts +22 -0
  11. package/dist/components/cancellable-loader.d.ts.map +1 -0
  12. package/dist/components/cancellable-loader.js +35 -0
  13. package/dist/components/cancellable-loader.js.map +1 -0
  14. package/dist/components/editor.d.ts +275 -0
  15. package/dist/components/editor.d.ts.map +1 -0
  16. package/dist/components/editor.js +1968 -0
  17. package/dist/components/editor.js.map +1 -0
  18. package/dist/components/image.d.ts +34 -0
  19. package/dist/components/image.d.ts.map +1 -0
  20. package/dist/components/image.js +96 -0
  21. package/dist/components/image.js.map +1 -0
  22. package/dist/components/input.d.ts +37 -0
  23. package/dist/components/input.d.ts.map +1 -0
  24. package/dist/components/input.js +385 -0
  25. package/dist/components/input.js.map +1 -0
  26. package/dist/components/loader.d.ts +29 -0
  27. package/dist/components/loader.d.ts.map +1 -0
  28. package/dist/components/loader.js +69 -0
  29. package/dist/components/loader.js.map +1 -0
  30. package/dist/components/markdown.d.ts +100 -0
  31. package/dist/components/markdown.d.ts.map +1 -0
  32. package/dist/components/markdown.js +773 -0
  33. package/dist/components/markdown.js.map +1 -0
  34. package/dist/components/select-list.d.ts +61 -0
  35. package/dist/components/select-list.d.ts.map +1 -0
  36. package/dist/components/select-list.js +206 -0
  37. package/dist/components/select-list.js.map +1 -0
  38. package/dist/components/settings-list.d.ts +49 -0
  39. package/dist/components/settings-list.d.ts.map +1 -0
  40. package/dist/components/settings-list.js +170 -0
  41. package/dist/components/settings-list.js.map +1 -0
  42. package/dist/components/spacer.d.ts +12 -0
  43. package/dist/components/spacer.d.ts.map +1 -0
  44. package/dist/components/spacer.js +21 -0
  45. package/dist/components/spacer.js.map +1 -0
  46. package/dist/components/text.d.ts +19 -0
  47. package/dist/components/text.d.ts.map +1 -0
  48. package/dist/components/text.js +77 -0
  49. package/dist/components/text.js.map +1 -0
  50. package/dist/components/truncated-text.d.ts +14 -0
  51. package/dist/components/truncated-text.d.ts.map +1 -0
  52. package/dist/components/truncated-text.js +44 -0
  53. package/dist/components/truncated-text.js.map +1 -0
  54. package/dist/editor-component.d.ts +51 -0
  55. package/dist/editor-component.d.ts.map +1 -0
  56. package/dist/editor-component.js +2 -0
  57. package/dist/editor-component.js.map +1 -0
  58. package/dist/fullscreen.d.ts +105 -0
  59. package/dist/fullscreen.d.ts.map +1 -0
  60. package/dist/fullscreen.js +597 -0
  61. package/dist/fullscreen.js.map +1 -0
  62. package/dist/fuzzy.d.ts +26 -0
  63. package/dist/fuzzy.d.ts.map +1 -0
  64. package/dist/fuzzy.js +114 -0
  65. package/dist/fuzzy.js.map +1 -0
  66. package/dist/index.d.ts +29 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +27 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/keybindings.d.ts +239 -0
  71. package/dist/keybindings.d.ts.map +1 -0
  72. package/dist/keybindings.js +222 -0
  73. package/dist/keybindings.js.map +1 -0
  74. package/dist/keys.d.ts +184 -0
  75. package/dist/keys.d.ts.map +1 -0
  76. package/dist/keys.js +1163 -0
  77. package/dist/keys.js.map +1 -0
  78. package/dist/kill-ring.d.ts +26 -0
  79. package/dist/kill-ring.d.ts.map +1 -0
  80. package/dist/kill-ring.js +42 -0
  81. package/dist/kill-ring.js.map +1 -0
  82. package/dist/latex.d.ts +21 -0
  83. package/dist/latex.d.ts.map +1 -0
  84. package/dist/latex.js +797 -0
  85. package/dist/latex.js.map +1 -0
  86. package/dist/mouse.d.ts +24 -0
  87. package/dist/mouse.d.ts.map +1 -0
  88. package/dist/mouse.js +34 -0
  89. package/dist/mouse.js.map +1 -0
  90. package/dist/render-cache.d.ts +9 -0
  91. package/dist/render-cache.d.ts.map +1 -0
  92. package/dist/render-cache.js +23 -0
  93. package/dist/render-cache.js.map +1 -0
  94. package/dist/selection-metadata.d.ts +22 -0
  95. package/dist/selection-metadata.d.ts.map +1 -0
  96. package/dist/selection-metadata.js +141 -0
  97. package/dist/selection-metadata.js.map +1 -0
  98. package/dist/slash-command-context.d.ts +12 -0
  99. package/dist/slash-command-context.d.ts.map +1 -0
  100. package/dist/slash-command-context.js +32 -0
  101. package/dist/slash-command-context.js.map +1 -0
  102. package/dist/stdin-buffer.d.ts +50 -0
  103. package/dist/stdin-buffer.d.ts.map +1 -0
  104. package/dist/stdin-buffer.js +313 -0
  105. package/dist/stdin-buffer.js.map +1 -0
  106. package/dist/terminal-colors.d.ts +31 -0
  107. package/dist/terminal-colors.d.ts.map +1 -0
  108. package/dist/terminal-colors.js +161 -0
  109. package/dist/terminal-colors.js.map +1 -0
  110. package/dist/terminal-image.d.ts +85 -0
  111. package/dist/terminal-image.d.ts.map +1 -0
  112. package/dist/terminal-image.js +324 -0
  113. package/dist/terminal-image.js.map +1 -0
  114. package/dist/terminal.d.ts +113 -0
  115. package/dist/terminal.d.ts.map +1 -0
  116. package/dist/terminal.js +486 -0
  117. package/dist/terminal.js.map +1 -0
  118. package/dist/tui.d.ts +310 -0
  119. package/dist/tui.d.ts.map +1 -0
  120. package/dist/tui.js +1673 -0
  121. package/dist/tui.js.map +1 -0
  122. package/dist/undo-stack.d.ts +16 -0
  123. package/dist/undo-stack.d.ts.map +1 -0
  124. package/dist/undo-stack.js +26 -0
  125. package/dist/undo-stack.js.map +1 -0
  126. package/dist/utils.d.ts +98 -0
  127. package/dist/utils.d.ts.map +1 -0
  128. package/dist/utils.js +1233 -0
  129. package/dist/utils.js.map +1 -0
  130. package/package.json +52 -0
@@ -0,0 +1,601 @@
1
+ import { spawn } from "child_process";
2
+ import { readdirSync, statSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { basename, dirname, join } from "path";
5
+ import { fuzzyFilter } from "./fuzzy.js";
6
+ import { getSlashCommandContext } from "./slash-command-context.js";
7
+ const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
8
+ function toDisplayPath(value) {
9
+ return value.replace(/\\/g, "/");
10
+ }
11
+ function escapeRegex(value) {
12
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
+ }
14
+ function buildFdPathQuery(query) {
15
+ const normalized = toDisplayPath(query);
16
+ if (!normalized.includes("/")) {
17
+ return normalized;
18
+ }
19
+ const hasTrailingSeparator = normalized.endsWith("/");
20
+ const trimmed = normalized.replace(/^\/+|\/+$/g, "");
21
+ if (!trimmed) {
22
+ return normalized;
23
+ }
24
+ const separatorPattern = "[\\\\/]";
25
+ const segments = trimmed
26
+ .split("/")
27
+ .filter(Boolean)
28
+ .map((segment) => escapeRegex(segment));
29
+ if (segments.length === 0) {
30
+ return normalized;
31
+ }
32
+ let pattern = segments.join(separatorPattern);
33
+ if (hasTrailingSeparator) {
34
+ pattern += separatorPattern;
35
+ }
36
+ return pattern;
37
+ }
38
+ function findLastDelimiter(text) {
39
+ for (let i = text.length - 1; i >= 0; i -= 1) {
40
+ if (PATH_DELIMITERS.has(text[i] ?? "")) {
41
+ return i;
42
+ }
43
+ }
44
+ return -1;
45
+ }
46
+ function findUnclosedQuoteStart(text) {
47
+ let inQuotes = false;
48
+ let quoteStart = -1;
49
+ for (let i = 0; i < text.length; i += 1) {
50
+ if (text[i] === '"') {
51
+ inQuotes = !inQuotes;
52
+ if (inQuotes) {
53
+ quoteStart = i;
54
+ }
55
+ }
56
+ }
57
+ return inQuotes ? quoteStart : null;
58
+ }
59
+ function isTokenStart(text, index) {
60
+ return index === 0 || PATH_DELIMITERS.has(text[index - 1] ?? "");
61
+ }
62
+ function extractQuotedPrefix(text) {
63
+ const quoteStart = findUnclosedQuoteStart(text);
64
+ if (quoteStart === null) {
65
+ return null;
66
+ }
67
+ if (quoteStart > 0 && text[quoteStart - 1] === "@") {
68
+ if (!isTokenStart(text, quoteStart - 1)) {
69
+ return null;
70
+ }
71
+ return text.slice(quoteStart - 1);
72
+ }
73
+ if (!isTokenStart(text, quoteStart)) {
74
+ return null;
75
+ }
76
+ return text.slice(quoteStart);
77
+ }
78
+ function parsePathPrefix(prefix) {
79
+ if (prefix.startsWith('@"')) {
80
+ return { rawPrefix: prefix.slice(2), isAtPrefix: true, isQuotedPrefix: true };
81
+ }
82
+ if (prefix.startsWith('"')) {
83
+ return { rawPrefix: prefix.slice(1), isAtPrefix: false, isQuotedPrefix: true };
84
+ }
85
+ if (prefix.startsWith("@")) {
86
+ return { rawPrefix: prefix.slice(1), isAtPrefix: true, isQuotedPrefix: false };
87
+ }
88
+ return { rawPrefix: prefix, isAtPrefix: false, isQuotedPrefix: false };
89
+ }
90
+ function buildCompletionValue(path, options) {
91
+ const needsQuotes = options.isQuotedPrefix || path.includes(" ");
92
+ const prefix = options.isAtPrefix ? "@" : "";
93
+ if (!needsQuotes) {
94
+ return `${prefix}${path}`;
95
+ }
96
+ const openQuote = `${prefix}"`;
97
+ const closeQuote = '"';
98
+ return `${openQuote}${path}${closeQuote}`;
99
+ }
100
+ async function walkDirectoryWithFd(baseDir, fdPath, query, maxResults, signal) {
101
+ const args = [
102
+ "--base-directory",
103
+ baseDir,
104
+ "--max-results",
105
+ String(maxResults),
106
+ "--type",
107
+ "f",
108
+ "--type",
109
+ "d",
110
+ "--follow",
111
+ "--hidden",
112
+ "--exclude",
113
+ ".git",
114
+ "--exclude",
115
+ ".git/*",
116
+ "--exclude",
117
+ ".git/**",
118
+ ];
119
+ if (toDisplayPath(query).includes("/")) {
120
+ args.push("--full-path");
121
+ }
122
+ if (query) {
123
+ args.push(buildFdPathQuery(query));
124
+ }
125
+ return await new Promise((resolve) => {
126
+ if (signal.aborted) {
127
+ resolve([]);
128
+ return;
129
+ }
130
+ const child = spawn(fdPath, args, {
131
+ stdio: ["ignore", "pipe", "pipe"],
132
+ });
133
+ let stdout = "";
134
+ let resolved = false;
135
+ const finish = (results) => {
136
+ if (resolved)
137
+ return;
138
+ resolved = true;
139
+ signal.removeEventListener("abort", onAbort);
140
+ resolve(results);
141
+ };
142
+ const onAbort = () => {
143
+ if (child.exitCode === null) {
144
+ child.kill("SIGKILL");
145
+ }
146
+ };
147
+ signal.addEventListener("abort", onAbort, { once: true });
148
+ child.stdout.setEncoding("utf-8");
149
+ child.stdout.on("data", (chunk) => {
150
+ stdout += chunk;
151
+ });
152
+ child.on("error", () => {
153
+ finish([]);
154
+ });
155
+ child.on("close", (code) => {
156
+ if (signal.aborted || code !== 0 || !stdout) {
157
+ finish([]);
158
+ return;
159
+ }
160
+ const lines = stdout.trim().split("\n").filter(Boolean);
161
+ const results = [];
162
+ for (const line of lines) {
163
+ const displayLine = toDisplayPath(line);
164
+ const hasTrailingSeparator = displayLine.endsWith("/");
165
+ const normalizedPath = hasTrailingSeparator ? displayLine.slice(0, -1) : displayLine;
166
+ if (normalizedPath === ".git" || normalizedPath.startsWith(".git/") || normalizedPath.includes("/.git/")) {
167
+ continue;
168
+ }
169
+ results.push({
170
+ path: displayLine,
171
+ isDirectory: hasTrailingSeparator,
172
+ });
173
+ }
174
+ finish(results);
175
+ });
176
+ });
177
+ }
178
+ export class CombinedAutocompleteProvider {
179
+ commands;
180
+ basePath;
181
+ fdPath;
182
+ constructor(commands = [], basePath, fdPath = null) {
183
+ this.commands = commands;
184
+ this.basePath = basePath;
185
+ this.fdPath = fdPath;
186
+ }
187
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
188
+ const currentLine = lines[cursorLine] || "";
189
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
190
+ const atPrefix = this.extractAtPrefix(textBeforeCursor);
191
+ if (atPrefix) {
192
+ const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
193
+ const suggestions = await this.getFuzzyFileSuggestions(rawPrefix, {
194
+ isQuotedPrefix,
195
+ signal: options.signal,
196
+ });
197
+ if (suggestions.length === 0)
198
+ return null;
199
+ return {
200
+ items: suggestions,
201
+ prefix: atPrefix,
202
+ kind: "file",
203
+ };
204
+ }
205
+ const slashContext = options.force ? null : getSlashCommandContext(lines, cursorLine, cursorCol);
206
+ if (slashContext?.kind === "name") {
207
+ const prefix = slashContext.prefix.slice(1);
208
+ const commandItems = this.commands.map((cmd) => {
209
+ const name = "name" in cmd ? cmd.name : cmd.value;
210
+ const aliases = "aliases" in cmd && cmd.aliases ? cmd.aliases : [];
211
+ const argumentHint = "argumentHint" in cmd && cmd.argumentHint ? cmd.argumentHint : undefined;
212
+ const sourceTag = "sourceTag" in cmd && cmd.sourceTag ? cmd.sourceTag : undefined;
213
+ const takesArgument = "takesArgument" in cmd ? cmd.takesArgument === true : false;
214
+ return {
215
+ name,
216
+ searchText: [name, ...aliases].join(" "),
217
+ label: name,
218
+ description: cmd.description,
219
+ argumentHint,
220
+ sourceTag,
221
+ takesArgument,
222
+ };
223
+ });
224
+ const filtered = fuzzyFilter(commandItems, prefix, (item) => item.searchText).map((item) => ({
225
+ value: item.name,
226
+ label: item.label,
227
+ ...(item.description && { description: item.description }),
228
+ ...(item.argumentHint && { argumentHint: item.argumentHint }),
229
+ ...(item.sourceTag && { sourceTag: item.sourceTag }),
230
+ ...(item.takesArgument && { takesArgument: true }),
231
+ }));
232
+ if (filtered.length > 0) {
233
+ return {
234
+ items: filtered,
235
+ prefix: slashContext.prefix,
236
+ kind: "slash-command",
237
+ };
238
+ }
239
+ return null;
240
+ }
241
+ else if (slashContext?.kind === "argument") {
242
+ const command = this.commands.find((cmd) => {
243
+ const name = "name" in cmd ? cmd.name : cmd.value;
244
+ return name === slashContext.commandName;
245
+ });
246
+ if (!command || !("getArgumentCompletions" in command) || !command.getArgumentCompletions) {
247
+ return null;
248
+ }
249
+ const argumentSuggestions = await command.getArgumentCompletions(slashContext.prefix);
250
+ if (!Array.isArray(argumentSuggestions) || argumentSuggestions.length === 0) {
251
+ return null;
252
+ }
253
+ return {
254
+ items: argumentSuggestions,
255
+ prefix: slashContext.prefix,
256
+ };
257
+ }
258
+ const pathMatch = this.extractPathPrefix(textBeforeCursor, options.force ?? false);
259
+ if (pathMatch === null) {
260
+ return null;
261
+ }
262
+ const suggestions = this.getFileSuggestions(pathMatch);
263
+ if (suggestions.length === 0)
264
+ return null;
265
+ return {
266
+ items: suggestions,
267
+ prefix: pathMatch,
268
+ kind: "file",
269
+ };
270
+ }
271
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
272
+ const currentLine = lines[cursorLine] || "";
273
+ const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
274
+ const afterCursor = currentLine.slice(cursorCol);
275
+ const isQuotedPrefix = prefix.startsWith('"') || prefix.startsWith('@"');
276
+ const hasLeadingQuoteAfterCursor = afterCursor.startsWith('"');
277
+ const hasTrailingQuoteInItem = item.value.endsWith('"');
278
+ const adjustedAfterCursor = isQuotedPrefix && hasTrailingQuoteInItem && hasLeadingQuoteAfterCursor ? afterCursor.slice(1) : afterCursor;
279
+ const slashContext = getSlashCommandContext(lines, cursorLine, cursorCol);
280
+ const isSlashCommand = slashContext?.kind === "name" &&
281
+ slashContext.prefix === prefix &&
282
+ this.commands.some((command) => ("name" in command ? command.name : command.value) === item.value);
283
+ if (isSlashCommand) {
284
+ const hasSeparatorAfterCursor = /^[ \t]/.test(adjustedAfterCursor);
285
+ const separator = hasSeparatorAfterCursor ? "" : " ";
286
+ const newLine = `${beforePrefix}/${item.value}${separator}${adjustedAfterCursor}`;
287
+ const newLines = [...lines];
288
+ newLines[cursorLine] = newLine;
289
+ return {
290
+ lines: newLines,
291
+ cursorLine,
292
+ cursorCol: beforePrefix.length + item.value.length + 2,
293
+ };
294
+ }
295
+ if (prefix.startsWith("@")) {
296
+ // Don't add space after directories so user can continue autocompleting
297
+ const isDirectory = item.label.endsWith("/");
298
+ const suffix = isDirectory ? "" : " ";
299
+ const newLine = `${beforePrefix + item.value}${suffix}${adjustedAfterCursor}`;
300
+ const newLines = [...lines];
301
+ newLines[cursorLine] = newLine;
302
+ const hasTrailingQuote = item.value.endsWith('"');
303
+ const cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;
304
+ return {
305
+ lines: newLines,
306
+ cursorLine,
307
+ cursorCol: beforePrefix.length + cursorOffset + suffix.length,
308
+ };
309
+ }
310
+ if (slashContext?.kind === "argument" && slashContext.prefix === prefix) {
311
+ const newLine = beforePrefix + item.value + adjustedAfterCursor;
312
+ const newLines = [...lines];
313
+ newLines[cursorLine] = newLine;
314
+ const isDirectory = item.label.endsWith("/");
315
+ const hasTrailingQuote = item.value.endsWith('"');
316
+ const cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;
317
+ return {
318
+ lines: newLines,
319
+ cursorLine,
320
+ cursorCol: beforePrefix.length + cursorOffset,
321
+ };
322
+ }
323
+ const newLine = beforePrefix + item.value + adjustedAfterCursor;
324
+ const newLines = [...lines];
325
+ newLines[cursorLine] = newLine;
326
+ const isDirectory = item.label.endsWith("/");
327
+ const hasTrailingQuote = item.value.endsWith('"');
328
+ const cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;
329
+ return {
330
+ lines: newLines,
331
+ cursorLine,
332
+ cursorCol: beforePrefix.length + cursorOffset,
333
+ };
334
+ }
335
+ extractAtPrefix(text) {
336
+ const quotedPrefix = extractQuotedPrefix(text);
337
+ if (quotedPrefix?.startsWith('@"')) {
338
+ return quotedPrefix;
339
+ }
340
+ const lastDelimiterIndex = findLastDelimiter(text);
341
+ const tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
342
+ if (text[tokenStart] === "@") {
343
+ return text.slice(tokenStart);
344
+ }
345
+ return null;
346
+ }
347
+ extractPathPrefix(text, forceExtract = false) {
348
+ const quotedPrefix = extractQuotedPrefix(text);
349
+ if (quotedPrefix) {
350
+ return quotedPrefix;
351
+ }
352
+ const lastDelimiterIndex = findLastDelimiter(text);
353
+ const pathPrefix = lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1);
354
+ if (forceExtract) {
355
+ return pathPrefix;
356
+ }
357
+ // For natural triggers, return if it looks like a path, ends with /, starts with ~/, .
358
+ if (pathPrefix.includes("/") || pathPrefix.startsWith(".") || pathPrefix.startsWith("~/")) {
359
+ return pathPrefix;
360
+ }
361
+ if (pathPrefix === "" && text.endsWith(" ")) {
362
+ return pathPrefix;
363
+ }
364
+ return null;
365
+ }
366
+ expandHomePath(path) {
367
+ if (path.startsWith("~/")) {
368
+ const expandedPath = join(homedir(), path.slice(2));
369
+ return path.endsWith("/") && !expandedPath.endsWith("/") ? `${expandedPath}/` : expandedPath;
370
+ }
371
+ else if (path === "~") {
372
+ return homedir();
373
+ }
374
+ return path;
375
+ }
376
+ resolveScopedFuzzyQuery(rawQuery) {
377
+ const normalizedQuery = toDisplayPath(rawQuery);
378
+ const slashIndex = normalizedQuery.lastIndexOf("/");
379
+ if (slashIndex === -1) {
380
+ return null;
381
+ }
382
+ const displayBase = normalizedQuery.slice(0, slashIndex + 1);
383
+ const query = normalizedQuery.slice(slashIndex + 1);
384
+ let baseDir;
385
+ if (displayBase.startsWith("~/")) {
386
+ baseDir = this.expandHomePath(displayBase);
387
+ }
388
+ else if (displayBase.startsWith("/")) {
389
+ baseDir = displayBase;
390
+ }
391
+ else {
392
+ baseDir = join(this.basePath, displayBase);
393
+ }
394
+ try {
395
+ if (!statSync(baseDir).isDirectory()) {
396
+ return null;
397
+ }
398
+ }
399
+ catch {
400
+ return null;
401
+ }
402
+ return { baseDir, query, displayBase };
403
+ }
404
+ scopedPathForDisplay(displayBase, relativePath) {
405
+ const normalizedRelativePath = toDisplayPath(relativePath);
406
+ if (displayBase === "/") {
407
+ return `/${normalizedRelativePath}`;
408
+ }
409
+ return `${toDisplayPath(displayBase)}${normalizedRelativePath}`;
410
+ }
411
+ getFileSuggestions(prefix) {
412
+ try {
413
+ let searchDir;
414
+ let searchPrefix;
415
+ const { rawPrefix, isAtPrefix, isQuotedPrefix } = parsePathPrefix(prefix);
416
+ let expandedPrefix = rawPrefix;
417
+ if (expandedPrefix.startsWith("~")) {
418
+ expandedPrefix = this.expandHomePath(expandedPrefix);
419
+ }
420
+ const isRootPrefix = rawPrefix === "" ||
421
+ rawPrefix === "./" ||
422
+ rawPrefix === "../" ||
423
+ rawPrefix === "~" ||
424
+ rawPrefix === "~/" ||
425
+ rawPrefix === "/" ||
426
+ (isAtPrefix && rawPrefix === "");
427
+ if (isRootPrefix) {
428
+ if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) {
429
+ searchDir = expandedPrefix;
430
+ }
431
+ else {
432
+ searchDir = join(this.basePath, expandedPrefix);
433
+ }
434
+ searchPrefix = "";
435
+ }
436
+ else if (rawPrefix.endsWith("/")) {
437
+ if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) {
438
+ searchDir = expandedPrefix;
439
+ }
440
+ else {
441
+ searchDir = join(this.basePath, expandedPrefix);
442
+ }
443
+ searchPrefix = "";
444
+ }
445
+ else {
446
+ const dir = dirname(expandedPrefix);
447
+ const file = basename(expandedPrefix);
448
+ if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) {
449
+ searchDir = dir;
450
+ }
451
+ else {
452
+ searchDir = join(this.basePath, dir);
453
+ }
454
+ searchPrefix = file;
455
+ }
456
+ const entries = readdirSync(searchDir, { withFileTypes: true });
457
+ const suggestions = [];
458
+ for (const entry of entries) {
459
+ if (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) {
460
+ continue;
461
+ }
462
+ let isDirectory = entry.isDirectory();
463
+ if (!isDirectory && entry.isSymbolicLink()) {
464
+ try {
465
+ const fullPath = join(searchDir, entry.name);
466
+ isDirectory = statSync(fullPath).isDirectory();
467
+ }
468
+ catch {
469
+ // Ignore broken or inaccessible symbolic links.
470
+ }
471
+ }
472
+ let relativePath;
473
+ const name = entry.name;
474
+ const displayPrefix = rawPrefix;
475
+ if (displayPrefix.endsWith("/")) {
476
+ relativePath = displayPrefix + name;
477
+ }
478
+ else if (displayPrefix.includes("/") || displayPrefix.includes("\\")) {
479
+ if (displayPrefix.startsWith("~/")) {
480
+ const homeRelativeDir = displayPrefix.slice(2); // Remove ~/
481
+ const dir = dirname(homeRelativeDir);
482
+ relativePath = `~/${dir === "." ? name : join(dir, name)}`;
483
+ }
484
+ else if (displayPrefix.startsWith("/")) {
485
+ const dir = dirname(displayPrefix);
486
+ if (dir === "/") {
487
+ relativePath = `/${name}`;
488
+ }
489
+ else {
490
+ relativePath = `${dir}/${name}`;
491
+ }
492
+ }
493
+ else {
494
+ relativePath = join(dirname(displayPrefix), name);
495
+ if (displayPrefix.startsWith("./") && !relativePath.startsWith("./")) {
496
+ relativePath = `./${relativePath}`;
497
+ }
498
+ }
499
+ }
500
+ else {
501
+ if (displayPrefix.startsWith("~")) {
502
+ relativePath = `~/${name}`;
503
+ }
504
+ else {
505
+ relativePath = name;
506
+ }
507
+ }
508
+ relativePath = toDisplayPath(relativePath);
509
+ const pathValue = isDirectory ? `${relativePath}/` : relativePath;
510
+ const value = buildCompletionValue(pathValue, {
511
+ isDirectory,
512
+ isAtPrefix,
513
+ isQuotedPrefix,
514
+ });
515
+ suggestions.push({
516
+ value,
517
+ label: name + (isDirectory ? "/" : ""),
518
+ });
519
+ }
520
+ suggestions.sort((a, b) => {
521
+ const aIsDir = a.value.endsWith("/");
522
+ const bIsDir = b.value.endsWith("/");
523
+ if (aIsDir && !bIsDir)
524
+ return -1;
525
+ if (!aIsDir && bIsDir)
526
+ return 1;
527
+ return a.label.localeCompare(b.label);
528
+ });
529
+ return suggestions;
530
+ }
531
+ catch (_e) {
532
+ return [];
533
+ }
534
+ }
535
+ scoreEntry(filePath, query, isDirectory) {
536
+ const fileName = basename(filePath);
537
+ const lowerFileName = fileName.toLowerCase();
538
+ const lowerQuery = query.toLowerCase();
539
+ let score = 0;
540
+ if (lowerFileName === lowerQuery)
541
+ score = 100;
542
+ else if (lowerFileName.startsWith(lowerQuery))
543
+ score = 80;
544
+ else if (lowerFileName.includes(lowerQuery))
545
+ score = 50;
546
+ else if (filePath.toLowerCase().includes(lowerQuery))
547
+ score = 30;
548
+ if (isDirectory && score > 0)
549
+ score += 10;
550
+ return score;
551
+ }
552
+ async getFuzzyFileSuggestions(query, options) {
553
+ if (!this.fdPath || options.signal.aborted) {
554
+ return [];
555
+ }
556
+ try {
557
+ const scopedQuery = this.resolveScopedFuzzyQuery(query);
558
+ const fdBaseDir = scopedQuery?.baseDir ?? this.basePath;
559
+ const fdQuery = scopedQuery?.query ?? query;
560
+ const entries = await walkDirectoryWithFd(fdBaseDir, this.fdPath, fdQuery, 100, options.signal);
561
+ if (options.signal.aborted) {
562
+ return [];
563
+ }
564
+ const scoredEntries = entries
565
+ .map((entry) => ({
566
+ ...entry,
567
+ score: fdQuery ? this.scoreEntry(entry.path, fdQuery, entry.isDirectory) : 1,
568
+ }))
569
+ .filter((entry) => entry.score > 0);
570
+ scoredEntries.sort((a, b) => b.score - a.score);
571
+ const topEntries = scoredEntries.slice(0, 20);
572
+ const suggestions = [];
573
+ for (const { path: entryPath, isDirectory } of topEntries) {
574
+ const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
575
+ const displayPath = scopedQuery
576
+ ? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash)
577
+ : pathWithoutSlash;
578
+ const entryName = basename(pathWithoutSlash);
579
+ const completionPath = isDirectory ? `${displayPath}/` : displayPath;
580
+ const value = buildCompletionValue(completionPath, {
581
+ isDirectory,
582
+ isAtPrefix: true,
583
+ isQuotedPrefix: options.isQuotedPrefix,
584
+ });
585
+ suggestions.push({
586
+ value,
587
+ label: entryName + (isDirectory ? "/" : ""),
588
+ description: displayPath,
589
+ });
590
+ }
591
+ return suggestions;
592
+ }
593
+ catch {
594
+ return [];
595
+ }
596
+ }
597
+ shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
598
+ return getSlashCommandContext(lines, cursorLine, cursorCol)?.kind !== "name";
599
+ }
600
+ }
601
+ //# sourceMappingURL=autocomplete.js.map