@selesai/code 0.9.15 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/modes/interactive/components/settings-selector.d.ts +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +1 -1
  119. package/dist/skills/design-references/SKILL.md +31 -0
  120. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  121. package/dist/themes/adventure.json +84 -0
  122. package/dist/themes/adwaita-dark.json +84 -0
  123. package/dist/themes/arcoiris.json +84 -0
  124. package/dist/themes/arthur.json +84 -0
  125. package/dist/themes/atom.json +84 -0
  126. package/dist/themes/aura.json +84 -0
  127. package/dist/themes/black-metal-bathory.json +84 -0
  128. package/dist/themes/black-metal-burzum.json +84 -0
  129. package/dist/themes/black-metal-khold.json +84 -0
  130. package/dist/themes/box.json +84 -0
  131. package/dist/themes/brogrammer.json +84 -0
  132. package/dist/themes/carbonfox.json +84 -0
  133. package/dist/themes/catppuccin-mocha.json +84 -0
  134. package/dist/themes/citruszest.json +84 -0
  135. package/dist/themes/cursor-dark.json +84 -0
  136. package/dist/themes/cutie-pro.json +84 -0
  137. package/dist/themes/dark-modern.json +84 -0
  138. package/dist/themes/dark-pastel.json +84 -0
  139. package/dist/themes/dimmed-monokai.json +84 -0
  140. package/dist/themes/doom-peacock.json +84 -0
  141. package/dist/themes/dracula-plus.json +84 -0
  142. package/dist/themes/dracula.json +80 -0
  143. package/dist/themes/earthsong.json +84 -0
  144. package/dist/themes/everforest-dark-hard.json +84 -0
  145. package/dist/themes/everforest-dark.json +80 -0
  146. package/dist/themes/fahrenheit.json +84 -0
  147. package/dist/themes/flatland.json +84 -0
  148. package/dist/themes/flexoki-dark.json +84 -0
  149. package/dist/themes/front-end-delight.json +84 -0
  150. package/dist/themes/fun-forrest.json +84 -0
  151. package/dist/themes/galizur.json +84 -0
  152. package/dist/themes/github-dark-colorblind.json +84 -0
  153. package/dist/themes/github-dark-high-contrast.json +84 -0
  154. package/dist/themes/glacier.json +84 -0
  155. package/dist/themes/gruber-darker.json +84 -0
  156. package/dist/themes/gruvbox-dark-hard.json +84 -0
  157. package/dist/themes/gruvbox-dark.json +84 -0
  158. package/dist/themes/gruvbox-material.json +84 -0
  159. package/dist/themes/guezwhoz.json +84 -0
  160. package/dist/themes/hacktober.json +84 -0
  161. package/dist/themes/hardcore.json +84 -0
  162. package/dist/themes/havn-skumring.json +84 -0
  163. package/dist/themes/ic-orange-ppl.json +84 -0
  164. package/dist/themes/iterm2-smoooooth.json +84 -0
  165. package/dist/themes/iterm2-tango-dark.json +84 -0
  166. package/dist/themes/japanesque.json +84 -0
  167. package/dist/themes/jellybeans.json +84 -0
  168. package/dist/themes/kanagawa-wave.json +84 -0
  169. package/dist/themes/kurokula.json +84 -0
  170. package/dist/themes/later-this-evening.json +84 -0
  171. package/dist/themes/lovelace.json +84 -0
  172. package/dist/themes/material-darker.json +84 -0
  173. package/dist/themes/matte-black.json +84 -0
  174. package/dist/themes/mellow.json +84 -0
  175. package/dist/themes/miasma.json +84 -0
  176. package/dist/themes/nord.json +80 -0
  177. package/dist/themes/nvim-dark.json +84 -0
  178. package/dist/themes/one-dark.json +80 -0
  179. package/dist/themes/popping-and-locking.json +84 -0
  180. package/dist/themes/rose-pine.json +80 -0
  181. package/dist/themes/sea-shells.json +84 -0
  182. package/dist/themes/sleepy-hollow.json +84 -0
  183. package/dist/themes/smyck.json +84 -0
  184. package/dist/themes/solarized-dark.json +80 -0
  185. package/dist/themes/tokyo-night.json +80 -0
  186. package/dist/themes/tomorrow-night-bright.json +84 -0
  187. package/dist/themes/tomorrow-night-burns.json +84 -0
  188. package/dist/themes/tomorrow-night.json +84 -0
  189. package/dist/themes/twilight.json +84 -0
  190. package/dist/themes/vague.json +84 -0
  191. package/dist/themes/vesper.json +84 -0
  192. package/dist/themes/xcode-dark-hc.json +84 -0
  193. package/docs/custom-provider.md +0 -1
  194. package/package.json +3 -3
  195. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  196. package/dist/extensions/auto-model/classifier.ts +0 -23
  197. package/dist/extensions/auto-model/config.ts +0 -51
  198. package/dist/extensions/auto-model/index.ts +0 -22
  199. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  200. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  201. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  202. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  203. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  204. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  205. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  206. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  207. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  208. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  209. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  210. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  211. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  212. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  213. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  214. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  215. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  216. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  217. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  218. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  219. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  220. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  221. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  222. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  223. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  224. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  225. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  226. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  227. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  228. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  229. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  230. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  231. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  232. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  233. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  234. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  235. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  236. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  237. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  238. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  239. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  240. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  241. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  242. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  243. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  244. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  245. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  246. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  247. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  248. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  249. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  250. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  251. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  252. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  253. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  254. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  255. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  256. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  257. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  258. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  259. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  260. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  261. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  262. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  263. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  264. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  265. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  266. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  267. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  268. package/dist/extensions/preview-tools-disabled.ts +0 -11
  269. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -1,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
- }