@dungle-scrubs/tallow 0.9.3 → 0.9.6

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 (207) hide show
  1. package/dist/cli.js +7 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/interactive-mode-patch.d.ts +24 -10
  6. package/dist/interactive-mode-patch.d.ts.map +1 -1
  7. package/dist/interactive-mode-patch.js +285 -148
  8. package/dist/interactive-mode-patch.js.map +1 -1
  9. package/dist/interactive-reset.d.ts +49 -0
  10. package/dist/interactive-reset.d.ts.map +1 -0
  11. package/dist/interactive-reset.js +40 -0
  12. package/dist/interactive-reset.js.map +1 -0
  13. package/dist/pi-tui-editor-patch.d.ts +10 -0
  14. package/dist/pi-tui-editor-patch.d.ts.map +1 -0
  15. package/dist/pi-tui-editor-patch.js +159 -0
  16. package/dist/pi-tui-editor-patch.js.map +1 -0
  17. package/dist/pi-tui-patch.d.ts +2 -0
  18. package/dist/pi-tui-patch.d.ts.map +1 -0
  19. package/dist/pi-tui-patch.js +563 -0
  20. package/dist/pi-tui-patch.js.map +1 -0
  21. package/dist/pi-tui-settings-list-patch.d.ts +11 -0
  22. package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
  23. package/dist/pi-tui-settings-list-patch.js +38 -0
  24. package/dist/pi-tui-settings-list-patch.js.map +1 -0
  25. package/dist/reset-diagnostics.d.ts +69 -0
  26. package/dist/reset-diagnostics.d.ts.map +1 -0
  27. package/dist/reset-diagnostics.js +41 -0
  28. package/dist/reset-diagnostics.js.map +1 -0
  29. package/dist/sdk.d.ts +5 -21
  30. package/dist/sdk.d.ts.map +1 -1
  31. package/dist/sdk.js +180 -149
  32. package/dist/sdk.js.map +1 -1
  33. package/dist/workspace-transition-interactive.d.ts +1 -0
  34. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  35. package/dist/workspace-transition-interactive.js +7 -17
  36. package/dist/workspace-transition-interactive.js.map +1 -1
  37. package/extensions/__integration__/audit-findings.test.ts +6 -16
  38. package/extensions/__integration__/teams-runtime.test.ts +4 -1
  39. package/extensions/_icons/index.ts +2 -4
  40. package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
  41. package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
  42. package/extensions/_shared/image-metadata.ts +99 -0
  43. package/extensions/_shared/inline-preview.ts +1 -1
  44. package/extensions/_shared/pid-registry.ts +0 -1
  45. package/extensions/_shared/terminal-links.ts +22 -0
  46. package/extensions/ask-user-question-tool/index.ts +0 -3
  47. package/extensions/clear/__tests__/clear.test.ts +270 -3
  48. package/extensions/command-expansion/index.ts +1 -1
  49. package/extensions/context-files/index.ts +5 -1
  50. package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
  51. package/extensions/context-fork/extension.json +1 -1
  52. package/extensions/context-fork/index.ts +32 -0
  53. package/extensions/edit-tool-enhanced/index.ts +2 -1
  54. package/extensions/hooks/index.ts +33 -11
  55. package/extensions/loop/index.ts +14 -1
  56. package/extensions/lsp/index.ts +64 -13
  57. package/extensions/lsp/package.json +2 -2
  58. package/extensions/permissions/__tests__/permissions.test.ts +4 -4
  59. package/extensions/random-spinner/index.ts +7 -642
  60. package/extensions/read-tool-enhanced/index.ts +6 -8
  61. package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +4 -5
  62. package/extensions/render-stabilizer/index.ts +6 -6
  63. package/extensions/show-system-prompt/__tests__/show-system-prompt.test.ts +1 -1
  64. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
  65. package/extensions/slash-command-bridge/index.ts +14 -2
  66. package/extensions/subagent-tool/index.ts +1 -1
  67. package/extensions/subagent-tool/model-resolver.ts +274 -7
  68. package/extensions/tasks/__tests__/state-ui.test.ts +3 -3
  69. package/extensions/tasks/__tests__/widget-subagents.test.ts +2 -2
  70. package/extensions/tasks/commands/register-tasks-extension.ts +10 -10
  71. package/extensions/tasks/state/index.ts +1 -1
  72. package/extensions/tasks/ui/index.ts +2 -7
  73. package/extensions/teams-tool/tools/register-extension.ts +1 -3
  74. package/extensions/web-search-tool/index.ts +2 -1
  75. package/extensions/write-tool-enhanced/__tests__/write-tool-enhanced.test.ts +21 -6
  76. package/extensions/write-tool-enhanced/index.ts +2 -1
  77. package/node_modules/@mariozechner/pi-tui/README.md +56 -34
  78. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
  79. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
  80. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
  81. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
  82. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
  83. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
  84. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
  85. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
  86. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
  87. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
  88. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
  89. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
  90. package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
  91. package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
  92. package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
  93. package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
  94. package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
  95. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
  96. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
  97. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
  98. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
  99. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
  100. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
  101. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
  102. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
  103. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
  104. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
  105. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
  106. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +0 -2
  107. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
  108. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +12 -23
  109. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
  110. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
  111. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  112. package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
  113. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  114. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
  115. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  116. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
  117. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  118. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
  119. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  120. package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
  121. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  122. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
  123. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
  124. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
  125. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
  126. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
  127. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  128. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
  129. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  130. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
  131. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  132. package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
  133. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  134. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
  135. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
  136. package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
  137. package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
  138. package/node_modules/@mariozechner/pi-tui/package.json +6 -6
  139. package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
  140. package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
  141. package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
  142. package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
  143. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
  144. package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
  145. package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
  146. package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
  147. package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
  148. package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
  149. package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
  150. package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
  151. package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
  152. package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
  153. package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +11 -23
  154. package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
  155. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
  156. package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
  157. package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
  158. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
  159. package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
  160. package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
  161. package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
  162. package/package.json +13 -13
  163. package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
  164. package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
  165. package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
  166. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
  167. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
  168. package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
  169. package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
  170. package/packages/tallow-tui/node_modules/marked/README.md +5 -4
  171. package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
  172. package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
  173. package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
  174. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
  175. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
  176. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
  177. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
  178. package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
  179. package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
  180. package/packages/tallow-tui/node_modules/marked/package.json +26 -34
  181. package/runtime/model-metadata-overrides.ts +10 -1
  182. package/runtime/pid-schema.ts +26 -6
  183. package/skills/tallow-expert/SKILL.md +1 -3
  184. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
  185. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
  186. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
  187. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
  188. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
  189. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
  190. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
  191. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
  192. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
  193. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
  194. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
  195. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
  196. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
  197. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
  198. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
  199. package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -49
  200. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
  201. package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
  202. package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
  203. package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
  204. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
  205. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
  206. package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
  207. package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
