@selesai/code 0.9.15 → 0.10.0

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 (267) hide show
  1. package/CHANGELOG.md +22 -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/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/package.json +3 -3
  193. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  194. package/dist/extensions/auto-model/classifier.ts +0 -23
  195. package/dist/extensions/auto-model/config.ts +0 -51
  196. package/dist/extensions/auto-model/index.ts +0 -22
  197. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  198. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  199. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  200. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  201. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  202. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  203. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  204. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  205. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  206. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  207. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  208. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  209. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  210. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  211. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  212. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  213. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  214. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  215. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  216. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  217. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  218. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  219. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  220. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  221. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  222. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  223. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  224. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  225. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  226. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  227. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  228. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  229. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  230. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  231. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  232. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  233. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  234. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  235. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  236. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  237. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  238. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  239. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  240. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  241. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  242. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  243. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  244. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  245. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  246. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  247. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  248. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  249. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  250. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  251. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  252. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  253. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  254. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  255. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  256. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  257. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  258. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  259. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  260. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  261. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  262. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  263. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  264. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  265. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  266. package/dist/extensions/preview-tools-disabled.ts +0 -11
  267. 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
- }