@selesai/code 0.9.14 → 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 (263) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +79 -29
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +3 -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/docs/plans/auto-model-routing-extension.md +490 -0
  193. package/package.json +3 -3
  194. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  195. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  196. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  197. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  198. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  199. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  200. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  201. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  202. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  203. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  204. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  205. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  206. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  207. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  208. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  209. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  210. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  211. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  212. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  213. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  214. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  215. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  216. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  217. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  218. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  219. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  220. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  221. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  222. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  223. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  224. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  225. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  226. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  227. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  228. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  229. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  230. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  231. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  232. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  233. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  234. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  235. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  236. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  237. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  238. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  239. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  240. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  241. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  242. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  243. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  244. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  245. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  246. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  247. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  248. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  249. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  250. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  251. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  252. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  253. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  254. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  255. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  256. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  257. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  258. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  259. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  260. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  261. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  262. package/dist/extensions/preview-tools-disabled.ts +0 -11
  263. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -1,583 +0,0 @@
1
- import { fileURLToPath } from "node:url";
2
- import { CustomEditor, type KeybindingsManager } from "@selesai/code";
3
- import { isKeyRelease, matchesKey, visibleWidth, truncateToWidth } from "@earendil-works/pi-tui";
4
- import type { AutocompleteProvider } from "@earendil-works/pi-tui";
5
- import { matchesConfiguredShortcut } from "../shortcuts.ts";
6
- import type { GhostSuggestion } from "./types.ts";
7
-
8
- interface EditorBoundaryShortcuts {
9
- start: string | null;
10
- end: string | null;
11
- }
12
-
13
- interface BashModeEditorOptions {
14
- keybindings: KeybindingsManager;
15
- isBashModeActive: () => boolean;
16
- isShellRunning: () => boolean;
17
- onExitBashMode: () => void;
18
- onSubmitCommand: (command: string) => void;
19
- onEditorSubmit?: () => void;
20
- editorBoundaryShortcuts?: EditorBoundaryShortcuts;
21
- onInterrupt: () => void;
22
- onNotify: (message: string, level?: "info" | "warning" | "error") => void;
23
- getHistoryEntries: (prefix: string) => string[];
24
- areCompletionsEnabled?: () => boolean;
25
- resolveGhostSuggestion: (text: string, signal: AbortSignal) => Promise<GhostSuggestion | null>;
26
- }
27
-
28
- const DEFAULT_EDITOR_BOUNDARY_SHORTCUTS: EditorBoundaryShortcuts = {
29
- start: "super+shift+up",
30
- end: "super+shift+down",
31
- };
32
-
33
- const GHOST_UPDATE_DEBOUNCE_MS = 50;
34
- const FAST_BACKSPACE_COLUMN_THRESHOLD = 1200;
35
-
36
- export function isPrintableInput(data: string): boolean {
37
- if (data.length === 1) {
38
- const code = data.charCodeAt(0);
39
- return code >= 0x20 && (code < 0x7f || code > 0x9f) && (code < 0xd800 || code > 0xdfff);
40
- }
41
- if (data.length !== 2) return false;
42
- const first = data.charCodeAt(0);
43
- const second = data.charCodeAt(1);
44
- return first >= 0xd800 && first <= 0xdbff && second >= 0xdc00 && second <= 0xdfff;
45
- }
46
-
47
- function isCommandUndoShortcut(data: string): boolean {
48
- return data === "\x1b[122;9u"
49
- || data === "\x1b[122;9:1u"
50
- || data === "\x1b[122;9:2u"
51
- || data === "\x1b[27;9;122~";
52
- }
53
-
54
- function bracketedPasteContent(data: string): string | null {
55
- const startMarker = "\x1b[200~";
56
- const endMarker = "\x1b[201~";
57
- const start = data.indexOf(startMarker);
58
- if (start !== 0) return null;
59
-
60
- const end = data.indexOf(endMarker, startMarker.length);
61
- if (end === -1 || end + endMarker.length !== data.length) return null;
62
-
63
- return data.slice(startMarker.length, end);
64
- }
65
-
66
- function decodeFileUriList(text: string): string | null {
67
- const entries = text
68
- .split(/\r?\n|\s+/)
69
- .map((entry) => entry.trim())
70
- .filter((entry) => entry.length > 0 && !entry.startsWith("#"));
71
-
72
- if (entries.length === 0 || entries.some((entry) => !entry.startsWith("file://"))) {
73
- return null;
74
- }
75
-
76
- try {
77
- return entries.map((entry) => fileURLToPath(entry)).join(" ");
78
- } catch {
79
- return null;
80
- }
81
- }
82
-
83
- function droppedPathTextFromInput(data: string): string | null {
84
- const pasteContent = bracketedPasteContent(data);
85
- const text = pasteContent ?? data;
86
- const uriList = decodeFileUriList(text);
87
- if (uriList) return uriList;
88
-
89
- const trimmed = text.replace(/^[\r\n]+|[\r\n]+$/g, "");
90
- if (trimmed.length <= 1 || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(trimmed)) {
91
- return null;
92
- }
93
-
94
- if (/^(?:\/|~\/|\.\.?\/)/.test(trimmed) && !/[\r\n]/.test(trimmed)) {
95
- return trimmed;
96
- }
97
-
98
- return null;
99
- }
100
-
101
- function resetShellHistoryBrowse(state: object): void {
102
- Reflect.set(state, "shellHistoryIndex", -1);
103
- Reflect.set(state, "shellHistoryItems", []);
104
- Reflect.set(state, "shellHistoryDraft", "");
105
- }
106
-
107
- export class BashModeEditor extends CustomEditor {
108
- private readonly keybindingsRef: KeybindingsManager;
109
- private readonly optionsRef: BashModeEditorOptions;
110
- private wrappedProviderInstalled = false;
111
- private shellHistoryIndex = -1;
112
- private shellHistoryItems: string[] = [];
113
- private shellHistoryDraft = "";
114
- private promptHistoryDraft: string | null = null;
115
- private ghost: GhostSuggestion | null = null;
116
- private ghostAbort: AbortController | null = null;
117
- private ghostTimer: ReturnType<typeof setTimeout> | null = null;
118
- private ghostToken = 0;
119
- private plainBoundInputs: Set<string> | null = null;
120
- private readonly backspaceBindingConflicts = new Map<string, boolean>();
121
-
122
- constructor(tui: any, theme: any, keybindings: KeybindingsManager, options: BashModeEditorOptions) {
123
- super(tui, theme, keybindings);
124
- this.keybindingsRef = keybindings;
125
- this.optionsRef = options;
126
- }
127
-
128
- setAutocompleteProvider(provider: AutocompleteProvider): void {
129
- super.setAutocompleteProvider(provider);
130
- this.wrappedProviderInstalled = false;
131
- }
132
-
133
- installAutocompleteProvider(provider: AutocompleteProvider): void {
134
- this.setAutocompleteProvider(provider);
135
- this.wrappedProviderInstalled = true;
136
- }
137
-
138
- hasWrappedProvider(): boolean {
139
- return this.wrappedProviderInstalled;
140
- }
141
-
142
- getGhostSuggestion(): GhostSuggestion | null {
143
- return this.isShellCompletionContext() ? this.ghost : null;
144
- }
145
-
146
- refreshGhostSuggestion(): void {
147
- if (this.areCompletionsEnabled()) {
148
- this.scheduleGhostUpdate();
149
- } else {
150
- this.clearGhostSuggestion();
151
- }
152
- }
153
-
154
- clearGhostSuggestion(): void {
155
- if (this.ghostTimer) clearTimeout(this.ghostTimer);
156
- this.ghostTimer = null;
157
- this.ghostAbort?.abort();
158
- this.ghostAbort = null;
159
- this.ghostToken += 1;
160
- this.ghost = null;
161
- }
162
-
163
- dismissBashModeUi(): void {
164
- resetShellHistoryBrowse(this);
165
- this.clearGhostSuggestion();
166
-
167
- const cancelAutocomplete = Reflect.get(this, "cancelAutocomplete");
168
- if (typeof cancelAutocomplete === "function") {
169
- cancelAutocomplete.call(this);
170
- }
171
- this.tui.requestRender();
172
- }
173
-
174
- handleInput(data: string): void {
175
- if (BashModeEditor.prototype.tryFastPrintableInput.call(this, data)) return;
176
- if (BashModeEditor.prototype.tryFastAsciiBackspace.call(this, data)) return;
177
-
178
- const droppedPathText = droppedPathTextFromInput(data);
179
- if (droppedPathText !== null) {
180
- this.insertTextAtCursor(droppedPathText);
181
- resetShellHistoryBrowse(this);
182
- if (this.isShellCompletionContext()) {
183
- this.scheduleGhostUpdate();
184
- } else {
185
- this.clearGhostSuggestion();
186
- }
187
- return;
188
- }
189
-
190
- const pasteInProgress = data.includes("\x1b[200~") || Reflect.get(this, "isInPaste") === true;
191
- if (pasteInProgress) {
192
- super.handleInput(data);
193
- if (Reflect.get(this, "isInPaste") === true) {
194
- return;
195
- }
196
- } else {
197
- const bashMode = this.optionsRef.isBashModeActive();
198
- const oneOffBashCommand = !bashMode && this.isOneOffBashCommandContext();
199
-
200
- if (isCommandUndoShortcut(data)) {
201
- const undo = Reflect.get(this, "undo");
202
- if (typeof undo === "function") {
203
- undo.call(this);
204
- }
205
- resetShellHistoryBrowse(this);
206
- if (this.isShellCompletionContext()) {
207
- this.scheduleGhostUpdate();
208
- } else {
209
- this.clearGhostSuggestion();
210
- }
211
- return;
212
- }
213
-
214
- if (bashMode && this.keybindingsRef.matches(data, "app.interrupt")) {
215
- this.optionsRef.onExitBashMode();
216
- return;
217
- }
218
-
219
- if (bashMode && this.keybindingsRef.matches(data, "app.clear") && this.optionsRef.isShellRunning()) {
220
- this.optionsRef.onInterrupt();
221
- return;
222
- }
223
-
224
- if (bashMode && this.keybindingsRef.matches(data, "tui.editor.cursorUp")) {
225
- this.navigateShellHistory(-1);
226
- return;
227
- }
228
-
229
- if (bashMode && this.keybindingsRef.matches(data, "tui.editor.cursorDown")) {
230
- this.navigateShellHistory(1);
231
- return;
232
- }
233
-
234
- const editorBoundaryShortcuts = this.optionsRef.editorBoundaryShortcuts ?? DEFAULT_EDITOR_BOUNDARY_SHORTCUTS;
235
- if (!isKeyRelease(data) && matchesConfiguredShortcut(data, editorBoundaryShortcuts.start)) {
236
- this.moveCursorToEditorBoundary("start");
237
- return;
238
- }
239
-
240
- if (!isKeyRelease(data) && matchesConfiguredShortcut(data, editorBoundaryShortcuts.end)) {
241
- this.moveCursorToEditorBoundary("end");
242
- return;
243
- }
244
-
245
- if ((bashMode || oneOffBashCommand) && this.keybindingsRef.matches(data, "tui.input.tab")) {
246
- this.acceptGhostSuggestion();
247
- return;
248
- }
249
-
250
- if (
251
- (bashMode || oneOffBashCommand)
252
- && this.keybindingsRef.matches(data, "tui.editor.cursorRight")
253
- && this.acceptGhostSuggestion()
254
- ) {
255
- return;
256
- }
257
-
258
- if (!bashMode && matchesKey(data, "up") && this.isPromptHistoryRecallPosition()) {
259
- const navigateHistory = Reflect.get(this, "navigateHistory");
260
- if (typeof navigateHistory === "function") {
261
- if (Reflect.get(this, "historyIndex") === -1) {
262
- this.promptHistoryDraft = this.getText();
263
- }
264
- navigateHistory.call(this, -1);
265
- return;
266
- }
267
- }
268
-
269
- if (!bashMode && matchesKey(data, "down") && Reflect.get(this, "historyIndex") > -1) {
270
- const isOnLastVisualLine = Reflect.get(this, "isOnLastVisualLine");
271
- if (typeof isOnLastVisualLine !== "function" || isOnLastVisualLine.call(this)) {
272
- const navigateHistory = Reflect.get(this, "navigateHistory");
273
- if (typeof navigateHistory === "function") {
274
- navigateHistory.call(this, 1);
275
- if (Reflect.get(this, "historyIndex") === -1 && this.promptHistoryDraft !== null) {
276
- const draft = this.promptHistoryDraft;
277
- this.promptHistoryDraft = null;
278
- const setTextInternal = Reflect.get(this, "setTextInternal");
279
- if (typeof setTextInternal === "function") {
280
- setTextInternal.call(this, draft);
281
- } else {
282
- this.setText(draft);
283
- }
284
- }
285
- return;
286
- }
287
- }
288
- }
289
-
290
- if (bashMode && this.keybindingsRef.matches(data, "tui.input.submit") && !this.keybindingsRef.matches(data, "tui.input.newLine")) {
291
- if (this.optionsRef.isShellRunning()) {
292
- this.optionsRef.onNotify("Shell command already running", "warning");
293
- return;
294
- }
295
-
296
- const command = this.getExpandedText().trim();
297
- if (!command) return;
298
- this.clearGhostSuggestion();
299
- resetShellHistoryBrowse(this);
300
- this.optionsRef.onEditorSubmit?.();
301
- this.optionsRef.onSubmitCommand(command);
302
- this.setText("");
303
- this.refreshGhostSuggestion();
304
- return;
305
- }
306
-
307
- super.handleInput(data);
308
- }
309
-
310
- if (!this.isShellCompletionContext()) {
311
- resetShellHistoryBrowse(this);
312
- this.clearGhostSuggestion();
313
- return;
314
- }
315
-
316
- if (
317
- pasteInProgress
318
- ||
319
- isPrintableInput(data)
320
- || this.keybindingsRef.matches(data, "tui.editor.deleteCharBackward")
321
- || this.keybindingsRef.matches(data, "tui.editor.deleteCharForward")
322
- || this.keybindingsRef.matches(data, "tui.editor.deleteWordBackward")
323
- || this.keybindingsRef.matches(data, "tui.editor.deleteWordForward")
324
- || this.keybindingsRef.matches(data, "tui.editor.deleteToLineStart")
325
- || this.keybindingsRef.matches(data, "tui.editor.deleteToLineEnd")
326
- || this.keybindingsRef.matches(data, "tui.input.newLine")
327
- || this.keybindingsRef.matches(data, "tui.editor.cursorLeft")
328
- || this.keybindingsRef.matches(data, "tui.editor.cursorRight")
329
- ) {
330
- resetShellHistoryBrowse(this);
331
- this.scheduleGhostUpdate();
332
- }
333
- }
334
-
335
- private tryFastPrintableInput(data: string): boolean {
336
- if (!isPrintableInput(data)) return false;
337
- if (Reflect.get(this, "isInPaste") === true || Reflect.get(this, "jumpMode") !== null) return false;
338
-
339
- if (!this.plainBoundInputs) {
340
- this.plainBoundInputs = new Set<string>();
341
- for (const binding of Object.values(this.keybindingsRef.getEffectiveConfig())) {
342
- if (!binding) continue;
343
- for (const key of Array.isArray(binding) ? binding : [binding]) {
344
- if (isPrintableInput(key)) this.plainBoundInputs.add(key);
345
- if (key === "space") this.plainBoundInputs.add(" ");
346
- if (/^shift\+[a-z]$/.test(key)) this.plainBoundInputs.add(key.slice(-1).toUpperCase());
347
- }
348
- }
349
- }
350
- if (this.plainBoundInputs.has(data)) return false;
351
- if (this.onExtensionShortcut?.(data)) return true;
352
-
353
- const insertCharacter = Reflect.get(this, "insertCharacter");
354
- if (typeof insertCharacter !== "function") return false;
355
- insertCharacter.call(this, data);
356
-
357
- resetShellHistoryBrowse(this);
358
- if (this.isShellCompletionContext()) {
359
- this.scheduleGhostUpdate();
360
- } else {
361
- this.clearGhostSuggestion();
362
- }
363
- return true;
364
- }
365
-
366
- private tryFastAsciiBackspace(data: string): boolean {
367
- if (!this.keybindingsRef.matches(data, "tui.editor.deleteCharBackward")) return false;
368
- let hasBindingConflict = this.backspaceBindingConflicts.get(data);
369
- if (hasBindingConflict === undefined) {
370
- hasBindingConflict = Object.entries(this.keybindingsRef.getEffectiveConfig()).some(([id, binding]) => {
371
- if (id === "tui.editor.deleteCharBackward" || !binding) return false;
372
- return (Array.isArray(binding) ? binding : [binding]).some((key) => matchesKey(data, key));
373
- });
374
- this.backspaceBindingConflicts.set(data, hasBindingConflict);
375
- }
376
- if (hasBindingConflict) return false;
377
- if (Reflect.get(this, "isInPaste") === true || Reflect.get(this, "jumpMode") !== null) return false;
378
- if (Reflect.get(this, "autocompleteState") !== null) return false;
379
-
380
- const state = Reflect.get(this, "state");
381
- const lines = state && typeof state === "object" ? Reflect.get(state, "lines") : null;
382
- const cursorLine = state && typeof state === "object" ? Reflect.get(state, "cursorLine") : null;
383
- const cursorCol = state && typeof state === "object" ? Reflect.get(state, "cursorCol") : null;
384
- if (!Array.isArray(lines) || lines.length !== 1 || cursorLine !== 0 || typeof cursorCol !== "number") return false;
385
-
386
- const line = lines[0];
387
- if (typeof line !== "string" || cursorCol < FAST_BACKSPACE_COLUMN_THRESHOLD || cursorCol !== line.length) return false;
388
- const previousCode = line.charCodeAt(cursorCol - 2);
389
- const deletedCode = line.charCodeAt(cursorCol - 1);
390
- if (previousCode < 0x20 || previousCode > 0x7e || deletedCode < 0x20 || deletedCode > 0x7e) return false;
391
-
392
- const pastes = Reflect.get(this as object, "pastes");
393
- if (pastes instanceof Map && pastes.size) return false;
394
-
395
- const nextLine = line.slice(0, -1);
396
- const isInSlashCommandContext = Reflect.get(this, "isInSlashCommandContext");
397
- if (typeof isInSlashCommandContext === "function" && isInSlashCommandContext.call(this, nextLine)) return false;
398
- const autocompleteTriggerPattern = Reflect.get(this as object, "autocompleteTriggerPattern");
399
- if (autocompleteTriggerPattern instanceof RegExp && autocompleteTriggerPattern.test(nextLine)) return false;
400
-
401
- const exitHistoryBrowsing = Reflect.get(this, "exitHistoryBrowsing");
402
- const pushUndoSnapshot = Reflect.get(this, "pushUndoSnapshot");
403
- const setCursorCol = Reflect.get(this, "setCursorCol");
404
- if (typeof exitHistoryBrowsing !== "function" || typeof pushUndoSnapshot !== "function" || typeof setCursorCol !== "function") {
405
- return false;
406
- }
407
- if (this.onExtensionShortcut?.(data)) return true;
408
-
409
- exitHistoryBrowsing.call(this);
410
- pushUndoSnapshot.call(this);
411
- Reflect.set(this, "lastAction", null);
412
- lines[0] = nextLine;
413
- setCursorCol.call(this, cursorCol - 1);
414
- this.onChange?.(nextLine);
415
-
416
- resetShellHistoryBrowse(this);
417
- if (this.isShellCompletionContext()) {
418
- this.scheduleGhostUpdate();
419
- } else {
420
- this.clearGhostSuggestion();
421
- }
422
- return true;
423
- }
424
-
425
- render(width: number): string[] {
426
- const lines = super.render(width);
427
- if (!this.isShellCompletionContext()) return lines;
428
- if (!this.ghost) return lines;
429
-
430
- const text = this.getText();
431
- if (text.includes("\n")) return lines;
432
- const cursor = this.getCursor();
433
- if (cursor.line !== 0 || cursor.col !== text.length) return lines;
434
- if (!this.ghost.value.startsWith(text) || this.ghost.value === text) return lines;
435
- if (lines.length < 3) return lines;
436
-
437
- const suffix = this.ghost.value.slice(text.length);
438
- const contentLine = 1;
439
- const cursorBlock = "\x1b[7m \x1b[0m";
440
- const availableWidth = Math.max(0, width - visibleWidth(text) - 1);
441
- if (availableWidth === 0) return lines;
442
-
443
- const shownSuffix = truncateToWidth(suffix, availableWidth, "", true);
444
- if (!shownSuffix) return lines;
445
-
446
- const padding = " ".repeat(Math.max(0, width - visibleWidth(text) - 1 - visibleWidth(shownSuffix)));
447
- const ghost = `\x1b[38;5;244m${shownSuffix}\x1b[0m`;
448
- lines[contentLine] = `${text}${cursorBlock}${ghost}${padding}`;
449
- return lines;
450
- }
451
-
452
- private isShellCompletionContext(): boolean {
453
- return this.areCompletionsEnabled()
454
- && (this.optionsRef.isBashModeActive() || this.isOneOffBashCommandContext());
455
- }
456
-
457
- private areCompletionsEnabled(): boolean {
458
- return this.optionsRef.areCompletionsEnabled?.() ?? true;
459
- }
460
-
461
- private isOneOffBashCommandContext(): boolean {
462
- const state = Reflect.get(this, "state");
463
- const lines = state && typeof state === "object" ? Reflect.get(state, "lines") : null;
464
- return Array.isArray(lines) && typeof lines[0] === "string" && lines[0].startsWith("!");
465
- }
466
-
467
- private moveCursorToEditorBoundary(position: "start" | "end"): void {
468
- const state = Reflect.get(this, "state");
469
- const lines = state && typeof state === "object" ? Reflect.get(state, "lines") : null;
470
- if (!Array.isArray(lines)) {
471
- throw new Error("Editor cursor state is unavailable");
472
- }
473
-
474
- if (position === "start") {
475
- Reflect.set(state, "cursorLine", 0);
476
- Reflect.set(state, "cursorCol", 0);
477
- } else {
478
- const lastLine = Math.max(0, lines.length - 1);
479
- Reflect.set(state, "cursorLine", lastLine);
480
- Reflect.set(state, "cursorCol", typeof lines[lastLine] === "string" ? lines[lastLine].length : 0);
481
- }
482
-
483
- Reflect.set(this, "lastAction", null);
484
- Reflect.set(this, "preferredVisualCol", null);
485
- Reflect.set(this, "snappedFromCursorCol", null);
486
- this.tui.requestRender();
487
- }
488
-
489
- private acceptGhostSuggestion(): boolean {
490
- if (!this.ghost) return false;
491
- const text = this.getText();
492
- if (text.includes("\n")) return false;
493
-
494
- const cursor = this.getCursor();
495
- if (cursor.line !== 0 || cursor.col !== text.length) return false;
496
-
497
- if (!this.ghost.value.startsWith(text) || this.ghost.value === text) return false;
498
- this.setText(this.ghost.value);
499
- this.clearGhostSuggestion();
500
- return true;
501
- }
502
-
503
- private isPromptHistoryRecallPosition(): boolean {
504
- if (this.isShowingAutocomplete()) return false;
505
-
506
- const history = Reflect.get(this, "history");
507
- if (!Array.isArray(history) || history.length === 0) return false;
508
-
509
- const lines = this.getLines();
510
- const cursor = this.getCursor();
511
- if (lines.length === 1) {
512
- return cursor.line === 0 && cursor.col === (lines[0]?.length ?? 0);
513
- }
514
-
515
- const isOnFirstVisualLine = Reflect.get(this, "isOnFirstVisualLine");
516
- if (typeof isOnFirstVisualLine === "function" && !isOnFirstVisualLine.call(this)) {
517
- return false;
518
- }
519
-
520
- return cursor.line === 0;
521
- }
522
-
523
- private navigateShellHistory(direction: -1 | 1): void {
524
- const prefix = this.shellHistoryDraft || this.getExpandedText();
525
- if (this.shellHistoryIndex === -1) {
526
- this.shellHistoryDraft = prefix;
527
- this.shellHistoryItems = this.optionsRef.getHistoryEntries(prefix);
528
- }
529
-
530
- if (this.shellHistoryItems.length === 0) {
531
- this.optionsRef.onNotify("No shell history matches", "info");
532
- return;
533
- }
534
-
535
- if (direction < 0) {
536
- this.shellHistoryIndex = Math.min(this.shellHistoryItems.length - 1, this.shellHistoryIndex + 1);
537
- this.setText(this.shellHistoryItems[this.shellHistoryIndex] ?? this.shellHistoryDraft);
538
- this.clearGhostSuggestion();
539
- return;
540
- }
541
-
542
- this.shellHistoryIndex -= 1;
543
- if (this.shellHistoryIndex < 0) {
544
- this.shellHistoryIndex = -1;
545
- this.setText(this.shellHistoryDraft);
546
- this.scheduleGhostUpdate();
547
- return;
548
- }
549
-
550
- this.setText(this.shellHistoryItems[this.shellHistoryIndex] ?? this.shellHistoryDraft);
551
- this.clearGhostSuggestion();
552
- }
553
-
554
- private scheduleGhostUpdate(): void {
555
- if (!this.areCompletionsEnabled()) {
556
- this.clearGhostSuggestion();
557
- return;
558
- }
559
-
560
- const text = this.getText();
561
- const currentToken = ++this.ghostToken;
562
- if (this.ghostTimer) clearTimeout(this.ghostTimer);
563
- this.ghostAbort?.abort();
564
-
565
- const controller = new AbortController();
566
- this.ghostAbort = controller;
567
- this.ghostTimer = setTimeout(() => {
568
- this.ghostTimer = null;
569
- if (controller.signal.aborted || currentToken !== this.ghostToken) return;
570
-
571
- this.optionsRef.resolveGhostSuggestion(text, controller.signal)
572
- .then((ghost) => {
573
- if (controller.signal.aborted || currentToken !== this.ghostToken) return;
574
- this.ghost = ghost;
575
- this.tui.requestRender();
576
- })
577
- .catch((error) => {
578
- if (controller.signal.aborted) return;
579
- console.debug("[powerline-footer] Failed to resolve bash ghost suggestion:", error);
580
- });
581
- }, GHOST_UPDATE_DEBOUNCE_MS);
582
- }
583
- }