@@ -1,51 +1,73 @@
1
1
  # @mariozechner/pi-tui (Tallow fork)
2
2
 
3
- Tallow's fork of `@mariozechner/pi-tui` (v0.52.9). Adds customizable
4
- loader, border styles, and input middleware changes that require
5
- modifying pi-tui internals.
3
+ Tallow vendors a local fork of `@mariozechner/pi-tui` to keep a small set
4
+ of UI primitives that upstream still does not expose in the form tallow
5
+ needs.
6
6
 
7
- ## What's different from upstream
7
+ This is **not** a tiny historical snapshot anymore. The authoritative
8
+ upstream reference for the current reduction effort is
9
+ `@mariozechner/pi-tui@0.67.68`.
8
10
 
9
- ### Configurable Loader
11
+ ## Why the fork existed
10
12
 
11
- `Loader` accepts optional `frames` and `intervalMs` via constructor
12
- options. Static `Loader.defaultFrames` / `Loader.defaultIntervalMs`
13
- let extensions set global defaults at session start.
13
+ The original fork was created because tallow needed a few TUI primitives
14
+ that extension APIs could not provide directly:
14
15
 
15
- ### Border styles
16
+ - configurable loader defaults
17
+ - border styles and bordered boxes
18
+ - earlier input interception hooks
16
19
 
