@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,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
- }