@averyyy/pi-tui 0.87.1-piclient.2 → 0.99.1-piclient.1
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.
- package/README.md +41 -3
- package/dist/alt-screen-search.d.ts.map +1 -1
- package/dist/alt-screen-search.js.map +1 -1
- package/dist/autocomplete.d.ts +1 -1
- package/dist/autocomplete.d.ts.map +1 -1
- package/dist/autocomplete.js +28 -8
- package/dist/autocomplete.js.map +1 -1
- package/dist/colors.d.ts +73 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +273 -0
- package/dist/colors.js.map +1 -0
- package/dist/components/alt-screen-flash.d.ts.map +1 -1
- package/dist/components/alt-screen-flash.js.map +1 -1
- package/dist/components/box.d.ts.map +1 -1
- package/dist/components/box.js +8 -8
- package/dist/components/box.js.map +1 -1
- package/dist/components/cancellable-loader.d.ts.map +1 -1
- package/dist/components/cancellable-loader.js.map +1 -1
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +4 -2
- package/dist/components/editor.js.map +1 -1
- package/dist/components/h-stack.d.ts.map +1 -1
- package/dist/components/h-stack.js.map +1 -1
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js.map +1 -1
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js.map +1 -1
- package/dist/components/markdown.d.ts +1 -0
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +8 -2
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/mouse-region.d.ts.map +1 -1
- package/dist/components/mouse-region.js.map +1 -1
- package/dist/components/scroll-view.d.ts.map +1 -1
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js.map +1 -1
- package/dist/components/spacer.d.ts.map +1 -1
- package/dist/components/spacer.js.map +1 -1
- package/dist/components/stack.d.ts.map +1 -1
- package/dist/components/stack.js.map +1 -1
- package/dist/components/text.d.ts.map +1 -1
- package/dist/components/text.js.map +1 -1
- package/dist/components/truncated-text.d.ts.map +1 -1
- package/dist/components/truncated-text.js.map +1 -1
- package/dist/components/v-stack.d.ts.map +1 -1
- package/dist/components/v-stack.js.map +1 -1
- package/dist/editor-component.d.ts.map +1 -1
- package/dist/fuzzy.d.ts.map +1 -1
- package/dist/fuzzy.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js.map +1 -1
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/kill-ring.d.ts.map +1 -1
- package/dist/kill-ring.js.map +1 -1
- package/dist/latex.d.ts.map +1 -1
- package/dist/latex.js.map +1 -1
- package/dist/layout-node.d.ts.map +1 -1
- package/dist/layout-node.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js.map +1 -1
- package/dist/native-modifiers.d.ts.map +1 -1
- package/dist/native-modifiers.js.map +1 -1
- package/dist/native-module-path.d.ts.map +1 -1
- package/dist/native-module-path.js.map +1 -1
- package/dist/native-platform.d.ts +2 -0
- package/dist/native-platform.d.ts.map +1 -1
- package/dist/native-platform.js.map +1 -1
- package/dist/oklab.d.ts +43 -0
- package/dist/oklab.d.ts.map +1 -0
- package/dist/oklab.js +210 -0
- package/dist/oklab.js.map +1 -0
- package/dist/stdin-buffer.d.ts.map +1 -1
- package/dist/stdin-buffer.js.map +1 -1
- package/dist/terminal-colors.d.ts +19 -2
- package/dist/terminal-colors.d.ts.map +1 -1
- package/dist/terminal-colors.js +12 -7
- package/dist/terminal-colors.js.map +1 -1
- package/dist/terminal-image.d.ts +3 -1
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js +33 -9
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +3 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +15 -8
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +8 -4
- package/dist/tui-alt-screen.d.ts.map +1 -1
- package/dist/tui-alt-screen.js +13 -12
- package/dist/tui-alt-screen.js.map +1 -1
- package/dist/tui-main-screen.d.ts.map +1 -1
- package/dist/tui-main-screen.js.map +1 -1
- package/dist/tui.d.ts +23 -22
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +85 -66
- package/dist/tui.js.map +1 -1
- package/dist/undo-stack.d.ts.map +1 -1
- package/dist/undo-stack.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +99 -55
- package/dist/utils.js.map +1 -1
- package/dist/wheel-scroll.d.ts +23 -0
- package/dist/wheel-scroll.d.ts.map +1 -0
- package/dist/wheel-scroll.js +73 -0
- package/dist/wheel-scroll.js.map +1 -0
- package/dist/word-navigation.d.ts.map +1 -1
- package/dist/word-navigation.js.map +1 -1
- package/native/clipboard.h +40 -2
- package/native/darwin/README.md +1 -1
- package/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/native/darwin/src/darwin-platform.m +39 -0
- package/native/napi.h +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,6 +78,44 @@ tui.requestRender(); // Request a re-render
|
|
|
78
78
|
tui.onDebug = () => console.log("Debug triggered");
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
### Colors and terminal styles
|
|
82
|
+
|
|
83
|
+
Colors are values that can be converted or mixed before terminal rendering:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import {
|
|
87
|
+
colorToRgb,
|
|
88
|
+
foregroundAnsi,
|
|
89
|
+
getTerminalColorMode,
|
|
90
|
+
mixColors,
|
|
91
|
+
parseColor,
|
|
92
|
+
rgbColor,
|
|
93
|
+
styleText,
|
|
94
|
+
} from "@earendil-works/pi-tui";
|
|
95
|
+
|
|
96
|
+
const accent = parseColor("oklch(70% 0.12 220)");
|
|
97
|
+
const background = parseColor("#20242a");
|
|
98
|
+
const foreground = mixColors(accent, background, 0.2);
|
|
99
|
+
|
|
100
|
+
const text = styleText(
|
|
101
|
+
"Ready",
|
|
102
|
+
{ fg: foreground, bg: background, bold: true },
|
|
103
|
+
getTerminalColorMode(),
|
|
104
|
+
);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`Color` is an indexed ANSI color, an sRGB color, or an OKLCH color. Every color converts to sRGB, so color math such as `mixColors()` always works. Indices 0-15 follow the user's terminal palette, so their sRGB values are approximations. `styleText()` converts colors to truecolor or 256-color output based on the requested terminal mode.
|
|
108
|
+
|
|
109
|
+
`parseColor()` also accepts OKHSL, as in `okhsl(250 60% 55%)`; `okhslColor()` builds it in code and `colorToOkhsl()` reads any color's OKHSL channels. OKHSL saturation is relative to the most the sRGB gamut allows at the hue and lightness, so every value is in gamut and equal saturation looks equally colorful across hues. OKHSL colors are converted to sRGB when created.
|
|
110
|
+
|
|
111
|
+
Conversions are not cached. OKLCH colors, especially ones outside the sRGB gamut, are more expensive to convert than sRGB or indexed colors. For colors used on every render, convert once and reuse the result:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const { r, g, b } = colorToRgb(mixColors(accent, background, 0.2));
|
|
115
|
+
const foreground = rgbColor(r, g, b); // cheap to render repeatedly
|
|
116
|
+
const foregroundCode = foregroundAnsi(foreground, getTerminalColorMode());
|
|
117
|
+
```
|
|
118
|
+
|
|
81
119
|
### Alternate-screen viewport layouts
|
|
82
120
|
|
|
83
121
|
`TuiAltScreen` can render an explicit terminal-height layout. `VStack` and `HStack` allocate constrained regions, while `ScrollView` owns scrolling for one region. These semantics are intentionally unavailable on `TuiMainScreen`, where the terminal owns scrollback.
|
|
@@ -884,7 +922,7 @@ See `test/chat-simple.ts` for a complete chat interface example with:
|
|
|
884
922
|
|
|
885
923
|
Run it:
|
|
886
924
|
```bash
|
|
887
|
-
|
|
925
|
+
node test/chat-simple.ts
|
|
888
926
|
```
|
|
889
927
|
|
|
890
928
|
## Development
|
|
@@ -897,7 +935,7 @@ npm install
|
|
|
897
935
|
npm run check
|
|
898
936
|
|
|
899
937
|
# Run the demo
|
|
900
|
-
|
|
938
|
+
node test/chat-simple.ts
|
|
901
939
|
```
|
|
902
940
|
|
|
903
941
|
### Debug logging
|
|
@@ -905,5 +943,5 @@ npx tsx test/chat-simple.ts
|
|
|
905
943
|
Set `PI_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
|
|
906
944
|
|
|
907
945
|
```bash
|
|
908
|
-
PI_TUI_WRITE_LOG=/tmp/tui-ansi.log
|
|
946
|
+
PI_TUI_WRITE_LOG=/tmp/tui-ansi.log node test/chat-simple.ts
|
|
909
947
|
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alt-screen-search.d.ts","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAmBrD,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAwHD,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,gGAAgG;AAChG,qBAAa,oBAAoB;IAChC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAE7C,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAqBrE;CACD;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAG1G;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAI9E;AAED,qBAAa,wBAAyB,YAAW,SAAS,EAAE,SAAS;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAInB;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA6C;IACnF,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,mBAAmB,CAAM;IACjC,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,0BAA0B,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAS;IAEzB,YACC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACtC,qBAAqB,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,MAAuB,EAIlF;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAKxE;IAED,6BAA6B,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAIpE;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkE9B;CACD","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport { getKeybindings } from \"./keybindings.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\ttextStart: number;\n\ttextEnd: number;\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n\tlinearColumns: boolean;\n}\n\ninterface SearchCorpus {\n\ttext: string;\n\tspans: SearchSourceSpan[];\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nconst PRINTABLE_ASCII = /^[\\x20-\\x7e]*$/;\n\nfunction buildSearchCorpus(lines: readonly string[]): SearchCorpus {\n\tconst chunks: string[] = [];\n\tconst spans: SearchSourceSpan[] = [];\n\tlet textLength = 0;\n\tlet pendingSeparator = false;\n\n\tconst appendSeparator = (): void => {\n\t\tif (!pendingSeparator) return;\n\t\tchunks.push(\" \");\n\t\ttextLength += 1;\n\t\tpendingSeparator = false;\n\t};\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\n\t\t// Rendered transcripts are overwhelmingly ASCII. Index complete non-space\n\t\t// runs at once instead of segmenting and allocating one mapping per cell.\n\t\tif (PRINTABLE_ASCII.test(line)) {\n\t\t\tlet index = 0;\n\t\t\twhile (index < line.length) {\n\t\t\t\tif (line.charCodeAt(index) === 0x20) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += 1;\n\t\t\t\t\tindex += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet end = index + 1;\n\t\t\t\twhile (end < line.length && line.charCodeAt(end) !== 0x20) end += 1;\n\t\t\t\tappendSeparator();\n\t\t\t\tconst text = line.slice(index, end);\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + text.length,\n\t\t\t\t\tlinearColumns: true,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += text.length;\n\t\t\t\tindex = end;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\t\tconst text = grapheme.segment;\n\t\t\t\tconst width = visibleWidth(text);\n\t\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += width;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappendSeparator();\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + width,\n\t\t\t\t\tlinearColumns: false,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += width;\n\t\t\t}\n\t\t}\n\t\tif (textLength > 0) pendingSeparator = true;\n\t}\n\n\treturn { text: chunks.join(\"\"), spans };\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction findSearchCorpusMatches(corpus: SearchCorpus, normalizedQuery: string): AltScreenSearchMatch[] {\n\tif (!normalizedQuery) return [];\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\tlet spanIndex = 0;\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= start) spanIndex += 1;\n\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = spanIndex; index < corpus.spans.length; index++) {\n\t\t\tconst span = corpus.spans[index]!;\n\t\t\tif (span.textStart >= end) break;\n\t\t\tif (span.textEnd <= start) continue;\n\t\t\tconst startCol = span.linearColumns\n\t\t\t\t? span.startCol + Math.max(start, span.textStart) - span.textStart\n\t\t\t\t: span.startCol;\n\t\t\tconst endCol = span.linearColumns ? span.startCol + Math.min(end, span.textEnd) - span.textStart : span.endCol;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ row: span.row, startCol, endCol });\n\t\t\t}\n\t\t}\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= end) spanIndex += 1;\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport interface AltScreenSearchResult {\n\tmatches: AltScreenSearchMatch[];\n\tchanged: boolean;\n}\n\n/** Cache the searchable corpus and matches while rendered transcript lines remain unchanged. */\nexport class AltScreenSearchIndex {\n\tprivate sourceLines: string[] | undefined;\n\tprivate corpus: SearchCorpus | undefined;\n\tprivate normalizedQuery: string | undefined;\n\tprivate matches: AltScreenSearchMatch[] = [];\n\n\tsearch(lines: readonly string[], query: string): AltScreenSearchResult {\n\t\tlet sourceChanged = this.sourceLines?.length !== lines.length;\n\t\tif (!sourceChanged && this.sourceLines) {\n\t\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\t\tif (this.sourceLines[index] === lines[index]) continue;\n\t\t\t\tsourceChanged = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (sourceChanged || !this.corpus) {\n\t\t\tthis.sourceLines = Array.from(lines);\n\t\t\tthis.corpus = buildSearchCorpus(lines);\n\t\t}\n\n\t\tconst normalizedQuery = normalizeQuery(query);\n\t\tconst changed = sourceChanged || normalizedQuery !== this.normalizedQuery;\n\t\tif (changed) {\n\t\t\tthis.normalizedQuery = normalizedQuery;\n\t\t\tthis.matches = findSearchCorpusMatches(this.corpus, normalizedQuery);\n\t\t}\n\t\treturn { matches: this.matches, changed };\n\t}\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\treturn normalizedQuery ? findSearchCorpusMatches(buildSearchCorpus(lines), normalizedQuery) : [];\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input({\n\t\tprompt: \" \",\n\t\tplaceholder: \"Find in transcript\",\n\t\tplaceholderStyle: (text) => `\\x1b[2m${text}\\x1b[22m`,\n\t});\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate readonly navigationButtonStyle: (text: string, hovered: boolean) => string;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate previousButtonStart = -1;\n\tprivate previousButtonEnd = -1;\n\tprivate nextButtonStart = -1;\n\tprivate nextButtonEnd = -1;\n\tprivate hoveredNavigationDirection: -1 | 1 | undefined;\n\tprivate _focused = false;\n\n\tconstructor(\n\t\tonQueryChange: (query: string) => void,\n\t\tnavigationButtonStyle: (text: string, hovered: boolean) => string = (text) => text,\n\t) {\n\t\tthis.onQueryChange = onQueryChange;\n\t\tthis.navigationButtonStyle = navigationButtonStyle;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\tgetNavigationDirectionAt(row: number, column: number): -1 | 1 | undefined {\n\t\tif (row !== 2) return undefined;\n\t\tif (column >= this.previousButtonStart && column < this.previousButtonEnd) return -1;\n\t\tif (column >= this.nextButtonStart && column < this.nextButtonEnd) return 1;\n\t\treturn undefined;\n\t}\n\n\tsetHoveredNavigationDirection(direction: -1 | 1 | undefined): boolean {\n\t\tif (direction === this.hoveredNavigationDirection) return false;\n\t\tthis.hoveredNavigationDirection = direction;\n\t\treturn true;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst innerWidth = Math.max(0, safeWidth - 2);\n\t\tconst formatKey = (key: string | undefined): string =>\n\t\t\tkey\n\t\t\t\t? key\n\t\t\t\t\t\t.split(\"+\")\n\t\t\t\t\t\t.map((part) => {\n\t\t\t\t\t\t\tif (process.platform === \"darwin\" && part.toLowerCase() === \"alt\") return \"Option\";\n\t\t\t\t\t\t\treturn part.charAt(0).toUpperCase() + part.slice(1);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.join(\"+\")\n\t\t\t\t: \"Unbound\";\n\t\tconst keybindings = getKeybindings();\n\t\tconst previousKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchPrevious\")[0]);\n\t\tconst nextKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchNext\")[0]);\n\t\tconst query = this.input.getValue();\n\t\tconst result = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches\"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount}`;\n\t\tconst resultSpace = Math.max(0, innerWidth - 3);\n\t\tconst visibleResult = truncateToWidth(result, resultSpace, \"\");\n\t\tconst resultText = visibleResult ? `\\x1b[2m ${visibleResult} \\x1b[22m` : \"\";\n\t\tconst inputWidth = Math.max(0, innerWidth - visibleWidth(resultText));\n\t\tconst inputLine = truncateToWidth(this.input.render(Math.max(1, inputWidth))[0] ?? \"\", inputWidth, \"\");\n\t\tconst inputPadding = \" \".repeat(Math.max(0, inputWidth - visibleWidth(inputLine)));\n\t\tconst content = `${inputLine}${inputPadding}${resultText}`;\n\n\t\tlet previousButton = `↑ ${previousKey}`;\n\t\tlet nextButton = `↓ ${nextKey}`;\n\t\tlet separator = \" · \";\n\t\tconst outerGapWidth = 1;\n\t\tconst availableControlsWidth = Math.max(0, innerWidth - outerGapWidth * 2 - 1);\n\t\tlet controlsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\tif (controlsWidth > availableControlsWidth) {\n\t\t\tpreviousButton = \"↑\";\n\t\t\tnextButton = \"↓\";\n\t\t\tseparator = \" \";\n\t\t\tcontrolsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\t}\n\t\tconst showButtons = controlsWidth <= availableControlsWidth;\n\t\tconst renderedButtons = showButtons\n\t\t\t? this.navigationButtonStyle(previousButton, this.hoveredNavigationDirection === -1) +\n\t\t\t\tseparator +\n\t\t\t\tthis.navigationButtonStyle(nextButton, this.hoveredNavigationDirection === 1)\n\t\t\t: \"\";\n\t\tconst outerGapsWidth = showButtons ? outerGapWidth * 2 : 0;\n\t\tconst rightRuleWidth = renderedButtons && innerWidth > controlsWidth + outerGapsWidth ? 1 : 0;\n\t\tconst leftRuleWidth = Math.max(\n\t\t\t0,\n\t\t\tinnerWidth - (showButtons ? controlsWidth : 0) - outerGapsWidth - rightRuleWidth,\n\t\t);\n\t\tconst previousStart = 1 + leftRuleWidth + outerGapWidth;\n\t\tthis.previousButtonStart = showButtons ? previousStart : -1;\n\t\tthis.previousButtonEnd = showButtons ? previousStart + visibleWidth(previousButton) : -1;\n\t\tthis.nextButtonStart = showButtons ? this.previousButtonEnd + visibleWidth(separator) : -1;\n\t\tthis.nextButtonEnd = showButtons ? this.nextButtonStart + visibleWidth(nextButton) : -1;\n\n\t\tif (safeWidth === 1) return [\"┌\", \"│\", \"└\"];\n\t\treturn [\n\t\t\t`┌${\"─\".repeat(innerWidth)}┐`,\n\t\t\t`│${content}│`,\n\t\t\t`└${\"─\".repeat(leftRuleWidth)}${renderedButtons ? \" \" : \"\"}${renderedButtons}${renderedButtons ? \" \" : \"\"}${\"─\".repeat(rightRuleWidth)}┘`,\n\t\t];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"alt-screen-search.d.ts","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAmBrD,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAwHD,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,gGAAgG;AAChG,qBAAa,oBAAoB;IAChC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAE7C,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAqBrE;CACD;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAG1G;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAI9E;AAED,qBAAa,wBAAyB,YAAW,SAAS,EAAE,SAAS;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAInB;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA6C;IACnF,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,mBAAmB,CAAM;IACjC,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,0BAA0B,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAS;IAEzB,YACC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACtC,qBAAqB,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,MAAuB,EAIlF;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAKxE;IAED,6BAA6B,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAIpE;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkE9B;CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alt-screen-search.js","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"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","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport { getKeybindings } from \"./keybindings.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\ttextStart: number;\n\ttextEnd: number;\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n\tlinearColumns: boolean;\n}\n\ninterface SearchCorpus {\n\ttext: string;\n\tspans: SearchSourceSpan[];\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nconst PRINTABLE_ASCII = /^[\\x20-\\x7e]*$/;\n\nfunction buildSearchCorpus(lines: readonly string[]): SearchCorpus {\n\tconst chunks: string[] = [];\n\tconst spans: SearchSourceSpan[] = [];\n\tlet textLength = 0;\n\tlet pendingSeparator = false;\n\n\tconst appendSeparator = (): void => {\n\t\tif (!pendingSeparator) return;\n\t\tchunks.push(\" \");\n\t\ttextLength += 1;\n\t\tpendingSeparator = false;\n\t};\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\n\t\t// Rendered transcripts are overwhelmingly ASCII. Index complete non-space\n\t\t// runs at once instead of segmenting and allocating one mapping per cell.\n\t\tif (PRINTABLE_ASCII.test(line)) {\n\t\t\tlet index = 0;\n\t\t\twhile (index < line.length) {\n\t\t\t\tif (line.charCodeAt(index) === 0x20) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += 1;\n\t\t\t\t\tindex += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet end = index + 1;\n\t\t\t\twhile (end < line.length && line.charCodeAt(end) !== 0x20) end += 1;\n\t\t\t\tappendSeparator();\n\t\t\t\tconst text = line.slice(index, end);\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + text.length,\n\t\t\t\t\tlinearColumns: true,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += text.length;\n\t\t\t\tindex = end;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\t\tconst text = grapheme.segment;\n\t\t\t\tconst width = visibleWidth(text);\n\t\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += width;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappendSeparator();\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + width,\n\t\t\t\t\tlinearColumns: false,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += width;\n\t\t\t}\n\t\t}\n\t\tif (textLength > 0) pendingSeparator = true;\n\t}\n\n\treturn { text: chunks.join(\"\"), spans };\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction findSearchCorpusMatches(corpus: SearchCorpus, normalizedQuery: string): AltScreenSearchMatch[] {\n\tif (!normalizedQuery) return [];\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\tlet spanIndex = 0;\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= start) spanIndex += 1;\n\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = spanIndex; index < corpus.spans.length; index++) {\n\t\t\tconst span = corpus.spans[index]!;\n\t\t\tif (span.textStart >= end) break;\n\t\t\tif (span.textEnd <= start) continue;\n\t\t\tconst startCol = span.linearColumns\n\t\t\t\t? span.startCol + Math.max(start, span.textStart) - span.textStart\n\t\t\t\t: span.startCol;\n\t\t\tconst endCol = span.linearColumns ? span.startCol + Math.min(end, span.textEnd) - span.textStart : span.endCol;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ row: span.row, startCol, endCol });\n\t\t\t}\n\t\t}\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= end) spanIndex += 1;\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport interface AltScreenSearchResult {\n\tmatches: AltScreenSearchMatch[];\n\tchanged: boolean;\n}\n\n/** Cache the searchable corpus and matches while rendered transcript lines remain unchanged. */\nexport class AltScreenSearchIndex {\n\tprivate sourceLines: string[] | undefined;\n\tprivate corpus: SearchCorpus | undefined;\n\tprivate normalizedQuery: string | undefined;\n\tprivate matches: AltScreenSearchMatch[] = [];\n\n\tsearch(lines: readonly string[], query: string): AltScreenSearchResult {\n\t\tlet sourceChanged = this.sourceLines?.length !== lines.length;\n\t\tif (!sourceChanged && this.sourceLines) {\n\t\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\t\tif (this.sourceLines[index] === lines[index]) continue;\n\t\t\t\tsourceChanged = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (sourceChanged || !this.corpus) {\n\t\t\tthis.sourceLines = Array.from(lines);\n\t\t\tthis.corpus = buildSearchCorpus(lines);\n\t\t}\n\n\t\tconst normalizedQuery = normalizeQuery(query);\n\t\tconst changed = sourceChanged || normalizedQuery !== this.normalizedQuery;\n\t\tif (changed) {\n\t\t\tthis.normalizedQuery = normalizedQuery;\n\t\t\tthis.matches = findSearchCorpusMatches(this.corpus, normalizedQuery);\n\t\t}\n\t\treturn { matches: this.matches, changed };\n\t}\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\treturn normalizedQuery ? findSearchCorpusMatches(buildSearchCorpus(lines), normalizedQuery) : [];\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input({\n\t\tprompt: \" \",\n\t\tplaceholder: \"Find in transcript\",\n\t\tplaceholderStyle: (text) => `\\x1b[2m${text}\\x1b[22m`,\n\t});\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate readonly navigationButtonStyle: (text: string, hovered: boolean) => string;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate previousButtonStart = -1;\n\tprivate previousButtonEnd = -1;\n\tprivate nextButtonStart = -1;\n\tprivate nextButtonEnd = -1;\n\tprivate hoveredNavigationDirection: -1 | 1 | undefined;\n\tprivate _focused = false;\n\n\tconstructor(\n\t\tonQueryChange: (query: string) => void,\n\t\tnavigationButtonStyle: (text: string, hovered: boolean) => string = (text) => text,\n\t) {\n\t\tthis.onQueryChange = onQueryChange;\n\t\tthis.navigationButtonStyle = navigationButtonStyle;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\tgetNavigationDirectionAt(row: number, column: number): -1 | 1 | undefined {\n\t\tif (row !== 2) return undefined;\n\t\tif (column >= this.previousButtonStart && column < this.previousButtonEnd) return -1;\n\t\tif (column >= this.nextButtonStart && column < this.nextButtonEnd) return 1;\n\t\treturn undefined;\n\t}\n\n\tsetHoveredNavigationDirection(direction: -1 | 1 | undefined): boolean {\n\t\tif (direction === this.hoveredNavigationDirection) return false;\n\t\tthis.hoveredNavigationDirection = direction;\n\t\treturn true;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst innerWidth = Math.max(0, safeWidth - 2);\n\t\tconst formatKey = (key: string | undefined): string =>\n\t\t\tkey\n\t\t\t\t? key\n\t\t\t\t\t\t.split(\"+\")\n\t\t\t\t\t\t.map((part) => {\n\t\t\t\t\t\t\tif (process.platform === \"darwin\" && part.toLowerCase() === \"alt\") return \"Option\";\n\t\t\t\t\t\t\treturn part.charAt(0).toUpperCase() + part.slice(1);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.join(\"+\")\n\t\t\t\t: \"Unbound\";\n\t\tconst keybindings = getKeybindings();\n\t\tconst previousKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchPrevious\")[0]);\n\t\tconst nextKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchNext\")[0]);\n\t\tconst query = this.input.getValue();\n\t\tconst result = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches\"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount}`;\n\t\tconst resultSpace = Math.max(0, innerWidth - 3);\n\t\tconst visibleResult = truncateToWidth(result, resultSpace, \"\");\n\t\tconst resultText = visibleResult ? `\\x1b[2m ${visibleResult} \\x1b[22m` : \"\";\n\t\tconst inputWidth = Math.max(0, innerWidth - visibleWidth(resultText));\n\t\tconst inputLine = truncateToWidth(this.input.render(Math.max(1, inputWidth))[0] ?? \"\", inputWidth, \"\");\n\t\tconst inputPadding = \" \".repeat(Math.max(0, inputWidth - visibleWidth(inputLine)));\n\t\tconst content = `${inputLine}${inputPadding}${resultText}`;\n\n\t\tlet previousButton = `↑ ${previousKey}`;\n\t\tlet nextButton = `↓ ${nextKey}`;\n\t\tlet separator = \" · \";\n\t\tconst outerGapWidth = 1;\n\t\tconst availableControlsWidth = Math.max(0, innerWidth - outerGapWidth * 2 - 1);\n\t\tlet controlsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\tif (controlsWidth > availableControlsWidth) {\n\t\t\tpreviousButton = \"↑\";\n\t\t\tnextButton = \"↓\";\n\t\t\tseparator = \" \";\n\t\t\tcontrolsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\t}\n\t\tconst showButtons = controlsWidth <= availableControlsWidth;\n\t\tconst renderedButtons = showButtons\n\t\t\t? this.navigationButtonStyle(previousButton, this.hoveredNavigationDirection === -1) +\n\t\t\t\tseparator +\n\t\t\t\tthis.navigationButtonStyle(nextButton, this.hoveredNavigationDirection === 1)\n\t\t\t: \"\";\n\t\tconst outerGapsWidth = showButtons ? outerGapWidth * 2 : 0;\n\t\tconst rightRuleWidth = renderedButtons && innerWidth > controlsWidth + outerGapsWidth ? 1 : 0;\n\t\tconst leftRuleWidth = Math.max(\n\t\t\t0,\n\t\t\tinnerWidth - (showButtons ? controlsWidth : 0) - outerGapsWidth - rightRuleWidth,\n\t\t);\n\t\tconst previousStart = 1 + leftRuleWidth + outerGapWidth;\n\t\tthis.previousButtonStart = showButtons ? previousStart : -1;\n\t\tthis.previousButtonEnd = showButtons ? previousStart + visibleWidth(previousButton) : -1;\n\t\tthis.nextButtonStart = showButtons ? this.previousButtonEnd + visibleWidth(separator) : -1;\n\t\tthis.nextButtonEnd = showButtons ? this.nextButtonStart + visibleWidth(nextButton) : -1;\n\n\t\tif (safeWidth === 1) return [\"┌\", \"│\", \"└\"];\n\t\treturn [\n\t\t\t`┌${\"─\".repeat(innerWidth)}┐`,\n\t\t\t`│${content}│`,\n\t\t\t`└${\"─\".repeat(leftRuleWidth)}${renderedButtons ? \" \" : \"\"}${renderedButtons}${renderedButtons ? \" \" : \"\"}${\"─\".repeat(rightRuleWidth)}┘`,\n\t\t];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"alt-screen-search.js","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"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","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport { getKeybindings } from \"./keybindings.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\ttextStart: number;\n\ttextEnd: number;\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n\tlinearColumns: boolean;\n}\n\ninterface SearchCorpus {\n\ttext: string;\n\tspans: SearchSourceSpan[];\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nconst PRINTABLE_ASCII = /^[\\x20-\\x7e]*$/;\n\nfunction buildSearchCorpus(lines: readonly string[]): SearchCorpus {\n\tconst chunks: string[] = [];\n\tconst spans: SearchSourceSpan[] = [];\n\tlet textLength = 0;\n\tlet pendingSeparator = false;\n\n\tconst appendSeparator = (): void => {\n\t\tif (!pendingSeparator) return;\n\t\tchunks.push(\" \");\n\t\ttextLength += 1;\n\t\tpendingSeparator = false;\n\t};\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\n\t\t// Rendered transcripts are overwhelmingly ASCII. Index complete non-space\n\t\t// runs at once instead of segmenting and allocating one mapping per cell.\n\t\tif (PRINTABLE_ASCII.test(line)) {\n\t\t\tlet index = 0;\n\t\t\twhile (index < line.length) {\n\t\t\t\tif (line.charCodeAt(index) === 0x20) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += 1;\n\t\t\t\t\tindex += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet end = index + 1;\n\t\t\t\twhile (end < line.length && line.charCodeAt(end) !== 0x20) end += 1;\n\t\t\t\tappendSeparator();\n\t\t\t\tconst text = line.slice(index, end);\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + text.length,\n\t\t\t\t\tlinearColumns: true,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += text.length;\n\t\t\t\tindex = end;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\t\tconst text = grapheme.segment;\n\t\t\t\tconst width = visibleWidth(text);\n\t\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += width;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappendSeparator();\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + width,\n\t\t\t\t\tlinearColumns: false,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += width;\n\t\t\t}\n\t\t}\n\t\tif (textLength > 0) pendingSeparator = true;\n\t}\n\n\treturn { text: chunks.join(\"\"), spans };\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction findSearchCorpusMatches(corpus: SearchCorpus, normalizedQuery: string): AltScreenSearchMatch[] {\n\tif (!normalizedQuery) return [];\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\tlet spanIndex = 0;\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= start) spanIndex += 1;\n\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = spanIndex; index < corpus.spans.length; index++) {\n\t\t\tconst span = corpus.spans[index]!;\n\t\t\tif (span.textStart >= end) break;\n\t\t\tif (span.textEnd <= start) continue;\n\t\t\tconst startCol = span.linearColumns\n\t\t\t\t? span.startCol + Math.max(start, span.textStart) - span.textStart\n\t\t\t\t: span.startCol;\n\t\t\tconst endCol = span.linearColumns ? span.startCol + Math.min(end, span.textEnd) - span.textStart : span.endCol;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ row: span.row, startCol, endCol });\n\t\t\t}\n\t\t}\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= end) spanIndex += 1;\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport interface AltScreenSearchResult {\n\tmatches: AltScreenSearchMatch[];\n\tchanged: boolean;\n}\n\n/** Cache the searchable corpus and matches while rendered transcript lines remain unchanged. */\nexport class AltScreenSearchIndex {\n\tprivate sourceLines: string[] | undefined;\n\tprivate corpus: SearchCorpus | undefined;\n\tprivate normalizedQuery: string | undefined;\n\tprivate matches: AltScreenSearchMatch[] = [];\n\n\tsearch(lines: readonly string[], query: string): AltScreenSearchResult {\n\t\tlet sourceChanged = this.sourceLines?.length !== lines.length;\n\t\tif (!sourceChanged && this.sourceLines) {\n\t\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\t\tif (this.sourceLines[index] === lines[index]) continue;\n\t\t\t\tsourceChanged = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (sourceChanged || !this.corpus) {\n\t\t\tthis.sourceLines = Array.from(lines);\n\t\t\tthis.corpus = buildSearchCorpus(lines);\n\t\t}\n\n\t\tconst normalizedQuery = normalizeQuery(query);\n\t\tconst changed = sourceChanged || normalizedQuery !== this.normalizedQuery;\n\t\tif (changed) {\n\t\t\tthis.normalizedQuery = normalizedQuery;\n\t\t\tthis.matches = findSearchCorpusMatches(this.corpus, normalizedQuery);\n\t\t}\n\t\treturn { matches: this.matches, changed };\n\t}\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\treturn normalizedQuery ? findSearchCorpusMatches(buildSearchCorpus(lines), normalizedQuery) : [];\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input({\n\t\tprompt: \" \",\n\t\tplaceholder: \"Find in transcript\",\n\t\tplaceholderStyle: (text) => `\\x1b[2m${text}\\x1b[22m`,\n\t});\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate readonly navigationButtonStyle: (text: string, hovered: boolean) => string;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate previousButtonStart = -1;\n\tprivate previousButtonEnd = -1;\n\tprivate nextButtonStart = -1;\n\tprivate nextButtonEnd = -1;\n\tprivate hoveredNavigationDirection: -1 | 1 | undefined;\n\tprivate _focused = false;\n\n\tconstructor(\n\t\tonQueryChange: (query: string) => void,\n\t\tnavigationButtonStyle: (text: string, hovered: boolean) => string = (text) => text,\n\t) {\n\t\tthis.onQueryChange = onQueryChange;\n\t\tthis.navigationButtonStyle = navigationButtonStyle;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\tgetNavigationDirectionAt(row: number, column: number): -1 | 1 | undefined {\n\t\tif (row !== 2) return undefined;\n\t\tif (column >= this.previousButtonStart && column < this.previousButtonEnd) return -1;\n\t\tif (column >= this.nextButtonStart && column < this.nextButtonEnd) return 1;\n\t\treturn undefined;\n\t}\n\n\tsetHoveredNavigationDirection(direction: -1 | 1 | undefined): boolean {\n\t\tif (direction === this.hoveredNavigationDirection) return false;\n\t\tthis.hoveredNavigationDirection = direction;\n\t\treturn true;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst innerWidth = Math.max(0, safeWidth - 2);\n\t\tconst formatKey = (key: string | undefined): string =>\n\t\t\tkey\n\t\t\t\t? key\n\t\t\t\t\t\t.split(\"+\")\n\t\t\t\t\t\t.map((part) => {\n\t\t\t\t\t\t\tif (process.platform === \"darwin\" && part.toLowerCase() === \"alt\") return \"Option\";\n\t\t\t\t\t\t\treturn part.charAt(0).toUpperCase() + part.slice(1);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.join(\"+\")\n\t\t\t\t: \"Unbound\";\n\t\tconst keybindings = getKeybindings();\n\t\tconst previousKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchPrevious\")[0]);\n\t\tconst nextKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchNext\")[0]);\n\t\tconst query = this.input.getValue();\n\t\tconst result = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches\"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount}`;\n\t\tconst resultSpace = Math.max(0, innerWidth - 3);\n\t\tconst visibleResult = truncateToWidth(result, resultSpace, \"\");\n\t\tconst resultText = visibleResult ? `\\x1b[2m ${visibleResult} \\x1b[22m` : \"\";\n\t\tconst inputWidth = Math.max(0, innerWidth - visibleWidth(resultText));\n\t\tconst inputLine = truncateToWidth(this.input.render(Math.max(1, inputWidth))[0] ?? \"\", inputWidth, \"\");\n\t\tconst inputPadding = \" \".repeat(Math.max(0, inputWidth - visibleWidth(inputLine)));\n\t\tconst content = `${inputLine}${inputPadding}${resultText}`;\n\n\t\tlet previousButton = `↑ ${previousKey}`;\n\t\tlet nextButton = `↓ ${nextKey}`;\n\t\tlet separator = \" · \";\n\t\tconst outerGapWidth = 1;\n\t\tconst availableControlsWidth = Math.max(0, innerWidth - outerGapWidth * 2 - 1);\n\t\tlet controlsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\tif (controlsWidth > availableControlsWidth) {\n\t\t\tpreviousButton = \"↑\";\n\t\t\tnextButton = \"↓\";\n\t\t\tseparator = \" \";\n\t\t\tcontrolsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\t}\n\t\tconst showButtons = controlsWidth <= availableControlsWidth;\n\t\tconst renderedButtons = showButtons\n\t\t\t? this.navigationButtonStyle(previousButton, this.hoveredNavigationDirection === -1) +\n\t\t\t\tseparator +\n\t\t\t\tthis.navigationButtonStyle(nextButton, this.hoveredNavigationDirection === 1)\n\t\t\t: \"\";\n\t\tconst outerGapsWidth = showButtons ? outerGapWidth * 2 : 0;\n\t\tconst rightRuleWidth = renderedButtons && innerWidth > controlsWidth + outerGapsWidth ? 1 : 0;\n\t\tconst leftRuleWidth = Math.max(\n\t\t\t0,\n\t\t\tinnerWidth - (showButtons ? controlsWidth : 0) - outerGapsWidth - rightRuleWidth,\n\t\t);\n\t\tconst previousStart = 1 + leftRuleWidth + outerGapWidth;\n\t\tthis.previousButtonStart = showButtons ? previousStart : -1;\n\t\tthis.previousButtonEnd = showButtons ? previousStart + visibleWidth(previousButton) : -1;\n\t\tthis.nextButtonStart = showButtons ? this.previousButtonEnd + visibleWidth(separator) : -1;\n\t\tthis.nextButtonEnd = showButtons ? this.nextButtonStart + visibleWidth(nextButton) : -1;\n\n\t\tif (safeWidth === 1) return [\"┌\", \"│\", \"└\"];\n\t\treturn [\n\t\t\t`┌${\"─\".repeat(innerWidth)}┐`,\n\t\t\t`│${content}│`,\n\t\t\t`└${\"─\".repeat(leftRuleWidth)}${renderedButtons ? \" \" : \"\"}${renderedButtons}${renderedButtons ? \" \" : \"\"}${\"─\".repeat(rightRuleWidth)}┘`,\n\t\t];\n\t}\n}\n"]}
|
package/dist/autocomplete.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare class CombinedAutocompleteProvider implements AutocompleteProvide
|
|
|
32
32
|
private commands;
|
|
33
33
|
private basePath;
|
|
34
34
|
private fdPath;
|
|
35
|
-
constructor(commands: (
|
|
35
|
+
constructor(commands: (SlashCommand | AutocompleteItem)[] | undefined, basePath: string, fdPath?: string | null);
|
|
36
36
|
getSuggestions(lines: string[], cursorLine: number, cursorCol: number, options: {
|
|
37
37
|
signal: AbortSignal;
|
|
38
38
|
force?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AAoOA,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;IACpC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAI7B,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;IAGF,2BAA2B,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9F;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAgB;IAE9B,YAAY,QAAQ,iDAA0C,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,EAI7G;IAEK,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,CAwFzC;IAED,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,CA+E5D;IAGD,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,uBAAuB;IA8B/B,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,kBAAkB;IAyI1B,OAAO,CAAC,UAAU;YAsBJ,qBAAqB;YAarB,uBAAuB;IA8ErC,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAU3F;CACD","sourcesContent":["import { spawn } from \"child_process\";\nimport { readdirSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { fuzzyFilter } from \"./fuzzy.ts\";\nimport { autocompleteBoundaryRegex, autocompleteSeparatorRegex } from \"./utils.ts\";\n\nconst PATH_DELIMITERS = new Set([\" \", \"\\t\", '\"', \"'\", \"=\"]);\nconst tokenStartRegex = new RegExp(`${autocompleteBoundaryRegex.source}$`, \"u\");\n\nfunction toDisplayPath(value: string): string {\n\treturn value.replace(/\\\\/g, \"/\");\n}\n\nfunction escapeRegex(value: string): string {\n\treturn value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction buildFdPathQuery(query: string): string {\n\tconst normalized = toDisplayPath(query);\n\tif (!normalized.includes(\"/\")) {\n\t\treturn normalized;\n\t}\n\n\tconst hasTrailingSeparator = normalized.endsWith(\"/\");\n\tconst trimmed = normalized.replace(/^\\/+|\\/+$/g, \"\");\n\tif (!trimmed) {\n\t\treturn normalized;\n\t}\n\n\tconst separatorPattern = \"[\\\\\\\\/]\";\n\tconst segments = trimmed\n\t\t.split(\"/\")\n\t\t.filter(Boolean)\n\t\t.map((segment) => escapeRegex(segment));\n\tif (segments.length === 0) {\n\t\treturn normalized;\n\t}\n\n\tlet pattern = segments.join(separatorPattern);\n\tif (hasTrailingSeparator) {\n\t\tpattern += separatorPattern;\n\t}\n\treturn pattern;\n}\n\nfunction findLastDelimiter(text: string): number {\n\tlet lastDelimiter = -1;\n\tlet index = 0;\n\tfor (const character of text) {\n\t\tindex += character.length;\n\t\tif (PATH_DELIMITERS.has(character) || autocompleteSeparatorRegex.test(character)) {\n\t\t\tlastDelimiter = index - 1;\n\t\t}\n\t}\n\treturn lastDelimiter;\n}\n\nfunction findUnclosedQuoteStart(text: string): number | null {\n\tlet inQuotes = false;\n\tlet quoteStart = -1;\n\n\tfor (let i = 0; i < text.length; i += 1) {\n\t\tif (text[i] === '\"') {\n\t\t\tinQuotes = !inQuotes;\n\t\t\tif (inQuotes) {\n\t\t\t\tquoteStart = i;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn inQuotes ? quoteStart : null;\n}\n\nfunction isTokenStart(text: string, index: number): boolean {\n\treturn PATH_DELIMITERS.has(text[index - 1] ?? \"\") || tokenStartRegex.test(text.slice(0, index));\n}\n\nfunction extractQuotedPrefix(text: string): string | null {\n\tconst quoteStart = findUnclosedQuoteStart(text);\n\tif (quoteStart === null) {\n\t\treturn null;\n\t}\n\n\tif (quoteStart > 0 && text[quoteStart - 1] === \"@\") {\n\t\tif (!isTokenStart(text, quoteStart - 1)) {\n\t\t\treturn null;\n\t\t}\n\t\treturn text.slice(quoteStart - 1);\n\t}\n\n\tif (!isTokenStart(text, quoteStart)) {\n\t\treturn null;\n\t}\n\n\treturn text.slice(quoteStart);\n}\n\nfunction parsePathPrefix(prefix: string): { rawPrefix: string; isAtPrefix: boolean; isQuotedPrefix: boolean } {\n\tif (prefix.startsWith('@\"')) {\n\t\treturn { rawPrefix: prefix.slice(2), isAtPrefix: true, isQuotedPrefix: true };\n\t}\n\tif (prefix.startsWith('\"')) {\n\t\treturn { rawPrefix: prefix.slice(1), isAtPrefix: false, isQuotedPrefix: true };\n\t}\n\tif (prefix.startsWith(\"@\")) {\n\t\treturn { rawPrefix: prefix.slice(1), isAtPrefix: true, isQuotedPrefix: false };\n\t}\n\treturn { rawPrefix: prefix, isAtPrefix: false, isQuotedPrefix: false };\n}\n\nfunction buildCompletionValue(\n\tpath: string,\n\toptions: { isDirectory: boolean; isAtPrefix: boolean; isQuotedPrefix: boolean },\n): string {\n\tconst needsQuotes = options.isQuotedPrefix || autocompleteSeparatorRegex.test(path);\n\tconst prefix = options.isAtPrefix ? \"@\" : \"\";\n\n\tif (!needsQuotes) {\n\t\treturn `${prefix}${path}`;\n\t}\n\n\tconst openQuote = `${prefix}\"`;\n\tconst closeQuote = '\"';\n\treturn `${openQuote}${path}${closeQuote}`;\n}\n\n// Use fd to walk directory tree (fast, respects .gitignore)\nasync function walkDirectoryWithFd(\n\tbaseDir: string,\n\tfdPath: string,\n\tquery: string,\n\tmaxResults: number,\n\tsignal: AbortSignal,\n\tmaxDepth?: number,\n): Promise<Array<{ path: string; isDirectory: boolean }>> {\n\tconst args = [\n\t\t\"--base-directory\",\n\t\tbaseDir,\n\t\t\"--max-results\",\n\t\tString(maxResults),\n\t\t\"--type\",\n\t\t\"f\",\n\t\t\"--type\",\n\t\t\"d\",\n\t\t\"--follow\",\n\t\t\"--hidden\",\n\t\t\"--exclude\",\n\t\t\".git\",\n\t\t\"--exclude\",\n\t\t\".git/*\",\n\t\t\"--exclude\",\n\t\t\".git/**\",\n\t];\n\n\tif (maxDepth !== undefined) {\n\t\targs.push(\"--max-depth\", String(maxDepth));\n\t}\n\n\tif (toDisplayPath(query).includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t}\n\n\tif (query) {\n\t\targs.push(buildFdPathQuery(query));\n\t}\n\n\treturn await new Promise((resolve) => {\n\t\tif (signal.aborted) {\n\t\t\tresolve([]);\n\t\t\treturn;\n\t\t}\n\n\t\tconst child = spawn(fdPath, args, {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\t\tlet stdout = \"\";\n\t\tlet resolved = false;\n\n\t\tconst finish = (results: Array<{ path: string; isDirectory: boolean }>) => {\n\t\t\tif (resolved) return;\n\t\t\tresolved = true;\n\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve(results);\n\t\t};\n\n\t\tconst onAbort = () => {\n\t\t\tif (child.exitCode === null) {\n\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t}\n\t\t};\n\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tchild.stdout.setEncoding(\"utf-8\");\n\t\tchild.stdout.on(\"data\", (chunk: string) => {\n\t\t\tstdout += chunk;\n\t\t});\n\t\tchild.on(\"error\", () => {\n\t\t\tfinish([]);\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (signal.aborted || code !== 0 || !stdout) {\n\t\t\t\tfinish([]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst lines = stdout.trim().split(\"\\n\").filter(Boolean);\n\t\t\tconst results: Array<{ path: string; isDirectory: boolean }> = [];\n\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst displayLine = toDisplayPath(line);\n\t\t\t\tconst hasTrailingSeparator = displayLine.endsWith(\"/\");\n\t\t\t\tconst normalizedPath = hasTrailingSeparator ? displayLine.slice(0, -1) : displayLine;\n\t\t\t\tif (normalizedPath === \".git\" || normalizedPath.startsWith(\".git/\") || normalizedPath.includes(\"/.git/\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tresults.push({\n\t\t\t\t\tpath: displayLine,\n\t\t\t\t\tisDirectory: hasTrailingSeparator,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tfinish(results);\n\t\t});\n\t});\n}\n\nexport interface AutocompleteItem {\n\tvalue: string;\n\tlabel: string;\n\tdescription?: string;\n}\n\ntype Awaitable<T> = T | Promise<T>;\n\nexport interface SlashCommand {\n\tname: string;\n\tdescription?: string;\n\targumentHint?: string;\n\t// Function to get argument completions for this command\n\t// Returns null if no argument completion is available\n\tgetArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;\n}\n\nexport interface AutocompleteSuggestions {\n\titems: AutocompleteItem[];\n\tprefix: string; // What we're matching against (e.g., \"/\" or \"src/\")\n}\n\nexport interface AutocompleteProvider {\n\t/** Characters that should naturally trigger this provider at token boundaries. */\n\ttriggerCharacters?: string[];\n\n\t// Get autocomplete suggestions for current text/cursor position\n\t// Returns null if no suggestions available\n\tgetSuggestions(\n\t\tlines: string[],\n\t\tcursorLine: number,\n\t\tcursorCol: number,\n\t\toptions: { signal: AbortSignal; force?: boolean },\n\t): Promise<AutocompleteSuggestions | null>;\n\n\t// Apply the selected item\n\t// Returns the new text and cursor position\n\tapplyCompletion(\n\t\tlines: string[],\n\t\tcursorLine: number,\n\t\tcursorCol: number,\n\t\titem: AutocompleteItem,\n\t\tprefix: string,\n\t): {\n\t\tlines: string[];\n\t\tcursorLine: number;\n\t\tcursorCol: number;\n\t};\n\n\t// Check if file completion should trigger for explicit Tab completion\n\tshouldTriggerFileCompletion?(lines: string[], cursorLine: number, cursorCol: number): boolean;\n}\n\n// Combined provider that handles both slash commands and file paths\nexport class CombinedAutocompleteProvider implements AutocompleteProvider {\n\tprivate commands: (SlashCommand | AutocompleteItem)[];\n\tprivate basePath: string;\n\tprivate fdPath: string | null;\n\n\tconstructor(commands: (SlashCommand | AutocompleteItem)[] = [], basePath: string, fdPath: string | null = null) {\n\t\tthis.commands = commands;\n\t\tthis.basePath = basePath;\n\t\tthis.fdPath = fdPath;\n\t}\n\n\tasync getSuggestions(\n\t\tlines: string[],\n\t\tcursorLine: number,\n\t\tcursorCol: number,\n\t\toptions: { signal: AbortSignal; force?: boolean },\n\t): Promise<AutocompleteSuggestions | null> {\n\t\tconst currentLine = lines[cursorLine] || \"\";\n\t\tconst textBeforeCursor = currentLine.slice(0, cursorCol);\n\n\t\tconst atPrefix = this.extractAtPrefix(textBeforeCursor);\n\t\tif (atPrefix) {\n\t\t\tconst { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);\n\t\t\tconst suggestions = await this.getFuzzyFileSuggestions(rawPrefix, {\n\t\t\t\tisQuotedPrefix,\n\t\t\t\tsignal: options.signal,\n\t\t\t});\n\t\t\tif (suggestions.length === 0) return null;\n\n\t\t\treturn {\n\t\t\t\titems: suggestions,\n\t\t\t\tprefix: atPrefix,\n\t\t\t};\n\t\t}\n\n\t\tif (!options.force && textBeforeCursor.startsWith(\"/\")) {\n\t\t\tconst spaceIndex = textBeforeCursor.indexOf(\" \");\n\n\t\t\tif (spaceIndex === -1) {\n\t\t\t\tconst prefix = textBeforeCursor.slice(1);\n\t\t\t\tconst commandItems = this.commands.map((cmd) => {\n\t\t\t\t\tconst name = \"name\" in cmd ? cmd.name : cmd.value;\n\t\t\t\t\tconst hint = \"argumentHint\" in cmd && cmd.argumentHint ? cmd.argumentHint : undefined;\n\t\t\t\t\tconst desc = cmd.description ?? \"\";\n\t\t\t\t\tconst fullDesc = hint ? (desc ? `${hint} — ${desc}` : hint) : desc;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tlabel: name,\n\t\t\t\t\t\tdescription: fullDesc || undefined,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\tconst filtered = fuzzyFilter(commandItems, prefix, (item) =>\n\t\t\t\t\t!prefix.startsWith(\"skill:\") && item.name.startsWith(\"skill:\")\n\t\t\t\t\t\t? item.name.slice(\"skill:\".length)\n\t\t\t\t\t\t: item.name,\n\t\t\t\t).map((item) => ({\n\t\t\t\t\tvalue: item.name,\n\t\t\t\t\tlabel: item.label,\n\t\t\t\t\t...(item.description && { description: item.description }),\n\t\t\t\t}));\n\n\t\t\t\tif (filtered.length === 0) return null;\n\n\t\t\t\treturn {\n\t\t\t\t\titems: filtered,\n\t\t\t\t\tprefix: textBeforeCursor,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst commandName = textBeforeCursor.slice(1, spaceIndex);\n\t\t\tconst argumentText = textBeforeCursor.slice(spaceIndex + 1);\n\n\t\t\tconst command = this.commands.find((cmd) => {\n\t\t\t\tconst name = \"name\" in cmd ? cmd.name : cmd.value;\n\t\t\t\treturn name === commandName;\n\t\t\t});\n\t\t\tif (!command || !(\"getArgumentCompletions\" in command) || !command.getArgumentCompletions) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst argumentSuggestions = await command.getArgumentCompletions(argumentText);\n\t\t\tif (!Array.isArray(argumentSuggestions) || argumentSuggestions.length === 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\titems: argumentSuggestions,\n\t\t\t\tprefix: argumentText,\n\t\t\t};\n\t\t}\n\n\t\tconst pathMatch = this.extractPathPrefix(textBeforeCursor, options.force ?? false);\n\t\tif (pathMatch === null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst suggestions = this.getFileSuggestions(pathMatch);\n\t\tif (suggestions.length === 0) return null;\n\n\t\treturn {\n\t\t\titems: suggestions,\n\t\t\tprefix: pathMatch,\n\t\t};\n\t}\n\n\tapplyCompletion(\n\t\tlines: string[],\n\t\tcursorLine: number,\n\t\tcursorCol: number,\n\t\titem: AutocompleteItem,\n\t\tprefix: string,\n\t): { lines: string[]; cursorLine: number; cursorCol: number } {\n\t\tconst currentLine = lines[cursorLine] || \"\";\n\t\tconst beforePrefix = currentLine.slice(0, cursorCol - prefix.length);\n\t\tconst afterCursor = currentLine.slice(cursorCol);\n\t\tconst isQuotedPrefix = prefix.startsWith('\"') || prefix.startsWith('@\"');\n\t\tconst hasLeadingQuoteAfterCursor = afterCursor.startsWith('\"');\n\t\tconst hasTrailingQuoteInItem = item.value.endsWith('\"');\n\t\tconst adjustedAfterCursor =\n\t\t\tisQuotedPrefix && hasTrailingQuoteInItem && hasLeadingQuoteAfterCursor ? afterCursor.slice(1) : afterCursor;\n\n\t\t// Check if we're completing a slash command (prefix starts with \"/\" but NOT a file path)\n\t\t// Slash commands are at the start of the line and don't contain path separators after the first /\n\t\tconst isSlashCommand = prefix.startsWith(\"/\") && beforePrefix.trim() === \"\" && !prefix.slice(1).includes(\"/\");\n\t\tif (isSlashCommand) {\n\t\t\t// This is a command name completion\n\t\t\tconst newLine = `${beforePrefix}/${item.value} ${adjustedAfterCursor}`;\n\t\t\tconst newLines = [...lines];\n\t\t\tnewLines[cursorLine] = newLine;\n\n\t\t\treturn {\n\t\t\t\tlines: newLines,\n\t\t\t\tcursorLine,\n\t\t\t\tcursorCol: beforePrefix.length + item.value.length + 2, // +2 for \"/\" and space\n\t\t\t};\n\t\t}\n\n\t\t// Check if we're completing a file attachment (prefix starts with \"@\")\n\t\tif (prefix.startsWith(\"@\")) {\n\t\t\t// This is a file attachment completion\n\t\t\t// Don't add space after directories so user can continue autocompleting\n\t\t\tconst isDirectory = item.label.endsWith(\"/\");\n\t\t\tconst suffix = isDirectory ? \"\" : \" \";\n\t\t\tconst newLine = `${beforePrefix + item.value}${suffix}${adjustedAfterCursor}`;\n\t\t\tconst newLines = [...lines];\n\t\t\tnewLines[cursorLine] = newLine;\n\n\t\t\tconst hasTrailingQuote = item.value.endsWith('\"');\n\t\t\tconst cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;\n\n\t\t\treturn {\n\t\t\t\tlines: newLines,\n\t\t\t\tcursorLine,\n\t\t\t\tcursorCol: beforePrefix.length + cursorOffset + suffix.length,\n\t\t\t};\n\t\t}\n\n\t\t// Check if we're in a slash command context (beforePrefix contains \"/command \")\n\t\tconst textBeforeCursor = currentLine.slice(0, cursorCol);\n\t\tif (textBeforeCursor.includes(\"/\") && textBeforeCursor.includes(\" \")) {\n\t\t\t// This is likely a command argument completion\n\t\t\tconst newLine = beforePrefix + item.value + adjustedAfterCursor;\n\t\t\tconst newLines = [...lines];\n\t\t\tnewLines[cursorLine] = newLine;\n\n\t\t\tconst isDirectory = item.label.endsWith(\"/\");\n\t\t\tconst hasTrailingQuote = item.value.endsWith('\"');\n\t\t\tconst cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;\n\n\t\t\treturn {\n\t\t\t\tlines: newLines,\n\t\t\t\tcursorLine,\n\t\t\t\tcursorCol: beforePrefix.length + cursorOffset,\n\t\t\t};\n\t\t}\n\n\t\t// For file paths, complete the path\n\t\tconst newLine = beforePrefix + item.value + adjustedAfterCursor;\n\t\tconst newLines = [...lines];\n\t\tnewLines[cursorLine] = newLine;\n\n\t\tconst isDirectory = item.label.endsWith(\"/\");\n\t\tconst hasTrailingQuote = item.value.endsWith('\"');\n\t\tconst cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;\n\n\t\treturn {\n\t\t\tlines: newLines,\n\t\t\tcursorLine,\n\t\t\tcursorCol: beforePrefix.length + cursorOffset,\n\t\t};\n\t}\n\n\t// Extract @ prefix for fuzzy file suggestions\n\tprivate extractAtPrefix(text: string): string | null {\n\t\tconst quotedPrefix = extractQuotedPrefix(text);\n\t\tif (quotedPrefix?.startsWith('@\"')) {\n\t\t\treturn quotedPrefix;\n\t\t}\n\n\t\tconst lastDelimiterIndex = findLastDelimiter(text);\n\t\tconst tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;\n\n\t\tif (text[tokenStart] === \"@\") {\n\t\t\treturn text.slice(tokenStart);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// Extract a path-like prefix from the text before cursor\n\tprivate extractPathPrefix(text: string, forceExtract: boolean = false): string | null {\n\t\tconst quotedPrefix = extractQuotedPrefix(text);\n\t\tif (quotedPrefix) {\n\t\t\treturn quotedPrefix;\n\t\t}\n\n\t\tconst lastDelimiterIndex = findLastDelimiter(text);\n\t\tconst pathPrefix = lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1);\n\n\t\t// For forced extraction (Tab key), always return something\n\t\tif (forceExtract) {\n\t\t\treturn pathPrefix;\n\t\t}\n\n\t\t// For natural triggers, return if it looks like a path, ends with /, starts with ~/, .\n\t\t// Only return empty string if the text looks like it's starting a path context\n\t\tif (pathPrefix.includes(\"/\") || pathPrefix.startsWith(\".\") || pathPrefix.startsWith(\"~/\")) {\n\t\t\treturn pathPrefix;\n\t\t}\n\n\t\t// Return an empty prefix after whitespace or CJK punctuation, but not for empty text.\n\t\t// Empty text should not trigger file suggestions - that's for forced Tab completion\n\t\tif (pathPrefix === \"\" && text !== \"\" && tokenStartRegex.test(text)) {\n\t\t\treturn pathPrefix;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// Expand home directory (~/) to actual home path\n\tprivate expandHomePath(path: string): string {\n\t\tif (path.startsWith(\"~/\")) {\n\t\t\tconst expandedPath = join(homedir(), path.slice(2));\n\t\t\t// Preserve trailing slash if original path had one\n\t\t\treturn path.endsWith(\"/\") && !expandedPath.endsWith(\"/\") ? `${expandedPath}/` : expandedPath;\n\t\t} else if (path === \"~\") {\n\t\t\treturn homedir();\n\t\t}\n\t\treturn path;\n\t}\n\n\tprivate resolveScopedFuzzyQuery(rawQuery: string): { baseDir: string; query: string; displayBase: string } | null {\n\t\tconst normalizedQuery = toDisplayPath(rawQuery);\n\t\tconst slashIndex = normalizedQuery.lastIndexOf(\"/\");\n\t\tif (slashIndex === -1) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst displayBase = normalizedQuery.slice(0, slashIndex + 1);\n\t\tconst query = normalizedQuery.slice(slashIndex + 1);\n\n\t\tlet baseDir: string;\n\t\tif (displayBase.startsWith(\"~/\")) {\n\t\t\tbaseDir = this.expandHomePath(displayBase);\n\t\t} else if (displayBase.startsWith(\"/\")) {\n\t\t\tbaseDir = displayBase;\n\t\t} else {\n\t\t\tbaseDir = join(this.basePath, displayBase);\n\t\t}\n\n\t\ttry {\n\t\t\tif (!statSync(baseDir).isDirectory()) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { baseDir, query, displayBase };\n\t}\n\n\tprivate scopedPathForDisplay(displayBase: string, relativePath: string): string {\n\t\tconst normalizedRelativePath = toDisplayPath(relativePath);\n\t\tif (displayBase === \"/\") {\n\t\t\treturn `/${normalizedRelativePath}`;\n\t\t}\n\t\treturn `${toDisplayPath(displayBase)}${normalizedRelativePath}`;\n\t}\n\n\t// Get file/directory suggestions for a given path prefix\n\tprivate getFileSuggestions(prefix: string): AutocompleteItem[] {\n\t\ttry {\n\t\t\tlet searchDir: string;\n\t\t\tlet searchPrefix: string;\n\t\t\tconst { rawPrefix, isAtPrefix, isQuotedPrefix } = parsePathPrefix(prefix);\n\t\t\tlet expandedPrefix = rawPrefix;\n\n\t\t\t// Handle home directory expansion\n\t\t\tif (expandedPrefix.startsWith(\"~\")) {\n\t\t\t\texpandedPrefix = this.expandHomePath(expandedPrefix);\n\t\t\t}\n\n\t\t\tconst isRootPrefix =\n\t\t\t\trawPrefix === \"\" ||\n\t\t\t\trawPrefix === \"./\" ||\n\t\t\t\trawPrefix === \"../\" ||\n\t\t\t\trawPrefix === \"~\" ||\n\t\t\t\trawPrefix === \"~/\" ||\n\t\t\t\trawPrefix === \"/\" ||\n\t\t\t\t(isAtPrefix && rawPrefix === \"\");\n\n\t\t\tif (isRootPrefix) {\n\t\t\t\t// Complete from specified position\n\t\t\t\tif (rawPrefix.startsWith(\"~\") || expandedPrefix.startsWith(\"/\")) {\n\t\t\t\t\tsearchDir = expandedPrefix;\n\t\t\t\t} else {\n\t\t\t\t\tsearchDir = join(this.basePath, expandedPrefix);\n\t\t\t\t}\n\t\t\t\tsearchPrefix = \"\";\n\t\t\t} else if (rawPrefix.endsWith(\"/\")) {\n\t\t\t\t// If prefix ends with /, show contents of that directory\n\t\t\t\tif (rawPrefix.startsWith(\"~\") || expandedPrefix.startsWith(\"/\")) {\n\t\t\t\t\tsearchDir = expandedPrefix;\n\t\t\t\t} else {\n\t\t\t\t\tsearchDir = join(this.basePath, expandedPrefix);\n\t\t\t\t}\n\t\t\t\tsearchPrefix = \"\";\n\t\t\t} else {\n\t\t\t\t// Split into directory and file prefix\n\t\t\t\tconst dir = dirname(expandedPrefix);\n\t\t\t\tconst file = basename(expandedPrefix);\n\t\t\t\tif (rawPrefix.startsWith(\"~\") || expandedPrefix.startsWith(\"/\")) {\n\t\t\t\t\tsearchDir = dir;\n\t\t\t\t} else {\n\t\t\t\t\tsearchDir = join(this.basePath, dir);\n\t\t\t\t}\n\t\t\t\tsearchPrefix = file;\n\t\t\t}\n\n\t\t\tconst entries = readdirSync(searchDir, { withFileTypes: true });\n\t\t\tconst suggestions: AutocompleteItem[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Check if entry is a directory (or a symlink pointing to a directory)\n\t\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\t\tif (!isDirectory && entry.isSymbolicLink()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst fullPath = join(searchDir, entry.name);\n\t\t\t\t\t\tisDirectory = statSync(fullPath).isDirectory();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Broken symlink or permission error - treat as file\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet relativePath: string;\n\t\t\t\tconst name = entry.name;\n\t\t\t\tconst displayPrefix = rawPrefix;\n\n\t\t\t\tif (displayPrefix.endsWith(\"/\")) {\n\t\t\t\t\t// If prefix ends with /, append entry to the prefix\n\t\t\t\t\trelativePath = displayPrefix + name;\n\t\t\t\t} else if (displayPrefix.includes(\"/\") || displayPrefix.includes(\"\\\\\")) {\n\t\t\t\t\t// Preserve ~/ format for home directory paths\n\t\t\t\t\tif (displayPrefix.startsWith(\"~/\")) {\n\t\t\t\t\t\tconst homeRelativeDir = displayPrefix.slice(2); // Remove ~/\n\t\t\t\t\t\tconst dir = dirname(homeRelativeDir);\n\t\t\t\t\t\trelativePath = `~/${dir === \".\" ? name : join(dir, name)}`;\n\t\t\t\t\t} else if (displayPrefix.startsWith(\"/\")) {\n\t\t\t\t\t\t// Absolute path - construct properly\n\t\t\t\t\t\tconst dir = dirname(displayPrefix);\n\t\t\t\t\t\tif (dir === \"/\") {\n\t\t\t\t\t\t\trelativePath = `/${name}`;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trelativePath = `${dir}/${name}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\trelativePath = join(dirname(displayPrefix), name);\n\t\t\t\t\t\t// path.join normalizes away ./ prefix, preserve it\n\t\t\t\t\t\tif (displayPrefix.startsWith(\"./\") && !relativePath.startsWith(\"./\")) {\n\t\t\t\t\t\t\trelativePath = `./${relativePath}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// For standalone entries, preserve ~/ if original prefix was ~/\n\t\t\t\t\tif (displayPrefix.startsWith(\"~\")) {\n\t\t\t\t\t\trelativePath = `~/${name}`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\trelativePath = name;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\trelativePath = toDisplayPath(relativePath);\n\t\t\t\tconst pathValue = isDirectory ? `${relativePath}/` : relativePath;\n\t\t\t\tconst value = buildCompletionValue(pathValue, {\n\t\t\t\t\tisDirectory,\n\t\t\t\t\tisAtPrefix,\n\t\t\t\t\tisQuotedPrefix,\n\t\t\t\t});\n\n\t\t\t\tsuggestions.push({\n\t\t\t\t\tvalue,\n\t\t\t\t\tlabel: name + (isDirectory ? \"/\" : \"\"),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Sort directories first, then alphabetically\n\t\t\tsuggestions.sort((a, b) => {\n\t\t\t\tconst aIsDir = a.label.endsWith(\"/\");\n\t\t\t\tconst bIsDir = b.label.endsWith(\"/\");\n\t\t\t\tif (aIsDir && !bIsDir) return -1;\n\t\t\t\tif (!aIsDir && bIsDir) return 1;\n\t\t\t\treturn a.label.localeCompare(b.label);\n\t\t\t});\n\n\t\t\treturn suggestions;\n\t\t} catch (_e) {\n\t\t\t// Directory doesn't exist or not accessible\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t// Score an entry against the query (higher = better match)\n\t// isDirectory adds bonus to prioritize folders\n\tprivate scoreEntry(filePath: string, query: string, isDirectory: boolean): number {\n\t\tconst fileName = basename(filePath);\n\t\tconst lowerFileName = fileName.toLowerCase();\n\t\tconst lowerQuery = query.toLowerCase();\n\n\t\tlet score = 0;\n\n\t\t// Exact filename match (highest)\n\t\tif (lowerFileName === lowerQuery) score = 100;\n\t\t// Filename starts with query\n\t\telse if (lowerFileName.startsWith(lowerQuery)) score = 80;\n\t\t// Substring match in filename\n\t\telse if (lowerFileName.includes(lowerQuery)) score = 50;\n\t\t// Substring match in full path\n\t\telse if (filePath.toLowerCase().includes(lowerQuery)) score = 30;\n\n\t\t// Directories get a bonus to appear first\n\t\tif (isDirectory && score > 0) score += 10;\n\n\t\treturn score;\n\t}\n\n\tprivate async getBaseDirSuggestions(\n\t\tbaseDir: string,\n\t\tquery: string,\n\t\tsignal: AbortSignal,\n\t): Promise<Array<{ path: string; isDirectory: boolean }>> {\n\t\tif (!this.fdPath || signal.aborted) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn await walkDirectoryWithFd(baseDir, this.fdPath, query, 100, signal, 1);\n\t}\n\n\t// Fuzzy file search using fd (fast, respects .gitignore)\n\tprivate async getFuzzyFileSuggestions(\n\t\tquery: string,\n\t\toptions: { isQuotedPrefix: boolean; signal: AbortSignal },\n\t): Promise<AutocompleteItem[]> {\n\t\tif (!this.fdPath || options.signal.aborted) {\n\t\t\treturn [];\n\t\t}\n\n\t\ttry {\n\t\t\tconst scopedQuery = this.resolveScopedFuzzyQuery(query);\n\t\t\tconst fdBaseDir = scopedQuery?.baseDir ?? this.basePath;\n\t\t\tconst fdQuery = scopedQuery?.query ?? query;\n\t\t\tconst baseDirEntries = await this.getBaseDirSuggestions(fdBaseDir, fdQuery, options.signal);\n\t\t\tconst recursiveEntries = await walkDirectoryWithFd(fdBaseDir, this.fdPath, fdQuery, 100, options.signal);\n\t\t\tconst seenPaths = new Set(baseDirEntries.map((entry) => entry.path));\n\t\t\tconst entries = [\n\t\t\t\t...baseDirEntries,\n\t\t\t\t...recursiveEntries.filter((entry) => {\n\t\t\t\t\tif (seenPaths.has(entry.path)) return false;\n\t\t\t\t\tseenPaths.add(entry.path);\n\t\t\t\t\treturn true;\n\t\t\t\t}),\n\t\t\t];\n\t\t\tif (options.signal.aborted) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tconst scoredEntries = entries\n\t\t\t\t.map((entry) => ({\n\t\t\t\t\t...entry,\n\t\t\t\t\tscore: fdQuery ? this.scoreEntry(entry.path, fdQuery, entry.isDirectory) : 1,\n\t\t\t\t}))\n\t\t\t\t.filter((entry) => entry.score > 0);\n\n\t\t\tscoredEntries.sort((a, b) => {\n\t\t\t\tconst scoreDiff = b.score - a.score;\n\t\t\t\tif (scoreDiff !== 0) return scoreDiff;\n\n\t\t\t\tconst aDepth = toDisplayPath(a.path).split(\"/\").filter(Boolean).length;\n\t\t\t\tconst bDepth = toDisplayPath(b.path).split(\"/\").filter(Boolean).length;\n\t\t\t\tconst depthDiff = aDepth - bDepth;\n\t\t\t\tif (depthDiff !== 0) return depthDiff;\n\n\t\t\t\tconst lengthDiff = a.path.length - b.path.length;\n\t\t\t\tif (lengthDiff !== 0) return lengthDiff;\n\n\t\t\t\treturn a.path.localeCompare(b.path);\n\t\t\t});\n\t\t\tconst topEntries = scoredEntries.slice(0, 20);\n\n\t\t\tconst suggestions: AutocompleteItem[] = [];\n\t\t\tfor (const { path: entryPath, isDirectory } of topEntries) {\n\t\t\t\tconst pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;\n\t\t\t\tconst displayPath = scopedQuery\n\t\t\t\t\t? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash)\n\t\t\t\t\t: pathWithoutSlash;\n\t\t\t\tconst entryName = basename(pathWithoutSlash);\n\t\t\t\tconst completionPath = isDirectory ? `${displayPath}/` : displayPath;\n\t\t\t\tconst value = buildCompletionValue(completionPath, {\n\t\t\t\t\tisDirectory,\n\t\t\t\t\tisAtPrefix: true,\n\t\t\t\t\tisQuotedPrefix: options.isQuotedPrefix,\n\t\t\t\t});\n\n\t\t\t\tsuggestions.push({\n\t\t\t\t\tvalue,\n\t\t\t\t\tlabel: entryName + (isDirectory ? \"/\" : \"\"),\n\t\t\t\t\tdescription: displayPath,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn suggestions;\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t// Check if we should trigger file completion (called on Tab key)\n\tshouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean {\n\t\tconst currentLine = lines[cursorLine] || \"\";\n\t\tconst textBeforeCursor = currentLine.slice(0, cursorCol);\n\n\t\t// Don't trigger if we're typing a slash command at the start of the line\n\t\tif (textBeforeCursor.trim().startsWith(\"/\") && !textBeforeCursor.trim().includes(\" \")) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AAwPA,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;IACpC,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAI7B,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;IAGF,2BAA2B,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9F;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAgB;IAE9B,YAAY,QAAQ,EAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAE,YAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,EAI7G;IAEK,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,CA6FzC;IAED,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,CA+E5D;IAGD,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,uBAAuB;IA8B/B,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,kBAAkB;IAyI1B,OAAO,CAAC,UAAU;YAsBJ,qBAAqB;YAarB,uBAAuB;IA8ErC,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAU3F;CACD"}
|
package/dist/autocomplete.js
CHANGED
|
@@ -6,6 +6,8 @@ import { fuzzyFilter } from "./fuzzy.js";
|
|
|
6
6
|
import { autocompleteBoundaryRegex, autocompleteSeparatorRegex } from "./utils.js";
|
|
7
7
|
const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
|
|
8
8
|
const tokenStartRegex = new RegExp(`${autocompleteBoundaryRegex.source}$`, "u");
|
|
9
|
+
// Opening wrappers that may precede a path in prose, mapped to their closing counterpart.
|
|
10
|
+
const PATH_WRAPPERS = { "(": ")", "[": "]", "{": "}", "<": ">", "`": "`" };
|
|
9
11
|
function toDisplayPath(value) {
|
|
10
12
|
return value.replace(/\\/g, "/");
|
|
11
13
|
}
|
|
@@ -47,6 +49,19 @@ function findLastDelimiter(text) {
|
|
|
47
49
|
}
|
|
48
50
|
return lastDelimiter;
|
|
49
51
|
}
|
|
52
|
+
// Strip opening wrappers before a path, e.g. "(~/Dev" -> "~/Dev" or "`src/ma" -> "src/ma".
|
|
53
|
+
// Keep a wrapper if the token also contains its closer, e.g. "app/[slug]/pa" or "(group)/pa".
|
|
54
|
+
function stripLeadingWrappers(token) {
|
|
55
|
+
let result = token;
|
|
56
|
+
while (result.length > 0) {
|
|
57
|
+
const closer = PATH_WRAPPERS[result[0]];
|
|
58
|
+
if (!closer || result.includes(closer, 1)) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
result = result.slice(1);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
50
65
|
function findUnclosedQuoteStart(text) {
|
|
51
66
|
let inQuotes = false;
|
|
52
67
|
let quoteStart = -1;
|
|
@@ -61,7 +76,11 @@ function findUnclosedQuoteStart(text) {
|
|
|
61
76
|
return inQuotes ? quoteStart : null;
|
|
62
77
|
}
|
|
63
78
|
function isTokenStart(text, index) {
|
|
64
|
-
|
|
79
|
+
let start = index;
|
|
80
|
+
while (start > 0 && PATH_WRAPPERS[text[start - 1]]) {
|
|
81
|
+
start -= 1;
|
|
82
|
+
}
|
|
83
|
+
return PATH_DELIMITERS.has(text[start - 1] ?? "") || tokenStartRegex.test(text.slice(0, start));
|
|
65
84
|
}
|
|
66
85
|
function extractQuotedPrefix(text) {
|
|
67
86
|
const quoteStart = findUnclosedQuoteStart(text);
|
|
@@ -225,9 +244,10 @@ export class CombinedAutocompleteProvider {
|
|
|
225
244
|
description: fullDesc || undefined,
|
|
226
245
|
};
|
|
227
246
|
});
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
247
|
+
const bareNameMatches = fuzzyFilter(commandItems, prefix, (item) => item.name.startsWith("skill:") ? item.name.slice("skill:".length) : item.name);
|
|
248
|
+
const bareNameMatchSet = new Set(bareNameMatches);
|
|
249
|
+
const fullNameOnlyMatches = fuzzyFilter(commandItems.filter((item) => item.name.startsWith("skill:") && !bareNameMatchSet.has(item)), prefix, (item) => item.name);
|
|
250
|
+
const filtered = [...bareNameMatches, ...fullNameOnlyMatches].map((item) => ({
|
|
231
251
|
value: item.name,
|
|
232
252
|
label: item.label,
|
|
233
253
|
...(item.description && { description: item.description }),
|
|
@@ -344,9 +364,9 @@ export class CombinedAutocompleteProvider {
|
|
|
344
364
|
return quotedPrefix;
|
|
345
365
|
}
|
|
346
366
|
const lastDelimiterIndex = findLastDelimiter(text);
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
return
|
|
367
|
+
const token = stripLeadingWrappers(lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1));
|
|
368
|
+
if (token.startsWith("@")) {
|
|
369
|
+
return token;
|
|
350
370
|
}
|
|
351
371
|
return null;
|
|
352
372
|
}
|
|
@@ -357,7 +377,7 @@ export class CombinedAutocompleteProvider {
|
|
|
357
377
|
return quotedPrefix;
|
|
358
378
|
}
|
|
359
379
|
const lastDelimiterIndex = findLastDelimiter(text);
|
|
360
|
-
const pathPrefix = lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1);
|
|
380
|
+
const pathPrefix = stripLeadingWrappers(lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1));
|
|
361
381
|
// For forced extraction (Tab key), always return something
|
|
362
382
|
if (forceExtract) {
|
|
363
383
|
return pathPrefix;
|