17
- New `BorderStyle` interface with three presets: `SHARP` (┌┐└┘),
18
- `ROUNDED` (╭╮╰╯), `FLAT` (horizontal rules only).
19
- `BorderedBox` component wraps content in a full border with optional
20
- title, padding, and color functions.
20
+ Since then, upstream added several features that used to be local, so the
21
+ current fork is larger than the original rationale justifies.
21
22
 
22
- ### Input middleware
23
+ ## Long-term keep surface
23
24
 
24
- `TUI.addInputMiddleware(fn)` inserts a hook before `handleInput`
25
- forwards to the focused component. Middleware returns `true` to
26
- consume input. Used by the which-key overlay extension.
25
+ The fork reduction effort is shrinking the long-term delta to only the
26
+ smallest set of proven tallow requirements:
27
27
 
28
- ## Upstream sync
28
+ - border styles
29
+ - loader global defaults and hide sentinel
30
+ - editor ghost-text and change-listener APIs
31
+ - minimal reset/render primitives still required by tallow
29
32
 
30
- Source extracted from `@mariozechner/pi-tui@0.52.9` source maps.
33
+ Everything else is treated as revert-or-extract unless proven necessary.
31
34
 
32
- ### Modified files (conflict surface on sync)
35
+ ## Authoritative audit
33
36
 
34
- | File | Change |
35
- |------|--------|
36
- | `components/loader.ts` | Constructor options, static defaults |
37
- | `tui.ts` | Input middleware array + add/remove methods |
37
+ Do not trust this README alone for the current delta. The canonical
38
+ status document is `docs/research/pi-tui-fork-audit.md`, generated and
39
+ validated by the audit script:
38
40
 
39
- ### Added files (zero conflict)
41
+ ```bash
42
+ node scripts/audit-pi-tui-fork.mjs
43
+ ```
40
44
 
41
- | File | Purpose |
42
- |------|---------|
43
- | `border-styles.ts` | BorderStyle interface + presets |
44
- | `components/bordered-box.ts` | Box component with configurable borders |
45
+ To regenerate the human-readable audit note:
45
46
 
46
- ### How to sync
47
+ ```bash
48
+ node scripts/audit-pi-tui-fork.mjs \
49
+ --write-markdown docs/research/pi-tui-fork-audit.md
50
+ ```
47
51
 
48
- 1. Extract new upstream source from source maps (same script)
49
- 2. Diff against `src/` — conflicts only in modified files above
50
- 3. Apply upstream changes, keep our additions
51
- 4. Rebuild: `cd packages/tallow-tui && npm run build`
52
+ The generated report classifies each changed runtime file as one of:
53
+
54
+ - `keep`
55
+ - `extract`
56
+ - `upstream`
57
+ - `revert`
58
+
59
+ ## Current ownership rules
60
+
61
+ - `packages/tallow-tui` should own generic TUI primitives only
62
+ - tallow session/reset behavior belongs in `src/interactive-*.ts`, not in
63
+ the fork
64
+ - application helpers should move to tallow or upstream rather than stay
65
+ in the fork by accident
66
+
67
+ ## Sync approach
68
+
69
+ 1. audit local-vs-upstream runtime deltas
70
+ 2. revert low-risk files first
71
+ 3. extract tallow-owned helpers out of the fork
72
+ 4. keep only the justified primitive surface
73
+ 5. shrink high-risk render/input deltas last with PTY regression tests
@@ -3,16 +3,22 @@ export interface AutocompleteItem {
3
3
  label: string;
4
4
  description?: string;
5
5
  }
6
+ type Awaitable<T> = T | Promise<T>;
6
7
  export interface SlashCommand {
7
8
  name: string;
8
9
  description?: string;
9
- getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
10
+ argumentHint?: string;
11
+ getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
12
+ }
13
+ export interface AutocompleteSuggestions {
14
+ items: AutocompleteItem[];
15
+ prefix: string;
10
16
  }
11
17
  export interface AutocompleteProvider {
12
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
13
- items: AutocompleteItem[];
14
- prefix: string;
15
- } | null;
18
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, options: {
19
+ signal: AbortSignal;
20
+ force?: boolean;
21
+ }): Promise<AutocompleteSuggestions | null>;
16
22
  applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
