@selesai/code 0.9.15 → 0.10.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.
Files changed (269) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/modes/interactive/components/settings-selector.d.ts +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +1 -1
  119. package/dist/skills/design-references/SKILL.md +31 -0
  120. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  121. package/dist/themes/adventure.json +84 -0
  122. package/dist/themes/adwaita-dark.json +84 -0
  123. package/dist/themes/arcoiris.json +84 -0
  124. package/dist/themes/arthur.json +84 -0
  125. package/dist/themes/atom.json +84 -0
  126. package/dist/themes/aura.json +84 -0
  127. package/dist/themes/black-metal-bathory.json +84 -0
  128. package/dist/themes/black-metal-burzum.json +84 -0
  129. package/dist/themes/black-metal-khold.json +84 -0
  130. package/dist/themes/box.json +84 -0
  131. package/dist/themes/brogrammer.json +84 -0
  132. package/dist/themes/carbonfox.json +84 -0
  133. package/dist/themes/catppuccin-mocha.json +84 -0
  134. package/dist/themes/citruszest.json +84 -0
  135. package/dist/themes/cursor-dark.json +84 -0
  136. package/dist/themes/cutie-pro.json +84 -0
  137. package/dist/themes/dark-modern.json +84 -0
  138. package/dist/themes/dark-pastel.json +84 -0
  139. package/dist/themes/dimmed-monokai.json +84 -0
  140. package/dist/themes/doom-peacock.json +84 -0
  141. package/dist/themes/dracula-plus.json +84 -0
  142. package/dist/themes/dracula.json +80 -0
  143. package/dist/themes/earthsong.json +84 -0
  144. package/dist/themes/everforest-dark-hard.json +84 -0
  145. package/dist/themes/everforest-dark.json +80 -0
  146. package/dist/themes/fahrenheit.json +84 -0
  147. package/dist/themes/flatland.json +84 -0
  148. package/dist/themes/flexoki-dark.json +84 -0
  149. package/dist/themes/front-end-delight.json +84 -0
  150. package/dist/themes/fun-forrest.json +84 -0
  151. package/dist/themes/galizur.json +84 -0
  152. package/dist/themes/github-dark-colorblind.json +84 -0
  153. package/dist/themes/github-dark-high-contrast.json +84 -0
  154. package/dist/themes/glacier.json +84 -0
  155. package/dist/themes/gruber-darker.json +84 -0
  156. package/dist/themes/gruvbox-dark-hard.json +84 -0
  157. package/dist/themes/gruvbox-dark.json +84 -0
  158. package/dist/themes/gruvbox-material.json +84 -0
  159. package/dist/themes/guezwhoz.json +84 -0
  160. package/dist/themes/hacktober.json +84 -0
  161. package/dist/themes/hardcore.json +84 -0
  162. package/dist/themes/havn-skumring.json +84 -0
  163. package/dist/themes/ic-orange-ppl.json +84 -0
  164. package/dist/themes/iterm2-smoooooth.json +84 -0
  165. package/dist/themes/iterm2-tango-dark.json +84 -0
  166. package/dist/themes/japanesque.json +84 -0
  167. package/dist/themes/jellybeans.json +84 -0
  168. package/dist/themes/kanagawa-wave.json +84 -0
  169. package/dist/themes/kurokula.json +84 -0
  170. package/dist/themes/later-this-evening.json +84 -0
  171. package/dist/themes/lovelace.json +84 -0
  172. package/dist/themes/material-darker.json +84 -0
  173. package/dist/themes/matte-black.json +84 -0
  174. package/dist/themes/mellow.json +84 -0
  175. package/dist/themes/miasma.json +84 -0
  176. package/dist/themes/nord.json +80 -0
  177. package/dist/themes/nvim-dark.json +84 -0
  178. package/dist/themes/one-dark.json +80 -0
  179. package/dist/themes/popping-and-locking.json +84 -0
  180. package/dist/themes/rose-pine.json +80 -0
  181. package/dist/themes/sea-shells.json +84 -0
  182. package/dist/themes/sleepy-hollow.json +84 -0
  183. package/dist/themes/smyck.json +84 -0
  184. package/dist/themes/solarized-dark.json +80 -0
  185. package/dist/themes/tokyo-night.json +80 -0
  186. package/dist/themes/tomorrow-night-bright.json +84 -0
  187. package/dist/themes/tomorrow-night-burns.json +84 -0
  188. package/dist/themes/tomorrow-night.json +84 -0
  189. package/dist/themes/twilight.json +84 -0
  190. package/dist/themes/vague.json +84 -0
  191. package/dist/themes/vesper.json +84 -0
  192. package/dist/themes/xcode-dark-hc.json +84 -0
  193. package/docs/custom-provider.md +0 -1
  194. package/package.json +3 -3
  195. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  196. package/dist/extensions/auto-model/classifier.ts +0 -23
  197. package/dist/extensions/auto-model/config.ts +0 -51
  198. package/dist/extensions/auto-model/index.ts +0 -22
  199. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  200. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  201. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  202. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  203. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  204. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  205. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  206. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  207. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  208. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  209. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  210. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  211. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  212. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  213. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  214. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  215. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  216. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  217. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  218. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  219. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  220. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  221. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  222. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  223. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  224. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  225. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  226. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  227. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  228. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  229. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  230. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  231. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  232. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  233. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  234. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  235. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  236. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  237. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  238. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  239. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  240. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  241. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  242. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  243. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  244. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  245. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  246. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  247. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  248. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  249. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  250. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  251. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  252. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  253. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  254. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  255. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  256. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  257. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  258. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  259. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  260. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  261. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  262. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  263. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  264. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  265. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  266. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  267. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  268. package/dist/extensions/preview-tools-disabled.ts +0 -11
  269. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -1,586 +0,0 @@
