@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
@@ -0,0 +1,1290 @@
1
+ import { CustomEditor, type KeybindingsManager, type Theme } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ type AutocompleteProvider,
4
+ type Component,
5
+ type EditorComponent,
6
+ type EditorTheme,
7
+ type TUI,
8
+ truncateToWidth,
9
+ visibleWidth,
10
+ } from "@earendil-works/pi-tui";
11
+ import { ACCENT_RAIL_CHROME_WIDTH, renderAccentRailEditorFrame } from "./accent-rail-editor";
12
+ import { renderCompletionPalette } from "./completion-menu";
13
+ import type { EditorStyle, ZentuiConfig } from "./config";
14
+ import {
15
+ type EditorMetadataZones,
16
+ renderEditorMetadataFormatSplit,
17
+ } from "./editor-metadata-format";
18
+ import { type MinimalistEditorMetadata, renderMinimalistFrame } from "./minimalist-editor";
19
+ import {
20
+ EDITOR_ACCENT_FALLBACK,
21
+ EDITOR_BORDER_FALLBACK,
22
+ renderStyleForSourceOrFallback,
23
+ safeThemeFg,
24
+ } from "./style";
25
+
26
+ const LEGACY_SPLIT_POLISHED_FRAME = Symbol.for("pi-zentui.polished-frame");
27
+
28
+ export type ViewportCounts = {
29
+ above?: string;
30
+ below?: string;
31
+ };
32
+
33
+ type PolishedFrameSplit = {
34
+ editorLines: string[];
35
+ /** Logical, unframed autocomplete payload rows owned by this module. */
36
+ trailingLines: string[];
37
+ viewport: ViewportCounts;
38
+ };
39
+
40
+ type PolishedFrameProvenance = {
41
+ rows: readonly string[];
42
+ split: PolishedFrameSplit;
43
+ };
44
+
45
+ const POLISHED_FRAME_SPLITS = new WeakMap<string[], PolishedFrameProvenance>();
46
+
47
+ type AutocompleteListInternals = Pick<Component, "render">;
48
+
49
+ type AutocompleteEditorInternals = {
50
+ autocompleteList?: AutocompleteListInternals;
51
+ isShowingAutocomplete?: () => boolean;
52
+ };
53
+
54
+ type AutocompleteCapture = {
55
+ compatible: boolean;
56
+ called: number;
57
+ rows: string[];
58
+ };
59
+
60
+ type WrappedEditor = EditorComponent &
61
+ AutocompleteEditorInternals & {
62
+ focused?: boolean;
63
+ onEscape?: () => void;
64
+ onCtrlD?: () => void;
65
+ onPasteImage?: () => void;
66
+ onExtensionShortcut?: (data: string) => boolean;
67
+ actionHandlers?: Map<unknown, () => void>;
68
+ wantsKeyRelease?: boolean;
69
+ disableSubmit?: boolean;
70
+ getLines?: () => string[];
71
+ getCursor?: () => unknown;
72
+ getMode?: () => unknown;
73
+ getPaddingX?: () => number;
74
+ getAutocompleteMaxVisible?: () => number;
75
+ addToHistory?: (text: string) => void;
76
+ getExpandedText?: () => string;
77
+ insertTextAtCursor?: (text: string) => void;
78
+ setAutocompleteProvider?: (provider: AutocompleteProvider) => void;
79
+ setPaddingX?: (padding: number) => void;
80
+ setAutocompleteMaxVisible?: (maxVisible: number) => void;
81
+ };
82
+
83
+ export type EditorMeta = {
84
+ modelLabel: string;
85
+ modelId?: string;
86
+ modelName?: string;
87
+ providerLabel: string;
88
+ sessionName?: string;
89
+ contextPercent?: number;
90
+ contextWindow?: number;
91
+ inputTokens?: number;
92
+ outputTokens?: number;
93
+ cacheHitRate?: number;
94
+ };
95
+
96
+ export type PolishedEditorFrameOptions = {
97
+ width: number;
98
+ editorLines: string[];
99
+ autocompleteLines?: string[];
100
+ viewport?: ViewportCounts;
101
+ uiTheme: Theme;
102
+ config: ZentuiConfig;
103
+ modelMeta: EditorMeta;
104
+ thinkingLevel?: string;
105
+ rightStatus?: string;
106
+ borderColor?: (text: string) => string;
107
+ };
108
+
109
+ type PolishedFrameOptions = {
110
+ width: number;
111
+ baseRendered: string[];
112
+ autocompleteSource: AutocompleteEditorInternals;
113
+ autocompleteCapture?: AutocompleteCapture;
114
+ uiTheme: Theme;
115
+ config: ZentuiConfig;
116
+ modelMeta: EditorMeta;
117
+ thinkingLevel: string | undefined;
118
+ rightStatus?: string;
119
+ ownedFrame?: PolishedFrameSplit;
120
+ trustedBaseFrame?: boolean;
121
+ borderColor?: (text: string) => string;
122
+ };
123
+
124
+ type PolishedFrameResult = {
125
+ lines: string[];
126
+ decorated: boolean;
127
+ };
128
+
129
+ type AccentRailFrameAdapterOptions = {
130
+ width: number;
131
+ baseRendered: string[];
132
+ autocompleteSource: AutocompleteEditorInternals;
133
+ autocompleteCapture?: AutocompleteCapture;
134
+ uiTheme: Theme;
135
+ config: ZentuiConfig;
136
+ ownedFrame?: PolishedFrameSplit;
137
+ trustedBaseFrame?: boolean;
138
+ };
139
+
140
+ type MinimalistFrameAdapterOptions = {
141
+ width: number;
142
+ baseRendered: string[];
143
+ autocompleteSource: AutocompleteEditorInternals;
144
+ autocompleteCapture?: AutocompleteCapture;
145
+ uiTheme: Theme;
146
+ config: ZentuiConfig;
147
+ inputText: string;
148
+ metadata: MinimalistEditorMetadata;
149
+ ownedFrame?: PolishedFrameSplit;
150
+ trustedBaseFrame?: boolean;
151
+ borderColor?: (text: string) => string;
152
+ };
153
+
154
+ type AutocompleteCount = { known: true; count: number } | { known: false };
155
+
156
+ function isStringArray(value: unknown): value is string[] {
157
+ return Array.isArray(value) && value.every((line) => typeof line === "string");
158
+ }
159
+
160
+ function isViewportCounts(value: unknown): value is ViewportCounts {
161
+ if (!value || typeof value !== "object") return false;
162
+ const counts = value as Record<string, unknown>;
163
+ return [counts.above, counts.below].every(
164
+ (count) => count === undefined || (typeof count === "string" && /^[1-9]\d*$/.test(count)),
165
+ );
166
+ }
167
+
168
+ function isPolishedFrameSplit(value: unknown, baseLineCount: number): value is PolishedFrameSplit {
169
+ if (!value || typeof value !== "object") return false;
170
+ const split = value as Record<string, unknown>;
171
+ return (
172
+ isStringArray(split.editorLines) &&
173
+ isStringArray(split.trailingLines) &&
174
+ split.trailingLines.length <= baseLineCount &&
175
+ isViewportCounts(split.viewport)
176
+ );
177
+ }
178
+
179
+ function stripNativeRightPadding(value: string): string {
180
+ return value.replace(/ +$/, "");
181
+ }
182
+
183
+ function autocompleteCount(
184
+ source: AutocompleteEditorInternals,
185
+ capture: AutocompleteCapture | undefined,
186
+ baseRendered: string[],
187
+ ): AutocompleteCount {
188
+ try {
189
+ const showing = source.isShowingAutocomplete;
190
+ if (typeof showing !== "function" || !showing.call(source)) return { known: true, count: 0 };
191
+ if (
192
+ !capture?.compatible ||
193
+ capture.called !== 1 ||
194
+ capture.rows.length <= 0 ||
195
+ capture.rows.length >= baseRendered.length
196
+ )
197
+ return { known: false };
198
+ const suffix = baseRendered.slice(-capture.rows.length);
199
+ if (
200
+ !suffix.every((line, index) => {
201
+ const captured = capture.rows[index];
202
+ return (
203
+ captured !== undefined &&
204
+ (line === captured || stripNativeRightPadding(line) === stripNativeRightPadding(captured))
205
+ );
206
+ })
207
+ )
208
+ return { known: false };
209
+ return { known: true, count: capture.rows.length };
210
+ } catch {
211
+ return { known: false };
212
+ }
213
+ }
214
+
215
+ /** @internal Exported only for descriptor-safety regression tests. */
216
+ export function renderWithAutocompleteCapture<T>(
217
+ source: AutocompleteEditorInternals,
218
+ render: () => T,
219
+ ): { value: T; capture?: AutocompleteCapture } {
220
+ let showing = false;
221
+ try {
222
+ showing =
223
+ typeof source.isShowingAutocomplete === "function" &&
224
+ source.isShowingAutocomplete.call(source);
225
+ } catch {
226
+ return { value: render() };
227
+ }
228
+ if (!showing) return { value: render(), capture: { compatible: true, called: 0, rows: [] } };
229
+
230
+ let list: AutocompleteListInternals;
231
+ let own: PropertyDescriptor | undefined;
232
+ let predecessor: (...args: unknown[]) => unknown;
233
+ try {
234
+ const candidate = source.autocompleteList;
235
+ if (!candidate) return { value: render() };
236
+ own = Object.getOwnPropertyDescriptor(candidate, "render");
237
+ const current = Reflect.get(candidate, "render");
238
+ if (typeof current !== "function") return { value: render() };
239
+ if (own && (!("value" in own) || own.writable !== true)) return { value: render() };
240
+ if (!own && !Object.isExtensible(candidate)) return { value: render() };
241
+ list = candidate;
242
+ predecessor = current as (...args: unknown[]) => unknown;
243
+ } catch {
244
+ return { value: render() };
245
+ }
246
+
247
+ const capture: AutocompleteCapture = { compatible: true, called: 0, rows: [] };
248
+ const wrapper = function (this: AutocompleteListInternals, ...args: unknown[]) {
249
+ const result = Reflect.apply(predecessor, this, args);
250
+ capture.called++;
251
+ if (!isStringArray(result)) {
252
+ capture.compatible = false;
253
+ return result;
254
+ }
255
+ capture.rows = [...result];
256
+ return result;
257
+ };
258
+ const installedDescriptor: PropertyDescriptor = {
259
+ ...(own ?? { configurable: true, enumerable: false, writable: true }),
260
+ value: wrapper,
261
+ };
262
+ try {
263
+ Object.defineProperty(list, "render", installedDescriptor);
264
+ } catch {
265
+ return { value: render() };
266
+ }
267
+
268
+ try {
269
+ return { value: render(), capture };
270
+ } finally {
271
+ let current: PropertyDescriptor | undefined;
272
+ let currentValue: unknown;
273
+ let descriptorKnown = true;
274
+ try {
275
+ current = Object.getOwnPropertyDescriptor(list, "render");
276
+ currentValue = current && "value" in current ? current.value : Reflect.get(list, "render");
277
+ } catch {
278
+ descriptorKnown = false;
279
+ try {
280
+ currentValue = Reflect.get(list, "render");
281
+ } catch {
282
+ currentValue = undefined;
283
+ }
284
+ }
285
+ if (currentValue === wrapper) {
286
+ if (
287
+ !descriptorKnown ||
288
+ !current ||
289
+ current.configurable !== installedDescriptor.configurable ||
290
+ current.enumerable !== installedDescriptor.enumerable ||
291
+ current.writable !== installedDescriptor.writable
292
+ )
293
+ capture.compatible = false;
294
+ try {
295
+ if (own) Object.defineProperty(list, "render", own);
296
+ else if (!Reflect.deleteProperty(list, "render")) capture.compatible = false;
297
+ } catch {
298
+ capture.compatible = false;
299
+ }
300
+ } else {
301
+ // A synchronous third-party replacement wins; captured semantics are no longer trustworthy.
302
+ capture.compatible = false;
303
+ }
304
+ }
305
+ }
306
+
307
+ function clampRenderedLines(lines: string[], width: number): string[] {
308
+ const maxWidth = Math.max(0, width);
309
+ return lines.map((line) => truncateToWidth(line, maxWidth, ""));
310
+ }
311
+
312
+ function fillLine(content: string, width: number): string {
313
+ const truncated = truncateToWidth(content, Math.max(0, width), "");
314
+ const pad = " ".repeat(Math.max(0, width - visibleWidth(truncated)));
315
+ return `${truncated}${pad}`;
316
+ }
317
+
318
+ function isLowRailPolishedStyle(style: EditorStyle): boolean {
319
+ return style === "opencode-copy-friendly";
320
+ }
321
+
322
+ function selectedPolishedConfig(config: ZentuiConfig) {
323
+ switch (config.components.editor.style) {
324
+ case "opencode":
325
+ return config.components.editor.styles.opencode;
326
+ case "opencode-copy-friendly":
327
+ return config.components.editor.styles["opencode-copy-friendly"];
328
+ case "accent-rail":
329
+ case "minimalist":
330
+ return undefined;
331
+ }
332
+ }
333
+
334
+ function lowRailPrompt(config: ZentuiConfig, uiTheme: Theme, reset: string): string {
335
+ const promptIcon = config.icons.editorPrompt;
336
+ return promptIcon
337
+ ? `${renderStyleForSourceOrFallback(
338
+ uiTheme,
339
+ config.components.editor.colorSource,
340
+ config.colors.editorPrompt ?? config.colors.editorAccent,
341
+ EDITOR_ACCENT_FALLBACK,
342
+ promptIcon,
343
+ )}${reset} `
344
+ : "";
345
+ }
346
+
347
+ function getEditorChromeWidths(config: ZentuiConfig, uiTheme: Theme, reset: string) {
348
+ const lowRail = isLowRailPolishedStyle(config.components.editor.style);
349
+ const prompt = lowRailPrompt(config, uiTheme, reset);
350
+ const rail = lowRail
351
+ ? ""
352
+ : `${renderStyleForSourceOrFallback(
353
+ uiTheme,
354
+ config.components.editor.colorSource,
355
+ config.colors.editorAccent,
356
+ EDITOR_ACCENT_FALLBACK,
357
+ config.icons.rail,
358
+ )}${reset} `;
359
+ return {
360
+ prompt,
361
+ promptWidth: visibleWidth(prompt),
362
+ rail,
363
+ railWidth: lowRail ? visibleWidth(prompt) : visibleWidth(rail),
364
+ };
365
+ }
366
+
367
+ export function composeEditorMetadataLine(
368
+ { left, middle, right }: EditorMetadataZones,
369
+ rightStatus: string | undefined,
370
+ width: number,
371
+ ): string {
372
+ const maxWidth = Math.max(0, width);
373
+
374
+ // Preserve the legacy no-fill path exactly, including deferring left-only
375
+ // truncation to the style-specific frame clamp below.
376
+ if (!middle && !right) {
377
+ if (!rightStatus) return left;
378
+ const rightStatusWidth = visibleWidth(rightStatus);
379
+ if (rightStatusWidth >= maxWidth) return truncateToWidth(rightStatus, maxWidth, "");
380
+
381
+ const leftBudget = Math.max(0, maxWidth - rightStatusWidth - 1);
382
+ const leftText = truncateToWidth(left, leftBudget, "");
383
+ const gap = " ".repeat(Math.max(1, maxWidth - visibleWidth(leftText) - rightStatusWidth));
384
+ return `${leftText}${gap}${rightStatus}`;
385
+ }
386
+
387
+ const statusText = rightStatus ? truncateToWidth(rightStatus, maxWidth, "") : "";
388
+ const statusWidth = visibleWidth(statusText);
389
+ const leftBudget = Math.max(0, maxWidth - statusWidth - (statusText && left ? 1 : 0));
390
+ const leftText = truncateToWidth(left, leftBudget, "");
391
+ const leftWidth = visibleWidth(leftText);
392
+
393
+ const rightBudget = Math.max(
394
+ 0,
395
+ maxWidth - leftWidth - statusWidth - (leftText ? 1 : 0) - (statusText ? 1 : 0),
396
+ );
397
+ const configuredRight = truncateToWidth(right, rightBudget, "");
398
+ const rightText =
399
+ configuredRight && statusText
400
+ ? `${configuredRight} ${statusText}`
401
+ : configuredRight || statusText;
402
+ const rightWidth = visibleWidth(rightText);
403
+ const gapWidth = Math.max(0, maxWidth - leftWidth - rightWidth);
404
+ const middleWidth = visibleWidth(middle);
405
+ const minimumMiddleGap = (leftText ? 1 : 0) + (rightText ? 1 : 0);
406
+
407
+ if (!middle || middleWidth + minimumMiddleGap > gapWidth) {
408
+ return `${leftText}${" ".repeat(gapWidth)}${rightText}`;
409
+ }
410
+
411
+ const availablePadding = gapWidth - middleWidth;
412
+ const minimumLeftPadding = leftText ? 1 : 0;
413
+ const maximumLeftPadding = availablePadding - (rightText ? 1 : 0);
414
+ const leftPadding = Math.min(
415
+ maximumLeftPadding,
416
+ Math.max(minimumLeftPadding, Math.floor(availablePadding / 2)),
417
+ );
418
+ return `${leftText}${" ".repeat(leftPadding)}${middle}${" ".repeat(
419
+ availablePadding - leftPadding,
420
+ )}${rightText}`;
421
+ }
422
+
423
+ function ansiStrippedText(line: string): string {
424
+ return line.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "");
425
+ }
426
+
427
+ function plainRenderedText(line: string): string {
428
+ return ansiStrippedText(line).replace(/\[\/?[^\]]+\]/g, "");
429
+ }
430
+
431
+ function parseEditorBorder(
432
+ line: string,
433
+ direction: keyof ViewportCounts,
434
+ ): { count?: string } | undefined {
435
+ const plain = ansiStrippedText(line);
436
+ if (/^─+$/.test(plain)) return {};
437
+
438
+ const arrow = direction === "above" ? "↑" : "↓";
439
+ const match = new RegExp(`^─── ${arrow} ([1-9]\\d*) more ─*$`).exec(plain);
440
+ return match?.[1] ? { count: match[1] } : undefined;
441
+ }
442
+
443
+ function renderEditorBorder(
444
+ width: number,
445
+ direction: keyof ViewportCounts,
446
+ count: string | undefined,
447
+ ): string {
448
+ if (!count) return "─".repeat(width);
449
+ const arrow = direction === "above" ? "↑" : "↓";
450
+ const indicator = `─── ${arrow} ${count} more `;
451
+ return `${indicator}${"─".repeat(Math.max(0, width - visibleWidth(indicator)))}`;
452
+ }
453
+
454
+ function unwrapPolishedFrameOnly(
455
+ lines: string[],
456
+ config: ZentuiConfig,
457
+ uiTheme: Theme,
458
+ ): { editorLines: string[]; viewport: ViewportCounts } | undefined {
459
+ if (lines.length < 5) return undefined;
460
+ const top = parseEditorBorder(lines[0] ?? "", "above");
461
+ const bottom = parseEditorBorder(lines.at(-1) ?? "", "below");
462
+ if (!top || !bottom) return undefined;
463
+
464
+ const viewport = { above: top.count, below: bottom.count };
465
+ const interior = lines.slice(1, -1);
466
+ if (interior.length < 3) return undefined;
467
+
468
+ if (isLowRailPolishedStyle(config.components.editor.style)) {
469
+ if (
470
+ plainRenderedText(interior[0] ?? "").trim() !== "" ||
471
+ plainRenderedText(interior.at(-2) ?? "").trim() !== "" ||
472
+ !(interior.at(-1) ?? "").startsWith(" ")
473
+ )
474
+ return undefined;
475
+
476
+ const { prompt, promptWidth } = getEditorChromeWidths(config, uiTheme, "\x1b[0m");
477
+ const continuation = " ".repeat(promptWidth);
478
+ const content = interior.slice(1, -2);
479
+ const unwrapped: string[] = [];
480
+ for (let index = 0; index < content.length; index++) {
481
+ const prefix = index === 0 ? prompt : continuation;
482
+ const line = content[index] ?? "";
483
+ if (prefix && !line.startsWith(prefix)) return undefined;
484
+ unwrapped.push(prefix ? line.slice(prefix.length) : line);
485
+ }
486
+ return { editorLines: unwrapped, viewport };
487
+ }
488
+
489
+ const { rail } = getEditorChromeWidths(config, uiTheme, "\x1b[0m");
490
+ if (!rail || interior.some((line) => !line.startsWith(rail))) return undefined;
491
+ const unrailed = interior.map((line) => line.slice(rail.length));
492
+ if (
493
+ plainRenderedText(unrailed[0] ?? "").trim() !== "" ||
494
+ plainRenderedText(unrailed.at(-2) ?? "").trim() !== ""
495
+ )
496
+ return undefined;
497
+ return { editorLines: unrailed.slice(1, -2), viewport };
498
+ }
499
+
500
+ function splitPolishedFrame(
501
+ lines: string[],
502
+ config: ZentuiConfig,
503
+ uiTheme: Theme,
504
+ ): PolishedFrameSplit | undefined {
505
+ if (!parseEditorBorder(lines[0] ?? "", "above")) return undefined;
506
+
507
+ for (let bottomIndex = lines.length - 1; bottomIndex >= 4; bottomIndex--) {
508
+ if (!parseEditorBorder(lines[bottomIndex] ?? "", "below")) continue;
509
+ const frame = unwrapPolishedFrameOnly(lines.slice(0, bottomIndex + 1), config, uiTheme);
510
+ if (frame) return { ...frame, trailingLines: lines.slice(bottomIndex + 1) };
511
+ }
512
+ return undefined;
513
+ }
514
+
515
+ function inspectPolishedFrameProvenance(
516
+ base: WrappedEditor,
517
+ rendered: string[],
518
+ config: ZentuiConfig,
519
+ uiTheme: Theme,
520
+ ): { safe: boolean; ownedFrame?: PolishedFrameSplit } {
521
+ const provenance = POLISHED_FRAME_SPLITS.get(rendered);
522
+ const provenanceMatches = Boolean(
523
+ provenance &&
524
+ provenance.rows.length === rendered.length &&
525
+ provenance.rows.every((line, index) => line === rendered[index]),
526
+ );
527
+ const ownedFrame = provenanceMatches ? provenance?.split : undefined;
528
+ const unsafe =
529
+ Boolean(provenance && !provenanceMatches) ||
530
+ LEGACY_SPLIT_POLISHED_FRAME in base ||
531
+ (!ownedFrame && Boolean(splitPolishedFrame(rendered, config, uiTheme)));
532
+ return { safe: !unsafe, ownedFrame };
533
+ }
534
+
535
+ function vimModeColor(mode: string): string {
536
+ switch (mode.toLowerCase()) {
537
+ case "insert":
538
+ return "success";
539
+ case "normal":
540
+ return "accent";
541
+ case "ex":
542
+ return "warning";
543
+ case "replace":
544
+ return "error";
545
+ case "visual":
546
+ return "syntaxKeyword";
547
+ default:
548
+ return "muted";
549
+ }
550
+ }
551
+
552
+ function readVimStatus(editor: WrappedEditor, uiTheme: Theme): string | undefined {
553
+ const mode = editor.getMode?.();
554
+ if (typeof mode !== "string") return undefined;
555
+ const normalized = mode.trim();
556
+ if (!normalized) return undefined;
557
+ const label = `${normalized.toUpperCase()} `;
558
+ return safeThemeFg(uiTheme, vimModeColor(normalized), label);
559
+ }
560
+
561
+ function renderAccentRailFrameFromBase({
562
+ width,
563
+ baseRendered,
564
+ autocompleteSource,
565
+ autocompleteCapture,
566
+ uiTheme,
567
+ config,
568
+ ownedFrame,
569
+ trustedBaseFrame = false,
570
+ }: AccentRailFrameAdapterOptions): PolishedFrameResult {
571
+ if (width < ACCENT_RAIL_CHROME_WIDTH + 1 || baseRendered.length < 2) {
572
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
573
+ }
574
+ if (ownedFrame && !isPolishedFrameSplit(ownedFrame, baseRendered.length)) {
575
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
576
+ }
577
+ const autocomplete = ownedFrame
578
+ ? { known: true as const, count: ownedFrame.trailingLines.length }
579
+ : autocompleteCount(autocompleteSource, autocompleteCapture, baseRendered);
580
+ if (!autocomplete.known) {
581
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
582
+ }
583
+ const editorFrame =
584
+ !ownedFrame && autocomplete.count > 0
585
+ ? baseRendered.slice(0, -autocomplete.count)
586
+ : baseRendered;
587
+ const autocompleteLines = ownedFrame
588
+ ? ownedFrame.trailingLines
589
+ : autocomplete.count > 0
590
+ ? baseRendered.slice(-autocomplete.count)
591
+ : [];
592
+ if (editorFrame.length < 2) {
593
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
594
+ }
595
+ const parsedTop = parseEditorBorder(editorFrame[0] ?? "", "above");
596
+ const parsedBottom = parseEditorBorder(editorFrame.at(-1) ?? "", "below");
597
+ if (!ownedFrame && !trustedBaseFrame && (!parsedTop || !parsedBottom)) {
598
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
599
+ }
600
+ const editorLines = ownedFrame?.editorLines ?? editorFrame.slice(1, -1);
601
+ const viewport = ownedFrame?.viewport ?? {
602
+ above: parsedTop?.count,
603
+ below: parsedBottom?.count,
604
+ };
605
+ const lines = renderAccentRailEditorFrame({
606
+ width,
607
+ editorLines,
608
+ autocompleteLines,
609
+ viewport,
610
+ uiTheme,
611
+ config,
612
+ });
613
+ POLISHED_FRAME_SPLITS.set(lines, {
614
+ rows: Object.freeze([...lines]),
615
+ split: { editorLines, trailingLines: autocompleteLines, viewport },
616
+ });
617
+ return { lines, decorated: true };
618
+ }
619
+
620
+ function renderMinimalistFrameFromBase({
621
+ width,
622
+ baseRendered,
623
+ autocompleteSource,
624
+ autocompleteCapture,
625
+ uiTheme,
626
+ config,
627
+ inputText,
628
+ metadata,
629
+ ownedFrame,
630
+ trustedBaseFrame = false,
631
+ borderColor,
632
+ }: MinimalistFrameAdapterOptions): PolishedFrameResult {
633
+ if (width <= 4 || baseRendered.length < 2) {
634
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
635
+ }
636
+ if (ownedFrame && !isPolishedFrameSplit(ownedFrame, baseRendered.length)) {
637
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
638
+ }
639
+ const autocomplete = ownedFrame
640
+ ? { known: true as const, count: ownedFrame.trailingLines.length }
641
+ : autocompleteCount(autocompleteSource, autocompleteCapture, baseRendered);
642
+ if (!autocomplete.known) {
643
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
644
+ }
645
+ const editorFrame =
646
+ !ownedFrame && autocomplete.count > 0
647
+ ? baseRendered.slice(0, -autocomplete.count)
648
+ : baseRendered;
649
+ const autocompleteLines = ownedFrame
650
+ ? ownedFrame.trailingLines
651
+ : autocomplete.count > 0
652
+ ? baseRendered.slice(-autocomplete.count)
653
+ : [];
654
+ if (editorFrame.length < 2) {
655
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
656
+ }
657
+ const parsedTop = parseEditorBorder(editorFrame[0] ?? "", "above");
658
+ const parsedBottom = parseEditorBorder(editorFrame.at(-1) ?? "", "below");
659
+ if (!ownedFrame && !trustedBaseFrame && (!parsedTop || !parsedBottom)) {
660
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
661
+ }
662
+ const viewport = ownedFrame?.viewport ?? {
663
+ above: parsedTop?.count,
664
+ below: parsedBottom?.count,
665
+ };
666
+ return {
667
+ lines: renderMinimalistFrame({
668
+ width,
669
+ editorLines: ownedFrame?.editorLines ?? editorFrame.slice(1, -1),
670
+ autocompleteLines,
671
+ viewport: config.components.editor.viewportIndicators ? viewport : undefined,
672
+ inputText,
673
+ metadata,
674
+ uiTheme,
675
+ config,
676
+ borderColor,
677
+ }),
678
+ decorated: true,
679
+ };
680
+ }
681
+
682
+ function renderPolishedFrame({
683
+ width,
684
+ baseRendered,
685
+ autocompleteSource,
686
+ autocompleteCapture,
687
+ uiTheme,
688
+ config,
689
+ modelMeta,
690
+ thinkingLevel,
691
+ rightStatus,
692
+ ownedFrame,
693
+ trustedBaseFrame = false,
694
+ borderColor,
695
+ }: PolishedFrameOptions): PolishedFrameResult {
696
+ if (width <= 2) return { lines: clampRenderedLines(baseRendered, width), decorated: false };
697
+
698
+ if (baseRendered.length < 2) {
699
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
700
+ }
701
+ if (ownedFrame && !isPolishedFrameSplit(ownedFrame, baseRendered.length)) {
702
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
703
+ }
704
+
705
+ const autocomplete = ownedFrame
706
+ ? { known: true, count: ownedFrame.trailingLines.length }
707
+ : autocompleteCount(autocompleteSource, autocompleteCapture, baseRendered);
708
+ if (!autocomplete.known) {
709
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
710
+ }
711
+ const editorFrame =
712
+ !ownedFrame && autocomplete.count > 0
713
+ ? baseRendered.slice(0, -autocomplete.count)
714
+ : baseRendered;
715
+ const autocompleteLines = ownedFrame
716
+ ? ownedFrame.trailingLines
717
+ : autocomplete.count > 0
718
+ ? baseRendered.slice(-autocomplete.count)
719
+ : [];
720
+ if (editorFrame.length < 2) {
721
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
722
+ }
723
+
724
+ const parsedTop = parseEditorBorder(editorFrame[0] ?? "", "above");
725
+ const parsedBottom = parseEditorBorder(editorFrame.at(-1) ?? "", "below");
726
+ if (!ownedFrame && !trustedBaseFrame && (!parsedTop || !parsedBottom)) {
727
+ return { lines: clampRenderedLines(baseRendered, width), decorated: false };
728
+ }
729
+ const editorLines = ownedFrame?.editorLines ?? editorFrame.slice(1, -1);
730
+ const viewport = ownedFrame?.viewport ?? {
731
+ above: parsedTop?.count,
732
+ below: parsedBottom?.count,
733
+ };
734
+ const lines = renderPolishedEditorFrame({
735
+ width,
736
+ editorLines,
737
+ autocompleteLines,
738
+ viewport,
739
+ uiTheme,
740
+ config,
741
+ modelMeta,
742
+ thinkingLevel,
743
+ rightStatus,
744
+ borderColor,
745
+ });
746
+ POLISHED_FRAME_SPLITS.set(lines, {
747
+ rows: Object.freeze([...lines]),
748
+ split: {
749
+ editorLines,
750
+ trailingLines: autocompleteLines,
751
+ viewport,
752
+ },
753
+ });
754
+ return { lines, decorated: true };
755
+ }
756
+
757
+ /** Pure Opencode frame composition shared by the live editor and settings preview. */
758
+ export function renderPolishedEditorFrame({
759
+ width,
760
+ editorLines,
761
+ autocompleteLines = [],
762
+ viewport = {},
763
+ uiTheme,
764
+ config,
765
+ modelMeta,
766
+ thinkingLevel,
767
+ rightStatus,
768
+ borderColor,
769
+ }: PolishedEditorFrameOptions): string[] {
770
+ if (width <= 2) return clampRenderedLines(editorLines, width);
771
+ const reset = "\x1b[0m";
772
+ const colorSource = config.components.editor.colorSource;
773
+ const { prompt, promptWidth, rail, railWidth } = getEditorChromeWidths(config, uiTheme, reset);
774
+ const innerWidth = Math.max(0, width - railWidth);
775
+ const lowRailContinuation = " ".repeat(promptWidth);
776
+ const metadataZones = renderEditorMetadataFormatSplit(
777
+ selectedPolishedConfig(config)?.metadataFormat ??
778
+ config.components.editor.styles.opencode.metadataFormat,
779
+ {
780
+ model: modelMeta.modelLabel,
781
+ modelId: modelMeta.modelId ?? "",
782
+ modelName: modelMeta.modelName ?? "",
783
+ provider: modelMeta.providerLabel,
784
+ thinking: thinkingLevel ?? "",
785
+ sessionName: modelMeta.sessionName ?? "",
786
+ contextPercent: modelMeta.contextPercent,
787
+ contextWindow: modelMeta.contextWindow,
788
+ inputTokens: modelMeta.inputTokens,
789
+ outputTokens: modelMeta.outputTokens,
790
+ cacheHitRate: modelMeta.cacheHitRate,
791
+ },
792
+ uiTheme,
793
+ config,
794
+ );
795
+ const lowRailMeta = composeEditorMetadataLine(metadataZones, rightStatus, Math.max(0, width - 1));
796
+ const railedMeta = composeEditorMetadataLine(metadataZones, rightStatus, innerWidth);
797
+
798
+ const renderStaticBorder = (text: string) =>
799
+ renderStyleForSourceOrFallback(
800
+ uiTheme,
801
+ colorSource,
802
+ config.colors.editorBorder,
803
+ EDITOR_BORDER_FALLBACK,
804
+ text,
805
+ );
806
+ const renderBorder = (text: string) => {
807
+ if (
808
+ config.components.editor.borderColorMode !== "adaptive" ||
809
+ typeof borderColor !== "function"
810
+ ) {
811
+ return renderStaticBorder(text);
812
+ }
813
+ try {
814
+ const rendered = borderColor(text);
815
+ return typeof rendered === "string" ? rendered : renderStaticBorder(text);
816
+ } catch {
817
+ return renderStaticBorder(text);
818
+ }
819
+ };
820
+ const top = renderBorder(
821
+ renderEditorBorder(
822
+ width,
823
+ "above",
824
+ config.components.editor.viewportIndicators ? viewport.above : undefined,
825
+ ),
826
+ );
827
+ const bottom = renderBorder(
828
+ renderEditorBorder(
829
+ width,
830
+ "below",
831
+ config.components.editor.viewportIndicators ? viewport.below : undefined,
832
+ ),
833
+ );
834
+ const completionLines =
835
+ selectedPolishedConfig(config)?.completionMenu === "palette"
836
+ ? renderCompletionPalette({
837
+ lines: autocompleteLines,
838
+ width,
839
+ theme: uiTheme,
840
+ renderSeparator: renderBorder,
841
+ ownedBackground: false,
842
+ })
843
+ : autocompleteLines;
844
+ const lines = ["", ...editorLines, "", railedMeta];
845
+ const renderedLines = isLowRailPolishedStyle(config.components.editor.style)
846
+ ? [
847
+ top,
848
+ "",
849
+ ...editorLines.map(
850
+ (line, index) =>
851
+ `${index === 0 ? prompt : lowRailContinuation}${fillLine(line, innerWidth)}`,
852
+ ),
853
+ "",
854
+ ` ${truncateToWidth(lowRailMeta, Math.max(0, width - 1), "")}`,
855
+ bottom,
856
+ ...completionLines,
857
+ ]
858
+ : [
859
+ top,
860
+ ...lines.map((line) => `${rail}${fillLine(line, innerWidth)}`),
861
+ bottom,
862
+ ...completionLines,
863
+ ];
864
+
865
+ return clampRenderedLines(renderedLines, width);
866
+ }
867
+
868
+ export class PolishedEditor extends CustomEditor {
869
+ private readonly getModelMeta: () => EditorMeta;
870
+ private readonly getThinkingLevel: () => string | undefined;
871
+ private readonly getMinimalistMetadata: () => MinimalistEditorMetadata;
872
+ private readonly onMinimalistDecorationChange: (active: boolean) => void;
873
+ private readonly getConfig: () => ZentuiConfig;
874
+ private readonly uiTheme: Theme;
875
+
876
+ constructor(
877
+ tui: TUI,
878
+ theme: EditorTheme,
879
+ keybindings: KeybindingsManager,
880
+ uiTheme: Theme,
881
+ getConfig: () => ZentuiConfig,
882
+ getModelMeta: () => EditorMeta,
883
+ getThinkingLevel: () => string | undefined,
884
+ getMinimalistMetadata: () => MinimalistEditorMetadata = () => ({ cwd: "" }),
885
+ onMinimalistDecorationChange: (active: boolean) => void = () => {},
886
+ ) {
887
+ super(tui, theme, keybindings, { paddingX: 0 });
888
+ this.borderColor = (text: string) => safeThemeFg(uiTheme, "border", text);
889
+ this.uiTheme = uiTheme;
890
+ this.getConfig = getConfig;
891
+ this.getModelMeta = getModelMeta;
892
+ this.getThinkingLevel = getThinkingLevel;
893
+ this.getMinimalistMetadata = getMinimalistMetadata;
894
+ this.onMinimalistDecorationChange = onMinimalistDecorationChange;
895
+ }
896
+
897
+ private reportMinimalistDecoration(active: boolean): void {
898
+ this.onMinimalistDecorationChange(active);
899
+ }
900
+
901
+ render(width: number): string[] {
902
+ const config = this.getConfig();
903
+ if (!config.components.editor.enabled) {
904
+ this.reportMinimalistDecoration(false);
905
+ return clampRenderedLines(super.render(width), width);
906
+ }
907
+ if (config.components.editor.style === "accent-rail") {
908
+ this.reportMinimalistDecoration(false);
909
+ if (width < ACCENT_RAIL_CHROME_WIDTH + 1) {
910
+ return clampRenderedLines(super.render(width), width);
911
+ }
912
+ let captured: { value: string[]; capture?: AutocompleteCapture };
913
+ try {
914
+ captured = renderWithAutocompleteCapture(
915
+ this as unknown as AutocompleteEditorInternals,
916
+ () => super.render(width - ACCENT_RAIL_CHROME_WIDTH),
917
+ );
918
+ } catch {
919
+ return clampRenderedLines(super.render(width), width);
920
+ }
921
+ try {
922
+ const result = renderAccentRailFrameFromBase({
923
+ width,
924
+ baseRendered: captured.value,
925
+ autocompleteSource: this as unknown as AutocompleteEditorInternals,
926
+ autocompleteCapture: captured.capture,
927
+ uiTheme: this.uiTheme,
928
+ config,
929
+ trustedBaseFrame: true,
930
+ });
931
+ if (result.decorated) return result.lines;
932
+ } catch {
933
+ // Decoration is optional; preserve the completed same-render rows below.
934
+ }
935
+ return clampRenderedLines(captured.value, width);
936
+ }
937
+ if (config.components.editor.style === "minimalist") {
938
+ if (width <= 4) {
939
+ this.reportMinimalistDecoration(false);
940
+ return clampRenderedLines(super.render(width), width);
941
+ }
942
+ let captured: { value: string[]; capture?: AutocompleteCapture };
943
+ try {
944
+ captured = renderWithAutocompleteCapture(
945
+ this as unknown as AutocompleteEditorInternals,
946
+ () => super.render(Math.max(0, width - 4)),
947
+ );
948
+ } catch {
949
+ this.reportMinimalistDecoration(false);
950
+ return clampRenderedLines(super.render(width), width);
951
+ }
952
+ try {
953
+ const result = renderMinimalistFrameFromBase({
954
+ width,
955
+ baseRendered: captured.value,
956
+ autocompleteSource: this as unknown as AutocompleteEditorInternals,
957
+ autocompleteCapture: captured.capture,
958
+ uiTheme: this.uiTheme,
959
+ config,
960
+ inputText: this.getText(),
961
+ metadata: this.getMinimalistMetadata(),
962
+ trustedBaseFrame: true,
963
+ borderColor: this.borderColor,
964
+ });
965
+ this.reportMinimalistDecoration(result.decorated);
966
+ return result.lines;
967
+ } catch {
968
+ this.reportMinimalistDecoration(false);
969
+ return clampRenderedLines(captured.value, width);
970
+ }
971
+ }
972
+ this.reportMinimalistDecoration(false);
973
+ if (width <= 2) {
974
+ return clampRenderedLines(super.render(width), width);
975
+ }
976
+
977
+ const { railWidth } = getEditorChromeWidths(config, this.uiTheme, "\x1b[0m");
978
+ const innerWidth = Math.max(0, width - railWidth);
979
+ let captured: { value: string[]; capture?: AutocompleteCapture };
980
+ try {
981
+ captured = renderWithAutocompleteCapture(this as unknown as AutocompleteEditorInternals, () =>
982
+ super.render(innerWidth),
983
+ );
984
+ } catch {
985
+ return clampRenderedLines(super.render(width), width);
986
+ }
987
+ try {
988
+ const result = renderPolishedFrame({
989
+ width,
990
+ baseRendered: captured.value,
991
+ autocompleteSource: this as unknown as AutocompleteEditorInternals,
992
+ autocompleteCapture: captured.capture,
993
+ uiTheme: this.uiTheme,
994
+ config,
995
+ modelMeta: this.getModelMeta(),
996
+ thinkingLevel: this.getThinkingLevel(),
997
+ trustedBaseFrame: true,
998
+ borderColor: this.borderColor,
999
+ });
1000
+ if (result.decorated) return result.lines;
1001
+ } catch {
1002
+ // Decoration is optional; preserve the completed same-render rows below.
1003
+ }
1004
+ return clampRenderedLines(captured.value, width);
1005
+ }
1006
+ }
1007
+
1008
+ export class WrappedPolishedEditor implements EditorComponent {
1009
+ declare readonly addToHistory?: (text: string) => void;
1010
+ declare readonly insertTextAtCursor?: (text: string) => void;
1011
+ declare readonly setAutocompleteProvider?: (provider: AutocompleteProvider) => void;
1012
+ declare readonly setPaddingX?: (padding: number) => void;
1013
+ declare readonly setAutocompleteMaxVisible?: (maxVisible: number) => void;
1014
+
1015
+ constructor(
1016
+ private readonly base: WrappedEditor,
1017
+ private readonly uiTheme: Theme,
1018
+ private readonly getConfig: () => ZentuiConfig,
1019
+ private readonly getModelMeta: () => EditorMeta,
1020
+ private readonly getThinkingLevel: () => string | undefined,
1021
+ private readonly getMinimalistMetadata: () => MinimalistEditorMetadata = () => ({ cwd: "" }),
1022
+ private readonly onMinimalistDecorationChange: (active: boolean) => void = () => {},
1023
+ ) {
1024
+ if (typeof base.addToHistory === "function") {
1025
+ this.addToHistory = (text) => base.addToHistory?.(text);
1026
+ }
1027
+ if (typeof base.insertTextAtCursor === "function") {
1028
+ this.insertTextAtCursor = (text) => base.insertTextAtCursor?.(text);
1029
+ }
1030
+ if (typeof base.setAutocompleteProvider === "function") {
1031
+ this.setAutocompleteProvider = (provider) => base.setAutocompleteProvider?.(provider);
1032
+ }
1033
+ if (typeof base.setPaddingX === "function") {
1034
+ this.setPaddingX = (padding) => base.setPaddingX?.(padding);
1035
+ }
1036
+ if (typeof base.setAutocompleteMaxVisible === "function") {
1037
+ this.setAutocompleteMaxVisible = (maxVisible) => base.setAutocompleteMaxVisible?.(maxVisible);
1038
+ }
1039
+ }
1040
+
1041
+ get focused(): boolean {
1042
+ return Boolean(this.base.focused);
1043
+ }
1044
+ set focused(value: boolean) {
1045
+ this.base.focused = value;
1046
+ }
1047
+
1048
+ get borderColor(): ((str: string) => string) | undefined {
1049
+ return this.base.borderColor;
1050
+ }
1051
+ set borderColor(value: ((str: string) => string) | undefined) {
1052
+ this.base.borderColor = value;
1053
+ }
1054
+
1055
+ get onSubmit(): ((text: string) => void) | undefined {
1056
+ return this.base.onSubmit;
1057
+ }
1058
+ set onSubmit(value: ((text: string) => void) | undefined) {
1059
+ this.base.onSubmit = value;
1060
+ }
1061
+
1062
+ get onChange(): ((text: string) => void) | undefined {
1063
+ return this.base.onChange;
1064
+ }
1065
+ set onChange(value: ((text: string) => void) | undefined) {
1066
+ this.base.onChange = value;
1067
+ }
1068
+
1069
+ get onEscape(): (() => void) | undefined {
1070
+ return this.base.onEscape;
1071
+ }
1072
+ set onEscape(value: (() => void) | undefined) {
1073
+ this.base.onEscape = value;
1074
+ }
1075
+
1076
+ get onCtrlD(): (() => void) | undefined {
1077
+ return this.base.onCtrlD;
1078
+ }
1079
+ set onCtrlD(value: (() => void) | undefined) {
1080
+ this.base.onCtrlD = value;
1081
+ }
1082
+
1083
+ get onPasteImage(): (() => void) | undefined {
1084
+ return this.base.onPasteImage;
1085
+ }
1086
+ set onPasteImage(value: (() => void) | undefined) {
1087
+ this.base.onPasteImage = value;
1088
+ }
1089
+
1090
+ get onExtensionShortcut(): ((data: string) => boolean) | undefined {
1091
+ return this.base.onExtensionShortcut;
1092
+ }
1093
+ set onExtensionShortcut(value: ((data: string) => boolean) | undefined) {
1094
+ this.base.onExtensionShortcut = value;
1095
+ }
1096
+
1097
+ get actionHandlers(): Map<unknown, () => void> | undefined {
1098
+ return this.base.actionHandlers;
1099
+ }
1100
+ set actionHandlers(value: Map<unknown, () => void> | undefined) {
1101
+ this.base.actionHandlers = value;
1102
+ }
1103
+
1104
+ get wantsKeyRelease(): boolean | undefined {
1105
+ return this.base.wantsKeyRelease;
1106
+ }
1107
+ set wantsKeyRelease(value: boolean | undefined) {
1108
+ this.base.wantsKeyRelease = value;
1109
+ }
1110
+
1111
+ get disableSubmit(): boolean | undefined {
1112
+ return this.base.disableSubmit;
1113
+ }
1114
+ set disableSubmit(value: boolean | undefined) {
1115
+ this.base.disableSubmit = value;
1116
+ }
1117
+
1118
+ private reportMinimalistDecoration(active: boolean): void {
1119
+ this.onMinimalistDecorationChange(active);
1120
+ }
1121
+
1122
+ render(width: number): string[] {
1123
+ const config = this.getConfig();
1124
+ if (!config.components.editor.enabled) {
1125
+ this.reportMinimalistDecoration(false);
1126
+ return clampRenderedLines(this.base.render(width), width);
1127
+ }
1128
+ if (config.components.editor.style === "accent-rail") {
1129
+ this.reportMinimalistDecoration(false);
1130
+ if (width < ACCENT_RAIL_CHROME_WIDTH + 1) {
1131
+ return clampRenderedLines(this.base.render(width), width);
1132
+ }
1133
+ let captured: { value: string[]; capture?: AutocompleteCapture };
1134
+ try {
1135
+ captured = renderWithAutocompleteCapture(this.base, () =>
1136
+ this.base.render(width - ACCENT_RAIL_CHROME_WIDTH),
1137
+ );
1138
+ } catch {
1139
+ return clampRenderedLines(this.base.render(width), width);
1140
+ }
1141
+ try {
1142
+ const provenance = inspectPolishedFrameProvenance(
1143
+ this.base,
1144
+ captured.value,
1145
+ config,
1146
+ this.uiTheme,
1147
+ );
1148
+ if (provenance.safe) {
1149
+ const result = renderAccentRailFrameFromBase({
1150
+ width,
1151
+ baseRendered: captured.value,
1152
+ autocompleteSource: this.base,
1153
+ autocompleteCapture: captured.capture,
1154
+ uiTheme: this.uiTheme,
1155
+ config,
1156
+ ownedFrame: provenance.ownedFrame,
1157
+ });
1158
+ if (result.decorated) return result.lines;
1159
+ }
1160
+ } catch {
1161
+ // Decoration is optional; preserve the completed same-render rows below.
1162
+ }
1163
+ return clampRenderedLines(captured.value, width);
1164
+ }
1165
+ if (config.components.editor.style === "minimalist") {
1166
+ if (width <= 4) {
1167
+ this.reportMinimalistDecoration(false);
1168
+ return clampRenderedLines(this.base.render(width), width);
1169
+ }
1170
+ let captured: { value: string[]; capture?: AutocompleteCapture };
1171
+ try {
1172
+ captured = renderWithAutocompleteCapture(this.base, () =>
1173
+ this.base.render(Math.max(0, width - 4)),
1174
+ );
1175
+ } catch {
1176
+ this.reportMinimalistDecoration(false);
1177
+ return clampRenderedLines(this.base.render(width), width);
1178
+ }
1179
+ try {
1180
+ const provenance = inspectPolishedFrameProvenance(
1181
+ this.base,
1182
+ captured.value,
1183
+ config,
1184
+ this.uiTheme,
1185
+ );
1186
+ if (provenance.safe) {
1187
+ const result = renderMinimalistFrameFromBase({
1188
+ width,
1189
+ baseRendered: captured.value,
1190
+ autocompleteSource: this.base,
1191
+ autocompleteCapture: captured.capture,
1192
+ uiTheme: this.uiTheme,
1193
+ config,
1194
+ inputText: this.base.getText(),
1195
+ metadata: this.getMinimalistMetadata(),
1196
+ ownedFrame: provenance.ownedFrame,
1197
+ borderColor: this.borderColor,
1198
+ });
1199
+ if (result.decorated) {
1200
+ this.reportMinimalistDecoration(true);
1201
+ return result.lines;
1202
+ }
1203
+ }
1204
+ } catch {
1205
+ // Decoration is optional; preserve the completed same-render rows below.
1206
+ }
1207
+ this.reportMinimalistDecoration(false);
1208
+ return clampRenderedLines(captured.value, width);
1209
+ }
1210
+ this.reportMinimalistDecoration(false);
1211
+ if (width <= 2) return clampRenderedLines(this.base.render(width), width);
1212
+
1213
+ const { railWidth } = getEditorChromeWidths(config, this.uiTheme, "\x1b[0m");
1214
+ const innerWidth = Math.max(0, width - railWidth);
1215
+ let captured: { value: string[]; capture?: AutocompleteCapture };
1216
+ try {
1217
+ captured = renderWithAutocompleteCapture(this.base, () => this.base.render(innerWidth));
1218
+ } catch {
1219
+ return clampRenderedLines(this.base.render(width), width);
1220
+ }
1221
+ let result: PolishedFrameResult | undefined;
1222
+ try {
1223
+ const provenance = inspectPolishedFrameProvenance(
1224
+ this.base,
1225
+ captured.value,
1226
+ config,
1227
+ this.uiTheme,
1228
+ );
1229
+ if (provenance.safe) {
1230
+ result = renderPolishedFrame({
1231
+ width,
1232
+ baseRendered: captured.value,
1233
+ autocompleteSource: this.base,
1234
+ autocompleteCapture: captured.capture,
1235
+ uiTheme: this.uiTheme,
1236
+ config,
1237
+ modelMeta: this.getModelMeta(),
1238
+ thinkingLevel: this.getThinkingLevel(),
1239
+ rightStatus: readVimStatus(this.base, this.uiTheme),
1240
+ ownedFrame: provenance.ownedFrame,
1241
+ borderColor: this.borderColor,
1242
+ });
1243
+ }
1244
+ } catch {
1245
+ // Decoration is optional; preserve the completed same-render rows below.
1246
+ }
1247
+ if (result?.decorated) return result.lines;
1248
+ return clampRenderedLines(captured.value, width);
1249
+ }
1250
+
1251
+ invalidate(): void {
1252
+ this.base.invalidate?.();
1253
+ }
1254
+
1255
+ handleInput(data: string): void {
1256
+ this.base.handleInput(data);
1257
+ }
1258
+
1259
+ getText(): string {
1260
+ return this.base.getText();
1261
+ }
1262
+
1263
+ setText(text: string): void {
1264
+ this.base.setText(text);
1265
+ }
1266
+
1267
+ getExpandedText(): string {
1268
+ return this.base.getExpandedText?.() ?? this.base.getText();
1269
+ }
1270
+
1271
+ getLines(): string[] {
1272
+ return this.base.getLines?.() ?? this.base.getText().split("\n");
1273
+ }
1274
+
1275
+ getCursor(): unknown {
1276
+ return this.base.getCursor?.();
1277
+ }
1278
+
1279
+ getMode(): unknown {
1280
+ return this.base.getMode?.();
1281
+ }
1282
+
1283
+ getPaddingX(): number | undefined {
1284
+ return this.base.getPaddingX?.();
1285
+ }
1286
+
1287
+ getAutocompleteMaxVisible(): number | undefined {
1288
+ return this.base.getAutocompleteMaxVisible?.();
1289
+ }
1290
+ }