17
23
  lines: string[];
18
24
  cursorLine: number;
@@ -24,10 +30,10 @@ export declare class CombinedAutocompleteProvider implements AutocompleteProvide
24
30
  private basePath;
25
31
  private fdPath;
26
32
  constructor(commands?: (SlashCommand | AutocompleteItem)[], basePath?: string, fdPath?: string | null);
27
- getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
28
- items: AutocompleteItem[];
29
- prefix: string;
30
- } | null;
33
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, options: {
34
+ signal: AbortSignal;
35
+ force?: boolean;
36
+ }): Promise<AutocompleteSuggestions | null>;
31
37
  applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
32
38
  lines: string[];
33
39
  cursorLine: number;
@@ -36,13 +42,12 @@ export declare class CombinedAutocompleteProvider implements AutocompleteProvide
36
42
  private extractAtPrefix;
37
43
  private extractPathPrefix;
38
44
  private expandHomePath;
45
+ private resolveScopedFuzzyQuery;
46
+ private scopedPathForDisplay;
39
47
  private getFileSuggestions;
40
48
  private scoreEntry;
41
49
  private getFuzzyFileSuggestions;
42
- getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
43
- items: AutocompleteItem[];
44
- prefix: string;
45
- } | null;
46
50
  shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
47
51
  }
52
+ export {};
48
53
  //# sourceMappingURL=autocomplete.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AA2JA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QACF,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAIT,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAgB;gBAG7B,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAClD,QAAQ,GAAE,MAAsB,EAChC,MAAM,GAAE,MAAM,GAAG,IAAW;IAO7B,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAqGvD,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAwF7D,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,kBAAkB;IAoI1B,OAAO,CAAC,UAAU;IAuBlB,OAAO,CAAC,uBAAuB;IAkD/B,uBAAuB,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAyBvD,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}