1
- import { readdirSync } from "node:fs";
2
- import { execFile } from "node:child_process";
3
- import { basename, dirname, isAbsolute, join, resolve } from "node:path";
4
- import type { AutocompleteItem, AutocompleteProvider, AutocompleteSuggestions } from "@earendil-works/pi-tui";
5
- import { matchHistoryEntries, readGlobalShellHistory, readProjectHistory } from "./history.ts";
6
- import type { ExtendedCompletionItem, GhostSuggestion } from "./types.ts";
7
-
8
- interface TokenContext {
9
- line: string;
10
- cursorCol: number;
11
- beforeCursor: string;
12
- afterCursor: string;
13
- token: string;
14
- tokenStart: number;
15
- tokenEnd: number;
16
- tokenIndex: number;
17
- previousTokens: string[];
18
- }
19
-
20
- export interface OneOffBashCommandContext {
21
- prefix: string;
22
- command: string;
23
- offset: number;
24
- }
25
-
26
- const GIT_SUBCOMMANDS = [
27
- "add", "bisect", "branch", "checkout", "cherry-pick", "clean", "clone", "commit", "diff", "fetch",
28
- "grep", "init", "log", "merge", "mv", "pull", "push", "rebase", "reset", "restore", "revert", "rm",
29
- "show", "stash", "status", "switch", "tag", "worktree",
30
- ];
31
-
32
- function tokenizeBeforeCursor(text: string): string[] {
33
- const tokens: string[] = [];
34
- let current = "";
35
- let quote: "'" | '"' | null = null;
36
- let escaped = false;
37
-
38
- for (const char of text) {
39
- if (escaped) {
40
- current += char;
41
- escaped = false;
42
- continue;
43
- }
44
-
45
- if (char === "\\") {
46
- escaped = true;
47
- current += char;
48
- continue;
49
- }
50
-
51
- if (quote) {
52
- current += char;
53
- if (char === quote) quote = null;
54
- continue;
55
- }
56
-
57
- if (char === "'" || char === '"') {
58
- quote = char;
59
- current += char;
60
- continue;
61
- }
62
-
63
- if (/\s/.test(char)) {
64
- if (current) {
65
- tokens.push(current);
66
- current = "";
67
- }
68
- continue;
69
- }
70
-
71
- current += char;
72
- }
73
-
74
- if (current) tokens.push(current);
75
- return tokens;
76
- }
77
-
78
- function getTokenContext(line: string, cursorCol: number): TokenContext {
79
- const beforeCursor = line.slice(0, cursorCol);
80
- const afterCursor = line.slice(cursorCol);
81
- const tokens = tokenizeBeforeCursor(beforeCursor);
82
-
83
- let tokenStart = 0;
84
- for (let i = beforeCursor.length - 1; i >= 0; i -= 1) {
85
- const char = beforeCursor[i];
86
- if (char && /\s/.test(char)) {
87
- tokenStart = i + 1;
88
- break;
89
- }
90
- }
91
-
92
- let tokenEnd = cursorCol;
93
- for (let i = cursorCol; i < line.length; i += 1) {
94
- const char = line[i];
95
- if (char && /\s/.test(char)) break;
96
- tokenEnd = i + 1;
97
- }
98
-
99
- return {
100
- line,
101
- cursorCol,
102
- beforeCursor,
103
- afterCursor,
104
- token: line.slice(tokenStart, tokenEnd),
105
- tokenStart,
106
- tokenEnd,
107
- tokenIndex: Math.max(0, tokens.length - 1),
108
- previousTokens: tokens,
109
- };
110
- }
111
-
112
- export function getOneOffBashCommandContext(line: string): OneOffBashCommandContext | null {
113
- if (line.startsWith("!!")) {
114
- return {
115
- prefix: "!!",
116
- command: line.slice(2),
117
- offset: 2,
118
- };
119
- }
120
-
121
- if (line.startsWith("!")) {
122
- return {
123
- prefix: "!",
124
- command: line.slice(1),
125
- offset: 1,
126
- };
127
- }
128
-
129
- return null;
130
- }
131
-
132
- function supportsShouldTriggerFileCompletion(
133
- provider: AutocompleteProvider,
134
- ): provider is AutocompleteProvider & {
135
- shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
136
- } {
137
- return "shouldTriggerFileCompletion" in provider && typeof provider.shouldTriggerFileCompletion === "function";
138
- }
139
-
140
- function isExtendedCompletionItem(item: AutocompleteItem): item is ExtendedCompletionItem {
141
- return "replacement" in item
142
- && typeof item.replacement === "string"
143
- && "startCol" in item
144
- && typeof item.startCol === "number"
145
- && "endCol" in item
146
- && typeof item.endCol === "number";
147
- }
148
-
149
- function uniqueByReplacement(items: ExtendedCompletionItem[]): ExtendedCompletionItem[] {
150
- const best = new Map<string, ExtendedCompletionItem>();
151
- for (const item of items) {
152
- const key = `${item.startCol}:${item.endCol}:${item.replacement}`;
153
- const existing = best.get(key);
154
- if (!existing || item.score > existing.score) {
155
- best.set(key, item);
156
- }
157
- }
158
- return [...best.values()].sort((a, b) => b.score - a.score || a.label.localeCompare(b.label));
159
- }
160
-
161
- function pathBase(token: string, cwd: string): { dir: string; prefix: string; displayPrefix: string } {
162
- const expanded = token.startsWith("~/")
163
- ? join(process.env.HOME || "", token.slice(2))
164
- : token;
165
-
166
- const hasSlash = expanded.includes("/");
167
- if (!hasSlash) {
168
- return { dir: cwd, prefix: expanded, displayPrefix: "" };
169
- }
170
-
171
- const baseDir = expanded.endsWith("/") ? expanded.slice(0, -1) : dirname(expanded);
172
- const resolvedDir = isAbsolute(baseDir) ? baseDir : resolve(cwd, baseDir);
173
- const prefix = expanded.endsWith("/") ? "" : basename(expanded);
174
- const displayPrefix = token.endsWith("/") ? token : token.slice(0, Math.max(0, token.length - prefix.length));
175
- return { dir: resolvedDir, prefix, displayPrefix };
176
- }
177
-
178
- function escapeShellPath(value: string): string {
179
- return value.replace(/([\\\s"'`$&|;<>()[\]{}?!*])/g, "\\$1");
180
- }
181
-
182
- function getPathSuggestions(token: string, cwd: string): ExtendedCompletionItem[] {
183
- if (!token) return [];
184
- const { dir, prefix, displayPrefix } = pathBase(token, cwd);
185
-
186
- try {
187
- const entries = readdirSync(dir, { withFileTypes: true })
188
- .filter((entry) => prefix.length === 0 || entry.name.startsWith(prefix))
189
- .slice(0, 100);
190
-
191
- return entries.map((entry) => {
192
- const suffix = entry.isDirectory() ? "/" : "";
193
- const label = `${displayPrefix}${entry.name}${suffix}`;
194
- const replacement = `${displayPrefix}${escapeShellPath(entry.name)}${suffix}`;
195
- return {
196
- value: replacement,
197
- label,
198
- replacement,
199
- startCol: 0,
200
- endCol: 0,
201
- source: "path",
202
- score: 40 + (entry.isDirectory() ? 4 : 0),
203
- } satisfies ExtendedCompletionItem;
204
- });
205
- } catch {
206
- // Missing or unreadable directories should only remove this completion source.
207
- return [];
208
- }
209
- }
210
-
211
- function runGit(args: string[], cwd: string, signal: AbortSignal): Promise<string[]> {
212
- return new Promise((resolve) => {
213
- try {
214
- execFile("git", args, { cwd, encoding: "utf8", signal }, (error, stdout) => {
215
- if (error || !stdout) {
216
- resolve([]);
217
- return;
218
- }
219
- resolve(stdout.split("\n").map((line) => line.trim()).filter(Boolean));
220
- });
221
- } catch {
222
- // Git-aware completions are optional and should not break the main completion flow.
223
- resolve([]);
224
- }
225
- });
226
- }
227
-
228
- async function getGitSuggestions(
229
- ctx: TokenContext,
230
- cwd: string,
231
- signal: AbortSignal,
232
- ): Promise<ExtendedCompletionItem[]> {
233
- const tokens = ctx.previousTokens;
234
- if (tokens[0] !== "git") return [];
235
-
236
- if (ctx.tokenIndex <= 1) {
237
- return GIT_SUBCOMMANDS
238
- .filter((command) => command.startsWith(ctx.token))
239
- .map((command) => ({
240
- value: command,
241
- label: command,
242
- replacement: command,
243
- startCol: 0,
244
- endCol: 0,
245
- source: "git",
246
- score: 52,
247
- }));
248
- }
249
-
250
- const subcommand = tokens[1] ?? "";
251
- if (!["checkout", "switch", "merge", "rebase", "branch", "show", "diff"].includes(subcommand)) {
252
- return [];
253
- }
254
-
255
- const [branches, tags] = await Promise.all([
256
- runGit(["branch", "--format=%(refname:short)"], cwd, signal),
257
- runGit(["tag", "--list"], cwd, signal),
258
- ]);
259
- if (signal.aborted) return [];
260
-
261
- return [...new Set([...branches, ...tags])]
262
- .filter((ref) => ref.startsWith(ctx.token))
263
- .slice(0, 100)
264
- .map((ref) => ({
265
- value: ref,
266
- label: ref,
267
- replacement: ref,
268
- startCol: 0,
269
- endCol: 0,
270
- source: "git",
271
- score: 50,
272
- }));
273
- }
274
-
275
- function canUseHistorySuggestion(ctx: TokenContext): boolean {
276
- return ctx.cursorCol === ctx.line.length && ctx.line.trim().length > 0;
277
- }
278
-
279
- function withRange(items: ExtendedCompletionItem[], startCol: number, endCol: number): ExtendedCompletionItem[] {
280
- return items.map((item) => ({ ...item, startCol, endCol }));
281
- }
282
-
283
- function applyCompletionToLine(line: string, item: ExtendedCompletionItem): string {
284
- return line.slice(0, item.startCol) + item.replacement + line.slice(item.endCol);
285
- }
286
-
287
- function commandHead(value: string): string {
288
- return tokenizeBeforeCursor(value.trim())[0] ?? "";
289
- }
290
-
291
- function findNewestHistoryMatchForHead(entries: string[], prefix: string, head: string): string | null {
292
- for (const rawEntry of entries) {
293
- const entry = rawEntry.trim();
294
- if (!entry || !entry.startsWith(prefix)) continue;
295
- if (commandHead(entry) !== head) continue;
296
- return entry;
297
- }
298
- return null;
299
- }
300
-
301
- function getCuratedCommandFallback(prefix: string): GhostSuggestion | null {
302
- const trimmed = prefix.trim();
303
- if (!trimmed) return null;
304
-
305
- if ("cd".startsWith(trimmed)) {
306
- return { value: "cd ..", source: "path" };
307
- }
308
-
309
- if ("git".startsWith(trimmed)) {
310
- return { value: "git status", source: "git" };
311
- }
312
-
313
- return null;
314
- }
315
-
316
- function isLikelyGitCommandHead(prefix: string): boolean {
317
- const trimmed = prefix.trim();
318
- return trimmed.length >= 2 && "git".startsWith(trimmed);
319
- }
320
-
321
- function boostValidatedItemsFromGlobalHistory(
322
- line: string,
323
- items: ExtendedCompletionItem[],
324
- globalHistoryMatches: string[],
325
- ): ExtendedCompletionItem[] {
326
- if (items.length === 0 || globalHistoryMatches.length === 0) {
327
- return items;
328
- }
329
-
330
- const boosts = new Map<string, number>();
331
- for (const [index, value] of globalHistoryMatches.entries()) {
332
- boosts.set(value, Math.max(1, 4 - index));
333
- }
334
-
335
- return items.map((item) => {
336
- const boost = boosts.get(applyCompletionToLine(line, item));
337
- return boost ? { ...item, score: item.score + boost } : item;
338
- });
339
- }
340
-
341
- export class BashCompletionEngine {
342
- async getGhostSuggestion(line: string, cwd: string, shellPath: string, signal: AbortSignal): Promise<GhostSuggestion | null> {
343
- const projectHistoryEntries = readProjectHistory(cwd);
344
-
345
- if (line.trim().length === 0) {
346
- const prefix = line;
347
- const projectHistory = matchHistoryEntries(
348
- projectHistoryEntries.map((entry) => entry.command),
349
- line,
350
- 1,
351
- );
352
- if (projectHistory.length > 0) {
353
- return { value: `${prefix}${projectHistory[0]!}`, source: "project-history" };
354
- }
355
-
356
- return null;
357
- }
358
-
359
- const ctx = getTokenContext(line, line.length);
360
- if (!canUseHistorySuggestion(ctx)) return null;
361
-
362
- const projectHistory = matchHistoryEntries(
363
- projectHistoryEntries.map((entry) => entry.command),
364
- line,
365
- 10,
366
- );
367
- if (projectHistory.length > 0) {
368
- return { value: projectHistory[0]!, source: "project-history" };
369
- }
370
-
371
- if (ctx.tokenIndex === 0) {
372
- return this.getCommandPositionGhostSuggestion(line, readGlobalShellHistory(shellPath));
373
- }
374
-
375
- const deterministic = await this.getDeterministicInlineSuggestions(ctx, cwd, signal);
376
- if (signal.aborted) return null;
377
- const globalHistory = matchHistoryEntries(readGlobalShellHistory(shellPath), line, 5);
378
- const ranked = boostValidatedItemsFromGlobalHistory(
379
- line,
380
- uniqueByReplacement(deterministic),
381
- globalHistory,
382
- );
383
-
384
- for (const item of ranked) {
385
- const value = this.buildInlineSuggestionValue(line, item);
386
- if (value) {
387
- return { value, source: item.source };
388
- }
389
- }
390
-
391
- return null;
392
- }
393
-
394
- private getCommandPositionGhostSuggestion(
395
- line: string,
396
- globalHistoryEntries: string[],
397
- ): GhostSuggestion | null {
398
- if (isLikelyGitCommandHead(line)) {
399
- const globalMatch = findNewestHistoryMatchForHead(globalHistoryEntries, line, "git");
400
- if (globalMatch) {
401
- return { value: globalMatch, source: "global-history" };
402
- }
403
- }
404
-
405
- return getCuratedCommandFallback(line);
406
- }
407
-
408
- private async getDeterministicInlineSuggestions(
409
- ctx: TokenContext,
410
- cwd: string,
411
- signal: AbortSignal,
412
- ): Promise<ExtendedCompletionItem[]> {
413
- const gitSuggestions = getGitSuggestions(ctx, cwd, signal);
414
- const pathSuggestions = getPathSuggestions(ctx.token, cwd);
415
- const items = [
416
- ...withRange(await gitSuggestions, ctx.tokenStart, ctx.tokenEnd),
417
- ...withRange(pathSuggestions, ctx.tokenStart, ctx.tokenEnd),
418
- ];
419
-
420
- return uniqueByReplacement(items);
421
- }
422
-
423
- private buildInlineSuggestionValue(line: string, item: ExtendedCompletionItem): string | null {
424
- const value = applyCompletionToLine(line, item);
425
- if (!value.startsWith(line) || value === line) {
426
- return null;
427
- }
428
- return value;
429
- }
430
-
431
- }
432
-
433
- export class BashAutocompleteProvider implements AutocompleteProvider {
434
- async getSuggestions(): Promise<AutocompleteSuggestions | null> {
435
- return null;
436
- }
437
-
438
- applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem): {
439
- lines: string[];
440
- cursorLine: number;
441
- cursorCol: number;
442
- } {
443
- if (!isExtendedCompletionItem(item)) {
444
- throw new Error("Expected an extended completion item for bash autocomplete");
445
- }
446
-
447
- return applyExtendedCompletion(lines, cursorLine, item);
448
- }
449
-
450
- shouldTriggerFileCompletion(): boolean {
451
- return false;
452
- }
453
- }
454
-
455
- function applyExtendedCompletion(lines: string[], cursorLine: number, item: ExtendedCompletionItem): {
456
- lines: string[];
457
- cursorLine: number;
458
- cursorCol: number;
459
- } {
460
- const currentLine = lines[cursorLine] || "";
461
- const startCol = Math.max(0, Math.min(item.startCol, currentLine.length));
462
- const endCol = Math.max(startCol, Math.min(item.endCol, currentLine.length));
463
- const nextLine = currentLine.slice(0, startCol) + item.replacement + currentLine.slice(endCol);
464
- const nextLines = [...lines];
465
- nextLines[cursorLine] = nextLine;
466
- return {
467
- lines: nextLines,
468
- cursorLine,
469
- cursorCol: startCol + item.replacement.length,
470
- };
471
- }
472
-
473
- export class OneOffBashAutocompleteProvider implements AutocompleteProvider {
474
- async getSuggestions(): Promise<AutocompleteSuggestions | null> {
475
- return null;
476
- }
477
-
478
- applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem): {
479
- lines: string[];
480
- cursorLine: number;
481
- cursorCol: number;
482
- } {
483
- if (!isExtendedCompletionItem(item)) {
484
- throw new Error("Expected an extended completion item for one-off bash autocomplete");
485
- }
486
-
487
- return applyExtendedCompletion(lines, cursorLine, item);
488
- }
489
-
490
- shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean {
491
- const bang = cursorLine === 0 ? getOneOffBashCommandContext(lines[0] || "") : null;
492
- return bang !== null && cursorCol >= bang.offset;
493
- }
494
- }
495
-
496
- export class ModeAwareAutocompleteProvider implements AutocompleteProvider {
497
- private readonly defaultProvider: AutocompleteProvider | undefined;
498
- private readonly bashProvider: AutocompleteProvider;
499
- private readonly oneOffBashProvider: AutocompleteProvider;
500
- private readonly isBashModeActive: () => boolean;
501
-
502
- constructor(
503
- defaultProvider: AutocompleteProvider | undefined,
504
- bashProvider: AutocompleteProvider,
505
- oneOffBashProvider: AutocompleteProvider,
506
- isBashModeActive: () => boolean,
507
- ) {
508
- this.defaultProvider = defaultProvider;
509
- this.bashProvider = bashProvider;
510
- this.oneOffBashProvider = oneOffBashProvider;
511
- this.isBashModeActive = isBashModeActive;
512
- }
513
-
514
- /**
515
- * Forward trigger characters from the underlying providers. Without this the
516
- * editor resets its trigger set to the defaults when this wrapper is installed
517
- * (e.g. "$" is dropped), so inline completions like `$skill` never open a popup.
518
- */
519
- get triggerCharacters(): string[] | undefined {
520
- const chars = new Set<string>();
521
- for (const provider of [this.defaultProvider, this.bashProvider, this.oneOffBashProvider]) {
522
- for (const character of provider?.triggerCharacters ?? []) {
523
- chars.add(character);
524
- }
525
- }
526
- return chars.size > 0 ? [...chars] : undefined;
527
- }
528
-
529
- async getSuggestions(
530
- lines: string[],
531
- cursorLine: number,
532
- cursorCol: number,
533
- options: { signal: AbortSignal; force?: boolean },
534
- ): Promise<AutocompleteSuggestions | null> {
535
- if (this.isBashModeActive()) {
536
- return this.bashProvider.getSuggestions(lines, cursorLine, cursorCol, options);
537
- }
538
-
539
- const shouldUseOneOffBash = supportsShouldTriggerFileCompletion(this.oneOffBashProvider)
540
- && this.oneOffBashProvider.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
541
- if (shouldUseOneOffBash) {
542
- return this.oneOffBashProvider.getSuggestions(lines, cursorLine, cursorCol, options);
543
- }
544
-
545
- return this.defaultProvider?.getSuggestions(lines, cursorLine, cursorCol, options) ?? null;
546
- }
547
-
548
- applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string) {
549
- if (this.isBashModeActive()) {
550
- return this.bashProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
551
- }
552
-
553
- const shouldUseOneOffBash = supportsShouldTriggerFileCompletion(this.oneOffBashProvider)
554
- && this.oneOffBashProvider.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
555
- if (shouldUseOneOffBash) {
556
- return this.oneOffBashProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
557
- }
558
-
559
- if (!this.defaultProvider) {
560
- return { lines, cursorLine, cursorCol };
561
- }
562
- return this.defaultProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
563
- }
564
-
565
- shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean {
566
- if (this.isBashModeActive()) {
567
- if (!supportsShouldTriggerFileCompletion(this.bashProvider)) {
568
- return true;
569
- }
570
-
571
- return this.bashProvider.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
572
- }
573
-
574
- const shouldUseOneOffBash = supportsShouldTriggerFileCompletion(this.oneOffBashProvider)
575
- && this.oneOffBashProvider.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
576
- if (shouldUseOneOffBash) {
577
- return true;
578
- }
579
-
580
- if (!this.defaultProvider || !supportsShouldTriggerFileCompletion(this.defaultProvider)) {
581
- return false;
582
- }
583
-
584
- return this.defaultProvider.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
585
- }
586
- }