1
+ {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AA8NA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEnC,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/C,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;IAI3C,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAgB;gBAG7B,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAClD,QAAQ,GAAE,MAAsB,EAChC,MAAM,GAAE,MAAM,GAAG,IAAW;IAOvB,cAAc,CACnB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/C,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAsF1C,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAwF7D,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,kBAAkB;IAyI1B,OAAO,CAAC,UAAU;YAuBJ,uBAAuB;IA6DrC,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}
@@ -1,9 +1,39 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { spawn } from "node:child_process";
2
2
  import { readdirSync, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { basename, dirname, join } from "node:path";
5
5
  import { fuzzyFilter } from "./fuzzy.js";
6
6
  const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
7
+ function toDisplayPath(value) {
8
+ return value.replace(/\\/g, "/");
9
+ }
10
+ function escapeRegex(value) {
11
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12
+ }
13
+ function buildFdPathQuery(query) {
14
+ const normalized = toDisplayPath(query);
15
+ if (!normalized.includes("/")) {
16
+ return normalized;
17
+ }
18
+ const hasTrailingSeparator = normalized.endsWith("/");
19
+ const trimmed = normalized.replace(/^\/+|\/+$/g, "");
20
+ if (!trimmed) {
21
+ return normalized;
22
+ }
23
+ const separatorPattern = "[\\\\/]";
24
+ const segments = trimmed
25
+ .split("/")
26
+ .filter(Boolean)
27
+ .map((segment) => escapeRegex(segment));
28
+ if (segments.length === 0) {
29
+ return normalized;
30
+ }
31
+ let pattern = segments.join(separatorPattern);
32
+ if (hasTrailingSeparator) {
33
+ pattern += separatorPattern;
34
+ }
35
+ return pattern;
36
+ }
7
37
  function findLastDelimiter(text) {
8
38
  for (let i = text.length - 1; i >= 0; i -= 1) {
9
39
  if (PATH_DELIMITERS.has(text[i] ?? "")) {
@@ -67,7 +97,7 @@ function buildCompletionValue(path, options) {
67
97
  return `${openQuote}${path}${closeQuote}`;
68
98
  }
69
99
  // Use fd to walk directory tree (fast, respects .gitignore)
70
- function walkDirectoryWithFd(baseDir, fdPath, query, maxResults) {
100
+ async function walkDirectoryWithFd(baseDir, fdPath, query, maxResults, signal) {
71
101
  const args = [
72
102
  "--base-directory",
73
103
  baseDir,
@@ -86,35 +116,63 @@ function walkDirectoryWithFd(baseDir, fdPath, query, maxResults) {
86
116
  "--exclude",
87
117
  ".git/**",
88
118
  ];
89
- // Add query as pattern if provided
90
119
  if (query) {
91
- args.push(query);
120
+ args.push(buildFdPathQuery(query));
92
121
  }
93
- const result = spawnSync(fdPath, args, {
94
- encoding: "utf-8",
95
- stdio: ["pipe", "pipe", "pipe"],
96
- maxBuffer: 10 * 1024 * 1024,
97
- });
98
- if (result.status !== 0 || !result.stdout) {
99
- return [];
100
- }
101
- const lines = result.stdout.trim().split("\n").filter(Boolean);
102
- const results = [];
103
- for (const line of lines) {
104
- const normalizedPath = line.endsWith("/") ? line.slice(0, -1) : line;
105
- if (normalizedPath === ".git" ||
106
- normalizedPath.startsWith(".git/") ||
107
- normalizedPath.includes("/.git/")) {
108
- continue;
122
+ return await new Promise((resolve) => {
123
+ if (signal.aborted) {
124
+ resolve([]);
125
+ return;
109
126
  }
110
- // fd outputs directories with trailing /
111
- const isDirectory = line.endsWith("/");
112
- results.push({
113
- path: line,
114
- isDirectory,
127
+ const child = spawn(fdPath, args, {
128
+ stdio: ["ignore", "pipe", "pipe"],
115
129
  });
116
- }
117
- return results;
130
+ let stdout = "";
131
+ let resolved = false;
132
+ const finish = (results) => {
133
+ if (resolved)
134
+ return;
135
+ resolved = true;
136
+ signal.removeEventListener("abort", onAbort);
137
+ resolve(results);
138
+ };
139
+ const onAbort = () => {
140
+ if (child.exitCode === null) {
141
+ child.kill("SIGKILL");
142
+ }
143
+ };
144
+ signal.addEventListener("abort", onAbort, { once: true });
145
+ child.stdout.setEncoding("utf-8");
146
+ child.stdout.on("data", (chunk) => {
147
+ stdout += chunk;
148
+ });
149
+ child.on("error", () => {
150
+ finish([]);
151
+ });
152
+ child.on("close", (code) => {
153
+ if (signal.aborted || code !== 0 || !stdout) {
154
+ finish([]);
155
+ return;
156
+ }
157
+ const lines = stdout.trim().split("\n").filter(Boolean);
158
+ const results = [];
159
+ for (const line of lines) {
160
+ const displayLine = toDisplayPath(line);
161
+ const hasTrailingSeparator = displayLine.endsWith("/");
162
+ const normalizedPath = hasTrailingSeparator ? displayLine.slice(0, -1) : displayLine;
163
+ if (normalizedPath === ".git" ||
164
+ normalizedPath.startsWith(".git/") ||
165
+ normalizedPath.includes("/.git/")) {
166
+ continue;
167
+ }
168
+ results.push({
169
+ path: displayLine,
170
+ isDirectory: hasTrailingSeparator,
171
+ });
172
+ }
173
+ finish(results);
174
+ });
175
+ });
118
176
  }
119
177
  // Combined provider that handles both slash commands and file paths
120
178
  export class CombinedAutocompleteProvider {
@@ -126,15 +184,15 @@ export class CombinedAutocompleteProvider {
126
184
  this.basePath = basePath;
127
185
  this.fdPath = fdPath;
128
186
  }
129
- getSuggestions(lines, cursorLine, cursorCol) {
187
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
130
188
  const currentLine = lines[cursorLine] || "";
131
189
  const textBeforeCursor = currentLine.slice(0, cursorCol);
132
- // Check for @ file reference (fuzzy search) - must be after a delimiter or at start
133
190
  const atPrefix = this.extractAtPrefix(textBeforeCursor);
134
191
  if (atPrefix) {
135
192
  const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
136
- const suggestions = this.getFuzzyFileSuggestions(rawPrefix, {
137
- isQuotedPrefix: isQuotedPrefix,
193
+ const suggestions = await this.getFuzzyFileSuggestions(rawPrefix, {
194
+ isQuotedPrefix,
195
+ signal: options.signal,
138
196
  });
139
197
  if (suggestions.length === 0)
140
198
  return null;
@@ -143,17 +201,21 @@ export class CombinedAutocompleteProvider {
143
201
  prefix: atPrefix,
144
202
  };
145
203
  }
146
- // Check for slash commands
147
- if (textBeforeCursor.startsWith("/")) {
204
+ if (!options.force && textBeforeCursor.startsWith("/")) {
148
205
  const spaceIndex = textBeforeCursor.indexOf(" ");
149
206
  if (spaceIndex === -1) {
150
- // No space yet - complete command names with fuzzy matching
151
- const prefix = textBeforeCursor.slice(1); // Remove the "/"
152
- const commandItems = this.commands.map((cmd) => ({
153
- name: "name" in cmd ? cmd.name : cmd.value,
154
- label: "name" in cmd ? cmd.name : cmd.label,
155
- description: cmd.description,
156
- }));
207
+ const prefix = textBeforeCursor.slice(1);
208
+ const commandItems = this.commands.map((cmd) => {
209
+ const name = "name" in cmd ? cmd.name : cmd.value;
210
+ const hint = "argumentHint" in cmd && cmd.argumentHint ? cmd.argumentHint : undefined;
211
+ const desc = cmd.description ?? "";
212
+ const fullDesc = hint ? (desc ? `${hint} — ${desc}` : hint) : desc;
213
+ return {
214
+ name,
215
+ label: name,
216
+ description: fullDesc || undefined,
217
+ };
218
+ });
157
219
  const filtered = fuzzyFilter(commandItems, prefix, (item) => item.name).map((item) => ({
158
220
  value: item.name,
159
221
  label: item.label,
@@ -166,52 +228,35 @@ export class CombinedAutocompleteProvider {
166
228
  prefix: textBeforeCursor,
167
229
  };
168
230
  }
169
- else {
170
- // Space found - complete command arguments
171
- const commandName = textBeforeCursor.slice(1, spaceIndex); // Command without "/"
172
- const argumentText = textBeforeCursor.slice(spaceIndex + 1); // Text after space
173
- const command = this.commands.find((cmd) => {
174
- const name = "name" in cmd ? cmd.name : cmd.value;
175
- return name === commandName;
176
- });
177
- if (!command || !("getArgumentCompletions" in command) || !command.getArgumentCompletions) {
178
- return null; // No argument completion for this command
179
- }
180
- const argumentSuggestions = command.getArgumentCompletions(argumentText);
181
- if (!argumentSuggestions || argumentSuggestions.length === 0) {
182
- return null;
183
- }
184
- return {
185
- items: argumentSuggestions,
186
- prefix: argumentText,
187
- };
231
+ const commandName = textBeforeCursor.slice(1, spaceIndex);
232
+ const argumentText = textBeforeCursor.slice(spaceIndex + 1);
233
+ const command = this.commands.find((cmd) => {
234
+ const name = "name" in cmd ? cmd.name : cmd.value;
235
+ return name === commandName;
236
+ });
237
+ if (!command || !("getArgumentCompletions" in command) || !command.getArgumentCompletions) {
238
+ return null;
188
239
  }
189
- }
190
- // Check for file paths - triggered by Tab or if we detect a path pattern
191
- const pathMatch = this.extractPathPrefix(textBeforeCursor, false);
192
- if (pathMatch !== null) {
193
- const suggestions = this.getFileSuggestions(pathMatch);
194
- if (suggestions.length === 0)
240
+ const argumentSuggestions = await command.getArgumentCompletions(argumentText);
241
+ if (!Array.isArray(argumentSuggestions) || argumentSuggestions.length === 0) {
195
242
  return null;
196
- // Check if we have an exact match that is a directory
197
- // In that case, we might want to return suggestions for the directory content instead
198
- // But only if the prefix ends with /
199
- if (suggestions.length === 1 &&
200
- suggestions[0]?.value === pathMatch &&
201
- !pathMatch.endsWith("/")) {
202
- // Exact match found (e.g. user typed "src" and "src/" is the only match)
203
- // We still return it so user can select it and add /
204
- return {
205
- items: suggestions,
206
- prefix: pathMatch,
207
- };
208
243
  }
209
244
  return {
210
- items: suggestions,
211
- prefix: pathMatch,
245
+ items: argumentSuggestions,
246
+ prefix: argumentText,
212
247
  };
213
248
  }
214
- return null;
249
+ const pathMatch = this.extractPathPrefix(textBeforeCursor, options.force ?? false);
250
+ if (pathMatch === null) {
251
+ return null;
252
+ }
253
+ const suggestions = this.getFileSuggestions(pathMatch);
254
+ if (suggestions.length === 0)
255
+ return null;
256
+ return {
257
+ items: suggestions,
258
+ prefix: pathMatch,
259
+ };
215
260
  }
216
261
  applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
217
262
  const currentLine = lines[cursorLine] || "";
@@ -332,6 +377,41 @@ export class CombinedAutocompleteProvider {
332
377
  }
333
378
  return path;
334
379
  }
380
+ resolveScopedFuzzyQuery(rawQuery) {
381
+ const normalizedQuery = toDisplayPath(rawQuery);
382
+ const slashIndex = normalizedQuery.lastIndexOf("/");
383
+ if (slashIndex === -1) {
384
+ return null;
385
+ }
386
+ const displayBase = normalizedQuery.slice(0, slashIndex + 1);
387
+ const query = normalizedQuery.slice(slashIndex + 1);
388
+ let baseDir;
389
+ if (displayBase.startsWith("~/")) {
390
+ baseDir = this.expandHomePath(displayBase);
391
+ }
392
+ else if (displayBase.startsWith("/")) {
393
+ baseDir = displayBase;
394
+ }
395
+ else {
396
+ baseDir = join(this.basePath, displayBase);
397
+ }
398
+ try {
399
+ if (!statSync(baseDir).isDirectory()) {
400
+ return null;
401
+ }
402
+ }
403
+ catch {
404
+ return null;
405
+ }
406
+ return { baseDir, query, displayBase };
407
+ }
408
+ scopedPathForDisplay(displayBase, relativePath) {
409
+ const normalizedRelativePath = toDisplayPath(relativePath);
410
+ if (displayBase === "/") {
411
+ return `/${normalizedRelativePath}`;
412
+ }
413
+ return `${toDisplayPath(displayBase)}${normalizedRelativePath}`;
414
+ }
335
415
  // Get file/directory suggestions for a given path prefix
336
416
  getFileSuggestions(prefix) {
337
417
  try {
@@ -406,7 +486,7 @@ export class CombinedAutocompleteProvider {
406
486
  // If prefix ends with /, append entry to the prefix
407
487
  relativePath = displayPrefix + name;
408
488
  }
409
- else if (displayPrefix.includes("/")) {
489
+ else if (displayPrefix.includes("/") || displayPrefix.includes("\\")) {
410
490
  // Preserve ~/ format for home directory paths
411
491
  if (displayPrefix.startsWith("~/")) {
412
492
  const homeRelativeDir = displayPrefix.slice(2); // Remove ~/
@@ -425,6 +505,10 @@ export class CombinedAutocompleteProvider {
425
505
  }
426
506
  else {
427
507
  relativePath = join(dirname(displayPrefix), name);
508
+ // path.join normalizes away ./ prefix, preserve it
509
+ if (displayPrefix.startsWith("./") && !relativePath.startsWith("./")) {
510
+ relativePath = `./${relativePath}`;
511
+ }
428
512
  }
429
513
  }
430
514
  else {
@@ -436,6 +520,7 @@ export class CombinedAutocompleteProvider {
436
520
  relativePath = name;
437
521
  }
438
522
  }
523
+ relativePath = toDisplayPath(relativePath);
439
524
  const pathValue = isDirectory ? `${relativePath}/` : relativePath;
440
525
  const value = buildCompletionValue(pathValue, {
441
526
  isDirectory,
@@ -489,30 +574,35 @@ export class CombinedAutocompleteProvider {
489
574
  return score;
490
575
  }
491
576
  // Fuzzy file search using fd (fast, respects .gitignore)
492
- getFuzzyFileSuggestions(query, options) {
493
- if (!this.fdPath) {
494
- // fd not available, return empty results
577
+ async getFuzzyFileSuggestions(query, options) {
578
+ if (!this.fdPath || options.signal.aborted) {
495
579
  return [];
496
580
  }
497
581
  try {
498
- const entries = walkDirectoryWithFd(this.basePath, this.fdPath, query, 100);
499
- // Score entries
582
+ const scopedQuery = this.resolveScopedFuzzyQuery(query);
583
+ const fdBaseDir = scopedQuery?.baseDir ?? this.basePath;
584
+ const fdQuery = scopedQuery?.query ?? query;
585
+ const entries = await walkDirectoryWithFd(fdBaseDir, this.fdPath, fdQuery, 100, options.signal);
586
+ if (options.signal.aborted) {
587
+ return [];
588
+ }
500
589
  const scoredEntries = entries
501
590
  .map((entry) => ({
502
591
  ...entry,
503
- score: query ? this.scoreEntry(entry.path, query, entry.isDirectory) : 1,
592
+ score: fdQuery ? this.scoreEntry(entry.path, fdQuery, entry.isDirectory) : 1,
504
593
  }))
505
594
  .filter((entry) => entry.score > 0);
506
- // Sort by score (descending) and take top 20
507
595
  scoredEntries.sort((a, b) => b.score - a.score);
508
596
  const topEntries = scoredEntries.slice(0, 20);
509
- // Build suggestions
510
597
  const suggestions = [];
511
598
  for (const { path: entryPath, isDirectory } of topEntries) {
512
- // fd already includes trailing / for directories
513
599
  const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
600
+ const displayPath = scopedQuery
601
+ ? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash)
602
+ : pathWithoutSlash;
514
603
  const entryName = basename(pathWithoutSlash);
515
- const value = buildCompletionValue(entryPath, {
604
+ const completionPath = isDirectory ? `${displayPath}/` : displayPath;
605
+ const value = buildCompletionValue(completionPath, {
516
606
  isDirectory,
517
607
  isAtPrefix: true,
518
608
  isQuotedPrefix: options.isQuotedPrefix,
@@ -520,7 +610,7 @@ export class CombinedAutocompleteProvider {
520
610
  suggestions.push({
521
611
  value,
522
612
  label: entryName + (isDirectory ? "/" : ""),
523
- description: pathWithoutSlash,
613
+ description: displayPath,
524
614
  });
525
615
  }
526
616
  return suggestions;
@@ -529,27 +619,6 @@ export class CombinedAutocompleteProvider {
529
619
  return [];
530
620
  }
531
621
  }
532
- // Force file completion (called on Tab key) - always returns suggestions
533
- getForceFileSuggestions(lines, cursorLine, cursorCol) {
534
- const currentLine = lines[cursorLine] || "";
535
- const textBeforeCursor = currentLine.slice(0, cursorCol);
536
- // Don't trigger if we're typing a slash command at the start of the line
537
- if (textBeforeCursor.trim().startsWith("/") && !textBeforeCursor.trim().includes(" ")) {
538
- return null;
539
- }
540
- // Force extract path prefix - this will always return something
541
- const pathMatch = this.extractPathPrefix(textBeforeCursor, true);
542
- if (pathMatch !== null) {
543
- const suggestions = this.getFileSuggestions(pathMatch);
544
- if (suggestions.length === 0)
545
- return null;
546
- return {
547
- items: suggestions,
548
- prefix: pathMatch,
549
- };
550
- }
551
- return null;
552
- }
553
622
  // Check if we should trigger file completion (called on Tab key)
554
623
  shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
555
624
  const currentLine = lines[cursorLine] || "";