@agimon-ai/doompi-ui 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (327) hide show
  1. package/README.md +21 -18
  2. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  3. package/dist/adapters/pi/extension.cjs +2 -0
  4. package/dist/adapters/pi/extension.cjs.map +1 -0
  5. package/dist/adapters/pi/extension.d.cts +7 -0
  6. package/dist/adapters/pi/extension.d.cts.map +1 -0
  7. package/dist/adapters/pi/extension.d.mts +7 -0
  8. package/dist/adapters/pi/extension.d.mts.map +1 -0
  9. package/dist/adapters/pi/extension.mjs +2 -0
  10. package/dist/adapters/pi/extension.mjs.map +1 -0
  11. package/dist/adapters/pi/extensionName.cjs +2 -0
  12. package/dist/adapters/pi/extensionName.cjs.map +1 -0
  13. package/dist/adapters/pi/extensionName.d.cts +16 -0
  14. package/dist/adapters/pi/extensionName.d.cts.map +1 -0
  15. package/dist/adapters/pi/extensionName.d.mts +16 -0
  16. package/dist/adapters/pi/extensionName.d.mts.map +1 -0
  17. package/dist/adapters/pi/extensionName.mjs +2 -0
  18. package/dist/adapters/pi/extensionName.mjs.map +1 -0
  19. package/dist/adapters/pi/toolOverrides.cjs +2 -0
  20. package/dist/adapters/pi/toolOverrides.cjs.map +1 -0
  21. package/dist/adapters/pi/toolOverrides.mjs +2 -0
  22. package/dist/adapters/pi/toolOverrides.mjs.map +1 -0
  23. package/dist/adapters/telemetry/logSinkTelemetry.cjs +2 -0
  24. package/dist/adapters/telemetry/logSinkTelemetry.cjs.map +1 -0
  25. package/dist/adapters/telemetry/logSinkTelemetry.d.cts +29 -0
  26. package/dist/adapters/telemetry/logSinkTelemetry.d.cts.map +1 -0
  27. package/dist/adapters/telemetry/logSinkTelemetry.d.mts +29 -0
  28. package/dist/adapters/telemetry/logSinkTelemetry.d.mts.map +1 -0
  29. package/dist/adapters/telemetry/logSinkTelemetry.mjs +2 -0
  30. package/dist/adapters/telemetry/logSinkTelemetry.mjs.map +1 -0
  31. package/dist/components/configOverlay.cjs +0 -0
  32. package/dist/components/configOverlay.d.cts +0 -0
  33. package/dist/components/configOverlay.d.mts +0 -0
  34. package/dist/components/configOverlay.mjs +0 -0
  35. package/dist/components/doomEditor.cjs +1 -2
  36. package/dist/components/doomEditor.d.cts +2 -59
  37. package/dist/components/doomEditor.d.mts +2 -59
  38. package/dist/components/doomEditor.mjs +1 -2
  39. package/dist/components/doomFooter.cjs +1 -2
  40. package/dist/components/doomFooter.d.cts +2 -32
  41. package/dist/components/doomFooter.d.mts +2 -32
  42. package/dist/components/doomFooter.mjs +1 -2
  43. package/dist/components/doomHeader.cjs +1 -2
  44. package/dist/components/doomHeader.d.cts +2 -34
  45. package/dist/components/doomHeader.d.mts +2 -34
  46. package/dist/components/doomHeader.mjs +1 -2
  47. package/dist/components/doomOverlay.cjs +1 -2
  48. package/dist/components/doomOverlay.d.cts +2 -79
  49. package/dist/components/doomOverlay.d.mts +2 -79
  50. package/dist/components/doomOverlay.mjs +1 -2
  51. package/dist/components/leaderHints.cjs +1 -2
  52. package/dist/components/leaderHints.d.cts +2 -37
  53. package/dist/components/leaderHints.d.mts +2 -37
  54. package/dist/components/leaderHints.mjs +1 -2
  55. package/dist/components/toolsOverlay.cjs +1 -2
  56. package/dist/components/toolsOverlay.d.cts +2 -29
  57. package/dist/components/toolsOverlay.d.mts +2 -29
  58. package/dist/components/toolsOverlay.mjs +1 -2
  59. package/dist/config.cjs +1 -2
  60. package/dist/config.d.cts +2 -5
  61. package/dist/config.d.mts +2 -5
  62. package/dist/config.mjs +1 -2
  63. package/dist/extension.cjs +1 -2
  64. package/dist/extension.d.cts +2 -7
  65. package/dist/extension.d.mts +2 -7
  66. package/dist/extension.mjs +1 -2
  67. package/dist/extensionName.cjs +1 -2
  68. package/dist/extensionName.d.cts +2 -5
  69. package/dist/extensionName.d.mts +2 -5
  70. package/dist/extensionName.mjs +1 -2
  71. package/dist/extensions/pi.cjs +1 -1
  72. package/dist/extensions/pi.cjs.map +1 -1
  73. package/dist/extensions/pi.d.cts +1 -1
  74. package/dist/extensions/pi.d.mts +1 -1
  75. package/dist/extensions/pi.mjs +1 -1
  76. package/dist/extensions/pi.mjs.map +1 -1
  77. package/dist/footer.cjs +1 -2
  78. package/dist/footer.d.cts +2 -5
  79. package/dist/footer.d.mts +2 -5
  80. package/dist/footer.mjs +1 -2
  81. package/dist/harnessMetadata.cjs +1 -2
  82. package/dist/harnessMetadata.d.cts +2 -21
  83. package/dist/harnessMetadata.d.mts +2 -21
  84. package/dist/harnessMetadata.mjs +1 -2
  85. package/dist/index.cjs +1 -1
  86. package/dist/index.d.cts +14 -25
  87. package/dist/index.d.mts +14 -25
  88. package/dist/index.mjs +1 -1
  89. package/dist/leaderRegistry.cjs +1 -2
  90. package/dist/leaderRegistry.d.cts +2 -58
  91. package/dist/leaderRegistry.d.mts +2 -58
  92. package/dist/leaderRegistry.mjs +1 -2
  93. package/dist/logSinkTelemetry.cjs +1 -2
  94. package/dist/logSinkTelemetry.d.cts +2 -29
  95. package/dist/logSinkTelemetry.d.mts +2 -29
  96. package/dist/logSinkTelemetry.mjs +1 -2
  97. package/dist/rendering.cjs +1 -2
  98. package/dist/rendering.d.cts +2 -11
  99. package/dist/rendering.d.mts +2 -11
  100. package/dist/rendering.mjs +1 -2
  101. package/dist/services/leader/leaderRegistry.cjs +2 -0
  102. package/dist/services/leader/leaderRegistry.cjs.map +1 -0
  103. package/dist/services/leader/leaderRegistry.d.cts +58 -0
  104. package/dist/services/leader/leaderRegistry.d.cts.map +1 -0
  105. package/dist/services/leader/leaderRegistry.d.mts +58 -0
  106. package/dist/services/leader/leaderRegistry.d.mts.map +1 -0
  107. package/dist/services/leader/leaderRegistry.mjs +2 -0
  108. package/dist/services/leader/leaderRegistry.mjs.map +1 -0
  109. package/dist/services/state/uiState.cjs +2 -0
  110. package/dist/services/state/uiState.cjs.map +1 -0
  111. package/dist/services/state/uiState.d.cts +38 -0
  112. package/dist/services/state/uiState.d.cts.map +1 -0
  113. package/dist/services/state/uiState.d.mts +38 -0
  114. package/dist/services/state/uiState.d.mts.map +1 -0
  115. package/dist/services/state/uiState.mjs +2 -0
  116. package/dist/services/state/uiState.mjs.map +1 -0
  117. package/dist/services/tools/toolInventory.cjs +2 -0
  118. package/dist/services/tools/toolInventory.cjs.map +1 -0
  119. package/dist/services/tools/toolInventory.d.cts +51 -0
  120. package/dist/services/tools/toolInventory.d.cts.map +1 -0
  121. package/dist/services/tools/toolInventory.d.mts +51 -0
  122. package/dist/services/tools/toolInventory.d.mts.map +1 -0
  123. package/dist/services/tools/toolInventory.mjs +2 -0
  124. package/dist/services/tools/toolInventory.mjs.map +1 -0
  125. package/dist/skills.cjs +1 -1
  126. package/dist/skills.d.cts +2 -1
  127. package/dist/skills.d.mts +2 -1
  128. package/dist/skills.mjs +1 -1
  129. package/dist/theme.cjs +1 -2
  130. package/dist/theme.d.cts +2 -95
  131. package/dist/theme.d.mts +2 -95
  132. package/dist/theme.mjs +1 -2
  133. package/dist/toolChrome.cjs +1 -0
  134. package/dist/toolChrome.d.cts +2 -0
  135. package/dist/toolChrome.d.mts +2 -0
  136. package/dist/toolChrome.mjs +1 -0
  137. package/dist/toolInventory.cjs +1 -2
  138. package/dist/toolInventory.d.cts +2 -40
  139. package/dist/toolInventory.d.mts +2 -40
  140. package/dist/toolInventory.mjs +1 -2
  141. package/dist/tui/builtinToolRender.cjs +14 -0
  142. package/dist/tui/builtinToolRender.cjs.map +1 -0
  143. package/dist/tui/builtinToolRender.mjs +14 -0
  144. package/dist/tui/builtinToolRender.mjs.map +1 -0
  145. package/dist/tui/codeGutter.cjs +2 -0
  146. package/dist/tui/codeGutter.cjs.map +1 -0
  147. package/dist/tui/codeGutter.mjs +2 -0
  148. package/dist/tui/codeGutter.mjs.map +1 -0
  149. package/dist/tui/configOverlay.cjs +0 -0
  150. package/dist/tui/configOverlay.cjs.map +1 -0
  151. package/dist/tui/configOverlay.d.cts +0 -0
  152. package/dist/tui/configOverlay.d.cts.map +1 -0
  153. package/dist/tui/configOverlay.d.mts +0 -0
  154. package/dist/tui/configOverlay.d.mts.map +1 -0
  155. package/dist/tui/configOverlay.mjs +0 -0
  156. package/dist/tui/configOverlay.mjs.map +1 -0
  157. package/dist/tui/diffRender.cjs +4 -0
  158. package/dist/tui/diffRender.cjs.map +1 -0
  159. package/dist/tui/diffRender.mjs +4 -0
  160. package/dist/tui/diffRender.mjs.map +1 -0
  161. package/dist/tui/doomEditor.cjs +2 -0
  162. package/dist/tui/doomEditor.cjs.map +1 -0
  163. package/dist/tui/doomEditor.d.cts +63 -0
  164. package/dist/tui/doomEditor.d.cts.map +1 -0
  165. package/dist/tui/doomEditor.d.mts +63 -0
  166. package/dist/tui/doomEditor.d.mts.map +1 -0
  167. package/dist/tui/doomEditor.mjs +2 -0
  168. package/dist/tui/doomEditor.mjs.map +1 -0
  169. package/dist/tui/doomFooter.cjs +2 -0
  170. package/dist/tui/doomFooter.cjs.map +1 -0
  171. package/dist/tui/doomFooter.d.cts +32 -0
  172. package/dist/tui/doomFooter.d.cts.map +1 -0
  173. package/dist/tui/doomFooter.d.mts +32 -0
  174. package/dist/tui/doomFooter.d.mts.map +1 -0
  175. package/dist/tui/doomFooter.mjs +2 -0
  176. package/dist/tui/doomFooter.mjs.map +1 -0
  177. package/dist/tui/doomHeader.cjs +2 -0
  178. package/dist/tui/doomHeader.cjs.map +1 -0
  179. package/dist/tui/doomHeader.d.cts +34 -0
  180. package/dist/tui/doomHeader.d.cts.map +1 -0
  181. package/dist/tui/doomHeader.d.mts +34 -0
  182. package/dist/tui/doomHeader.d.mts.map +1 -0
  183. package/dist/tui/doomHeader.mjs +2 -0
  184. package/dist/tui/doomHeader.mjs.map +1 -0
  185. package/dist/tui/doomOverlay.cjs +2 -0
  186. package/dist/tui/doomOverlay.cjs.map +1 -0
  187. package/dist/tui/doomOverlay.d.cts +84 -0
  188. package/dist/tui/doomOverlay.d.cts.map +1 -0
  189. package/dist/tui/doomOverlay.d.mts +84 -0
  190. package/dist/tui/doomOverlay.d.mts.map +1 -0
  191. package/dist/tui/doomOverlay.mjs +2 -0
  192. package/dist/tui/doomOverlay.mjs.map +1 -0
  193. package/dist/tui/leaderHints.cjs +2 -0
  194. package/dist/tui/leaderHints.cjs.map +1 -0
  195. package/dist/tui/leaderHints.d.cts +36 -0
  196. package/dist/tui/leaderHints.d.cts.map +1 -0
  197. package/dist/tui/leaderHints.d.mts +36 -0
  198. package/dist/tui/leaderHints.d.mts.map +1 -0
  199. package/dist/tui/leaderHints.mjs +2 -0
  200. package/dist/tui/leaderHints.mjs.map +1 -0
  201. package/dist/tui/rendering.cjs +2 -0
  202. package/dist/tui/rendering.cjs.map +1 -0
  203. package/dist/tui/rendering.d.cts +16 -0
  204. package/dist/tui/rendering.d.cts.map +1 -0
  205. package/dist/tui/rendering.d.mts +16 -0
  206. package/dist/tui/rendering.d.mts.map +1 -0
  207. package/dist/tui/rendering.mjs +2 -0
  208. package/dist/tui/rendering.mjs.map +1 -0
  209. package/dist/tui/theme.cjs +2 -0
  210. package/dist/tui/theme.cjs.map +1 -0
  211. package/dist/tui/theme.d.cts +103 -0
  212. package/dist/tui/theme.d.cts.map +1 -0
  213. package/dist/tui/theme.d.mts +103 -0
  214. package/dist/tui/theme.d.mts.map +1 -0
  215. package/dist/tui/theme.mjs +2 -0
  216. package/dist/tui/theme.mjs.map +1 -0
  217. package/dist/tui/toolChrome.cjs +2 -0
  218. package/dist/tui/toolChrome.cjs.map +1 -0
  219. package/dist/tui/toolChrome.d.cts +30 -0
  220. package/dist/tui/toolChrome.d.cts.map +1 -0
  221. package/dist/tui/toolChrome.d.mts +30 -0
  222. package/dist/tui/toolChrome.d.mts.map +1 -0
  223. package/dist/tui/toolChrome.mjs +2 -0
  224. package/dist/tui/toolChrome.mjs.map +1 -0
  225. package/dist/tui/toolsOverlay.cjs +2 -0
  226. package/dist/tui/toolsOverlay.cjs.map +1 -0
  227. package/dist/tui/toolsOverlay.d.cts +29 -0
  228. package/dist/tui/toolsOverlay.d.cts.map +1 -0
  229. package/dist/tui/toolsOverlay.d.mts +29 -0
  230. package/dist/tui/toolsOverlay.d.mts.map +1 -0
  231. package/dist/tui/toolsOverlay.mjs +2 -0
  232. package/dist/tui/toolsOverlay.mjs.map +1 -0
  233. package/dist/types/harnessMetadata.cjs +2 -0
  234. package/dist/types/harnessMetadata.cjs.map +1 -0
  235. package/dist/types/harnessMetadata.d.cts +21 -0
  236. package/dist/types/harnessMetadata.d.cts.map +1 -0
  237. package/dist/types/harnessMetadata.d.mts +21 -0
  238. package/dist/types/harnessMetadata.d.mts.map +1 -0
  239. package/dist/types/harnessMetadata.mjs +2 -0
  240. package/dist/types/harnessMetadata.mjs.map +1 -0
  241. package/dist/uiState.cjs +1 -2
  242. package/dist/uiState.d.cts +2 -37
  243. package/dist/uiState.d.mts +2 -37
  244. package/dist/uiState.mjs +1 -2
  245. package/package.json +19 -12
  246. package/themes/doom-pi-dark.json +32 -30
  247. package/dist/_virtual/_rolldown/runtime.mjs +0 -1
  248. package/dist/components/configOverlay.cjs.map +0 -1
  249. package/dist/components/configOverlay.d.cts.map +0 -1
  250. package/dist/components/configOverlay.d.mts.map +0 -1
  251. package/dist/components/configOverlay.mjs.map +0 -1
  252. package/dist/components/doomEditor.cjs.map +0 -1
  253. package/dist/components/doomEditor.d.cts.map +0 -1
  254. package/dist/components/doomEditor.d.mts.map +0 -1
  255. package/dist/components/doomEditor.mjs.map +0 -1
  256. package/dist/components/doomFooter.cjs.map +0 -1
  257. package/dist/components/doomFooter.d.cts.map +0 -1
  258. package/dist/components/doomFooter.d.mts.map +0 -1
  259. package/dist/components/doomFooter.mjs.map +0 -1
  260. package/dist/components/doomHeader.cjs.map +0 -1
  261. package/dist/components/doomHeader.d.cts.map +0 -1
  262. package/dist/components/doomHeader.d.mts.map +0 -1
  263. package/dist/components/doomHeader.mjs.map +0 -1
  264. package/dist/components/doomOverlay.cjs.map +0 -1
  265. package/dist/components/doomOverlay.d.cts.map +0 -1
  266. package/dist/components/doomOverlay.d.mts.map +0 -1
  267. package/dist/components/doomOverlay.mjs.map +0 -1
  268. package/dist/components/leaderHints.cjs.map +0 -1
  269. package/dist/components/leaderHints.d.cts.map +0 -1
  270. package/dist/components/leaderHints.d.mts.map +0 -1
  271. package/dist/components/leaderHints.mjs.map +0 -1
  272. package/dist/components/toolsOverlay.cjs.map +0 -1
  273. package/dist/components/toolsOverlay.d.cts.map +0 -1
  274. package/dist/components/toolsOverlay.d.mts.map +0 -1
  275. package/dist/components/toolsOverlay.mjs.map +0 -1
  276. package/dist/config.cjs.map +0 -1
  277. package/dist/config.d.cts.map +0 -1
  278. package/dist/config.d.mts.map +0 -1
  279. package/dist/config.mjs.map +0 -1
  280. package/dist/extension.cjs.map +0 -1
  281. package/dist/extension.d.cts.map +0 -1
  282. package/dist/extension.d.mts.map +0 -1
  283. package/dist/extension.mjs.map +0 -1
  284. package/dist/extensionName.cjs.map +0 -1
  285. package/dist/extensionName.d.cts.map +0 -1
  286. package/dist/extensionName.d.mts.map +0 -1
  287. package/dist/extensionName.mjs.map +0 -1
  288. package/dist/footer.cjs.map +0 -1
  289. package/dist/footer.d.cts.map +0 -1
  290. package/dist/footer.d.mts.map +0 -1
  291. package/dist/footer.mjs.map +0 -1
  292. package/dist/harnessMetadata.cjs.map +0 -1
  293. package/dist/harnessMetadata.d.cts.map +0 -1
  294. package/dist/harnessMetadata.d.mts.map +0 -1
  295. package/dist/harnessMetadata.mjs.map +0 -1
  296. package/dist/leaderRegistry.cjs.map +0 -1
  297. package/dist/leaderRegistry.d.cts.map +0 -1
  298. package/dist/leaderRegistry.d.mts.map +0 -1
  299. package/dist/leaderRegistry.mjs.map +0 -1
  300. package/dist/logSinkTelemetry.cjs.map +0 -1
  301. package/dist/logSinkTelemetry.d.cts.map +0 -1
  302. package/dist/logSinkTelemetry.d.mts.map +0 -1
  303. package/dist/logSinkTelemetry.mjs.map +0 -1
  304. package/dist/mode.cjs +0 -2
  305. package/dist/mode.cjs.map +0 -1
  306. package/dist/mode.d.cts +0 -5
  307. package/dist/mode.d.cts.map +0 -1
  308. package/dist/mode.d.mts +0 -5
  309. package/dist/mode.d.mts.map +0 -1
  310. package/dist/mode.mjs +0 -2
  311. package/dist/mode.mjs.map +0 -1
  312. package/dist/rendering.cjs.map +0 -1
  313. package/dist/rendering.d.cts.map +0 -1
  314. package/dist/rendering.d.mts.map +0 -1
  315. package/dist/rendering.mjs.map +0 -1
  316. package/dist/theme.cjs.map +0 -1
  317. package/dist/theme.d.cts.map +0 -1
  318. package/dist/theme.d.mts.map +0 -1
  319. package/dist/theme.mjs.map +0 -1
  320. package/dist/toolInventory.cjs.map +0 -1
  321. package/dist/toolInventory.d.cts.map +0 -1
  322. package/dist/toolInventory.d.mts.map +0 -1
  323. package/dist/toolInventory.mjs.map +0 -1
  324. package/dist/uiState.cjs.map +0 -1
  325. package/dist/uiState.d.cts.map +0 -1
  326. package/dist/uiState.d.mts.map +0 -1
  327. package/dist/uiState.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtinToolRender.mjs","names":[],"sources":["../../src/tui/builtinToolRender.ts"],"sourcesContent":["import {\n type EditToolDetails,\n type EditToolInput,\n type FindToolInput,\n type GrepToolInput,\n getLanguageFromPath,\n highlightCode,\n type LsToolInput,\n type ReadToolInput,\n type Theme,\n type WriteToolInput,\n} from '@earendil-works/pi-coding-agent';\nimport { type Component, wrapTextWithAnsi } from '@earendil-works/pi-tui';\nimport { gutterWidth, renderLineNumber } from './codeGutter.ts';\nimport { renderDoomDiff } from './diffRender.ts';\nimport { DoomToolCall, renderToolHeading } from './toolChrome.ts';\n\nconst COLLAPSED_FILE_LINES = 10;\n\ntype Highlighter = (code: string, language?: string) => string[];\ntype LanguageResolver = (path: string) => string | undefined;\n\nexport interface ToolPreviewOptions {\n expanded?: boolean;\n isError?: boolean;\n}\n\nexport interface WritePreviewOptions extends ToolPreviewOptions {\n argsStreaming?: boolean;\n}\n\nexport interface TextToolResult {\n content?: ReadonlyArray<{ type: string; text?: string }>;\n}\n\nexport interface EditToolResult extends TextToolResult {\n details?: EditToolDetails;\n}\n\n/** Self-owned result shell shared by Pi's native file and search tools. */\nclass BuiltinToolContent implements Component {\n constructor(private readonly text: string) {}\n\n render(width: number): string[] {\n if (width <= 0 || this.text.length === 0) return [];\n return wrapTextWithAnsi(this.text, width);\n }\n\n invalidate(): void {\n // Content is immutable and reflows from the current width on every render.\n }\n}\n\nclass BuiltinToolResultFrame implements Component {\n constructor(\n private content: Component,\n private readonly theme: Theme,\n ) {}\n\n setContent(content: Component): void {\n this.content = content;\n }\n\n getContent(): Component {\n return this.content;\n }\n\n render(width: number): string[] {\n if (width <= 0) return [];\n if (width < 3) return this.content.render(width);\n\n const contentWidth = width - 2;\n const lines = this.content.render(contentWidth).map((line) => ` ${line}`);\n if (lines.length === 0) return [];\n\n const divider = this.theme.fg('borderMuted', '─'.repeat(contentWidth));\n return [` ${divider}`, ...lines, ''];\n }\n\n invalidate(): void {\n this.content.invalidate();\n }\n}\n\n/** Recover the native renderer's component from the shell returned on its previous pass. */\nexport function previousBuiltinResult(component: Component | undefined): Component | undefined {\n return component instanceof BuiltinToolResultFrame ? component.getContent() : undefined;\n}\n\n/** Reuse the shell while allowing Pi's native renderer to reuse its own inner component. */\nexport function frameBuiltinResult(content: Component, theme: Theme, previous: Component | undefined): Component {\n if (previous instanceof BuiltinToolResultFrame) {\n previous.setContent(content);\n return previous;\n }\n return new BuiltinToolResultFrame(content, theme);\n}\n\nfunction renderCall(label: string, primary: string, details: readonly string[], theme: Theme): Component {\n let text = renderToolHeading(label, primary, theme);\n if (details.length > 0) text += ` ${theme.fg('muted', `· ${details.join(' · ')}`)}`;\n return new DoomToolCall(text);\n}\n\nfunction optionalCount(value: number | undefined, unit: string): string | undefined {\n return value === undefined ? undefined : `${value} ${unit}`;\n}\n\nfunction compactDetails(values: ReadonlyArray<string | undefined>): string[] {\n return values.filter((value): value is string => value !== undefined && value.length > 0);\n}\n\nexport function renderReadCall(args: ReadToolInput, theme: Theme): Component {\n return renderCall(\n 'read',\n args.path,\n compactDetails([args.offset === undefined ? undefined : `from ${args.offset}`, optionalCount(args.limit, 'lines')]),\n theme,\n );\n}\n\nexport function renderEditCall(args: EditToolInput, theme: Theme): Component {\n const count = args.edits.length;\n return renderCall('edit', args.path, [`${count} edit${count === 1 ? '' : 's'}`], theme);\n}\n\n/**\n * Read appends a bracketed pagination note after a blank line. Detach it so the\n * gutter numbers file content only, and mutate the caller's line list to match.\n */\nfunction takeTrailingNote(lines: string[]): string | undefined {\n const last = lines.at(-1);\n if (last === undefined || !last.startsWith('[') || !last.endsWith(']')) return undefined;\n\n lines.pop();\n while (lines.at(-1) === '') lines.pop();\n return last;\n}\n\nfunction textContent(result: TextToolResult): string {\n return (result.content ?? [])\n .filter((block) => block.type === 'text')\n .map((block) => block.text ?? '')\n .join('\\n')\n .replaceAll('\\t', ' ')\n .replaceAll('\\r\\n', '\\n');\n}\n\nexport function renderEditResult(\n args: EditToolInput,\n result: EditToolResult,\n options: ToolPreviewOptions,\n theme: Theme,\n highlight: Highlighter = highlightCode,\n resolveLanguage: LanguageResolver = getLanguageFromPath,\n): Component {\n if (options.isError) return new BuiltinToolContent(theme.fg('error', textContent(result)));\n\n const diff = result.details?.diff;\n // A successful edit whose details never arrived has nothing worth a frame.\n if (diff === undefined || diff.length === 0) return new BuiltinToolContent('');\n return renderDoomDiff(diff, theme, resolveLanguage(args.path), highlight);\n}\n\nexport function renderWriteCall(\n args: WriteToolInput,\n theme: Theme,\n options: WritePreviewOptions = {},\n highlight: Highlighter = highlightCode,\n resolveLanguage: LanguageResolver = getLanguageFromPath,\n): Component {\n const heading = renderToolHeading('write', args.path, theme);\n const details = theme.fg('muted', `· ${args.content.length.toLocaleString('en-US')} chars`);\n if (args.content.length === 0) return new DoomToolCall(`${heading} ${details}`);\n\n const normalizedLines = args.content.replaceAll('\\t', ' ').replaceAll('\\r\\n', '\\n').split('\\n');\n while (normalizedLines.at(-1) === '') normalizedLines.pop();\n const showLiveTail = options.argsStreaming === true && options.expanded !== true;\n const visibleLines = options.expanded\n ? normalizedLines\n : showLiveTail\n ? normalizedLines.slice(-COLLAPSED_FILE_LINES)\n : normalizedLines.slice(0, COLLAPSED_FILE_LINES);\n const language = resolveLanguage(args.path);\n const highlighted = language\n ? highlight(visibleLines.join('\\n'), language)\n : visibleLines.map((line) => theme.fg('toolOutput', line));\n const hiddenLineCount = normalizedLines.length - visibleLines.length;\n // A live tail shows the file's end, so numbering starts where the tail does.\n const firstVisibleLineNumber = showLiveTail ? hiddenLineCount + 1 : 1;\n const numberWidth = gutterWidth(firstVisibleLineNumber + visibleLines.length - 1);\n const preview = highlighted.map(\n (line, index) => `${renderLineNumber(firstVisibleLineNumber + index, numberWidth, theme)} ${line}`,\n );\n const previewLines = showLiveTail\n ? [\n ...(hiddenLineCount > 0 ? [theme.fg('muted', `… ${hiddenLineCount} earlier lines · live tail`)] : []),\n ...preview,\n ]\n : [\n ...preview,\n ...(hiddenLineCount > 0 ? [theme.fg('muted', `… ${hiddenLineCount} more lines · ctrl+o expand`)] : []),\n ];\n return new DoomToolCall([`${heading} ${details}`, '', ...previewLines].join('\\n'));\n}\n\nexport function renderReadResult(\n args: ReadToolInput,\n result: TextToolResult,\n options: ToolPreviewOptions,\n theme: Theme,\n highlight: Highlighter = highlightCode,\n resolveLanguage: LanguageResolver = getLanguageFromPath,\n): Component {\n const normalizedLines = textContent(result).split('\\n');\n while (normalizedLines.at(-1) === '') normalizedLines.pop();\n if (options.isError) {\n return new BuiltinToolContent(normalizedLines.map((line) => theme.fg('error', line)).join('\\n'));\n }\n\n const note = takeTrailingNote(normalizedLines);\n const visibleLines = options.expanded ? normalizedLines : normalizedLines.slice(0, COLLAPSED_FILE_LINES);\n const language = resolveLanguage(args.path);\n const highlighted = language\n ? highlight(visibleLines.join('\\n'), language)\n : visibleLines.map((line) => theme.fg('toolOutput', line));\n // Read starts at `offset`, so the gutter has to agree with the file, not the excerpt.\n const firstLineNumber = args.offset ?? 1;\n const numberWidth = gutterWidth(firstLineNumber + visibleLines.length - 1);\n const preview = highlighted.map(\n (line, index) => `${renderLineNumber(firstLineNumber + index, numberWidth, theme)} ${line}`,\n );\n const remaining = normalizedLines.length - visibleLines.length;\n if (remaining > 0) preview.push(theme.fg('muted', `… ${remaining} more lines · ctrl+o expand`));\n if (note !== undefined) preview.push(theme.fg('muted', note));\n return new BuiltinToolContent(preview.join('\\n'));\n}\n\nexport function renderGrepCall(args: GrepToolInput, theme: Theme): Component {\n return renderCall(\n 'grep',\n args.pattern,\n compactDetails([\n args.path ?? '.',\n args.glob,\n args.ignoreCase === true ? 'ignore case' : undefined,\n optionalCount(args.limit, 'matches'),\n ]),\n theme,\n );\n}\n\nexport function renderFindCall(args: FindToolInput, theme: Theme): Component {\n return renderCall(\n 'find',\n args.pattern,\n compactDetails([args.path ?? '.', optionalCount(args.limit, 'results')]),\n theme,\n );\n}\n\nexport function renderLsCall(args: LsToolInput, theme: Theme): Component {\n return renderCall('ls', args.path ?? '.', compactDetails([optionalCount(args.limit, 'entries')]), theme);\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e){return String(Math.max(1,e)).length}function t(e,t,n,r=!1){let i=r?`muted`:`dim`;return n.fg(i,String(e).padStart(t,` `))}exports.gutterWidth=e,exports.renderLineNumber=t;
2
+ //# sourceMappingURL=codeGutter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeGutter.cjs","names":[],"sources":["../../src/tui/codeGutter.ts"],"sourcesContent":["import type { Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\n/** Doom Emacs recesses `line-number` and brightens `line-number-current-line`. */\nconst CONTEXT_LINE_NUMBER_COLOR: ThemeColor = 'dim';\nconst CHANGED_LINE_NUMBER_COLOR: ThemeColor = 'muted';\n\n/** Right-align every number in a block so the code column never goes ragged. */\nexport function gutterWidth(highestLineNumber: number): number {\n return String(Math.max(1, highestLineNumber)).length;\n}\n\n/** Recessive right-aligned line number; blank numbers keep the column reserved. */\nexport function renderLineNumber(value: number | string, width: number, theme: Theme, changed = false): string {\n const color = changed ? CHANGED_LINE_NUMBER_COLOR : CONTEXT_LINE_NUMBER_COLOR;\n return theme.fg(color, String(value).padStart(width, ' '));\n}\n"],"mappings":"AAOA,SAAgB,EAAY,EAAmC,CAC7D,OAAO,OAAO,KAAK,IAAI,EAAG,CAAiB,CAAC,CAAC,CAAC,MAChD,CAGA,SAAgB,EAAiB,EAAwB,EAAe,EAAc,EAAU,GAAe,CAC7G,IAAM,EAAQ,EAAU,QAA4B,MACpD,OAAO,EAAM,GAAG,EAAO,OAAO,CAAK,CAAC,CAAC,SAAS,EAAO,GAAG,CAAC,CAC3D"}
@@ -0,0 +1,2 @@
1
+ function e(e){return String(Math.max(1,e)).length}function t(e,t,n,r=!1){let i=r?`muted`:`dim`;return n.fg(i,String(e).padStart(t,` `))}export{e as gutterWidth,t as renderLineNumber};
2
+ //# sourceMappingURL=codeGutter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeGutter.mjs","names":[],"sources":["../../src/tui/codeGutter.ts"],"sourcesContent":["import type { Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\n/** Doom Emacs recesses `line-number` and brightens `line-number-current-line`. */\nconst CONTEXT_LINE_NUMBER_COLOR: ThemeColor = 'dim';\nconst CHANGED_LINE_NUMBER_COLOR: ThemeColor = 'muted';\n\n/** Right-align every number in a block so the code column never goes ragged. */\nexport function gutterWidth(highestLineNumber: number): number {\n return String(Math.max(1, highestLineNumber)).length;\n}\n\n/** Recessive right-aligned line number; blank numbers keep the column reserved. */\nexport function renderLineNumber(value: number | string, width: number, theme: Theme, changed = false): string {\n const color = changed ? CHANGED_LINE_NUMBER_COLOR : CONTEXT_LINE_NUMBER_COLOR;\n return theme.fg(color, String(value).padStart(width, ' '));\n}\n"],"mappings":"AAOA,SAAgB,EAAY,EAAmC,CAC7D,OAAO,OAAO,KAAK,IAAI,EAAG,CAAiB,CAAC,CAAC,CAAC,MAChD,CAGA,SAAgB,EAAiB,EAAwB,EAAe,EAAc,EAAU,GAAe,CAC7G,IAAM,EAAQ,EAAU,QAA4B,MACpD,OAAO,EAAM,GAAG,EAAO,OAAO,CAAK,CAAC,CAAC,SAAS,EAAO,GAAG,CAAC,CAC3D"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configOverlay.cjs","names":["fitStyledLine","visibleWidth","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigRegistry, DoomConfigSectionView } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\nimport { fitStyledLine } from './rendering.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigRegistry;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed `source\u0000section\u0000field`. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigRegistry, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') {\n this.handleEditInput(data);\n return;\n }\n if (this.mode === 'choices') {\n this.handleChoiceInput(data);\n return;\n }\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) this.invoke(ACTION_ABORT);\n else this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n if (this.pane === 'sections') this.moveSection(-1);\n else this.moveField(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n if (this.pane === 'sections') this.moveSection(1);\n else this.moveField(1);\n return;\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) {\n this.invoke(offered.action);\n return;\n }\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n this.activate();\n return;\n }\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.moveChoice(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.moveChoice(1);\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fitRow(title, width)) : title);\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fitRow(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fitRow(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fitRow(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fitRow(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(\n `${INDENT}${this.theme.fg('warning', fitRow(`waiting for input ${prompt}`, width - INDENT.length))}`,\n );\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fitRow(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return this.renderStacked(width, height, right);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigRegistry): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configOverlay.d.cts","names":[],"sources":["../../src/tui/configOverlay.ts"],"mappings":";;;;cAoHa,+BAA+B;mBACzB;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;;mBAES;UACT;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,UAAU,oBAAoB,OAAO;;UAU5E;EAKR;UAIQ;UAeA;UAIA;UAIA;UAIA;UAIA;UAIA;UAeA;;UAUA;UAUA;EASR,YAAY;UAYJ;UAsDA;UAwBA;UA0BA;UAwBA;;;;;;;UAyBA;;UAeA;UAKA;UAOA;UA6DA;UA4BA;UA2CA;YASE,aAAa;YAcb,WAAW,eAAe;UAmB5B;;iBAeY,kBAAkB,KAAK,kBAAkB,UAAU,qBAAqB"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configOverlay.d.mts","names":[],"sources":["../../src/tui/configOverlay.ts"],"mappings":";;;;cAoHa,+BAA+B;mBACzB;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;;mBAES;UACT;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,UAAU,oBAAoB,OAAO;;UAU5E;EAKR;UAIQ;UAeA;UAIA;UAIA;UAIA;UAIA;UAIA;UAeA;;UAUA;UAUA;EASR,YAAY;UAYJ;UAsDA;UAwBA;UA0BA;UAwBA;;;;;;;UAyBA;;UAeA;UAKA;UAOA;UA6DA;UA4BA;UA2CA;YASE,aAAa;YAcb,WAAW,eAAe;UAmB5B;;iBAeY,kBAAkB,KAAK,kBAAkB,UAAU,qBAAqB"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configOverlay.mjs","names":[],"sources":["../../src/tui/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigRegistry, DoomConfigSectionView } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\nimport { fitStyledLine } from './rendering.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigRegistry;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed `source\u0000section\u0000field`. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigRegistry, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') {\n this.handleEditInput(data);\n return;\n }\n if (this.mode === 'choices') {\n this.handleChoiceInput(data);\n return;\n }\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) this.invoke(ACTION_ABORT);\n else this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n if (this.pane === 'sections') this.moveSection(-1);\n else this.moveField(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n if (this.pane === 'sections') this.moveSection(1);\n else this.moveField(1);\n return;\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) {\n this.invoke(offered.action);\n return;\n }\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n this.activate();\n return;\n }\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.moveChoice(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.moveChoice(1);\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fitRow(title, width)) : title);\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fitRow(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fitRow(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fitRow(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fitRow(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(\n `${INDENT}${this.theme.fg('warning', fitRow(`waiting for input ${prompt}`, width - INDENT.length))}`,\n );\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fitRow(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return this.renderStacked(width, height, right);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigRegistry): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ const e=require("./rendering.cjs"),t=require("./codeGutter.cjs");let n=require("@earendil-works/pi-coding-agent");const r=/^([+\- ])( *\d*) (.*)$/u,i={"+":`toolSuccessBg`,"-":`toolErrorBg`},a={"+":`toolDiffAdded`,"-":`toolDiffRemoved`};function o(e){return e!==` `}function s(e){return e===`+`||e===`-`?e:` `}function c(e){return e.split(`
2
+ `).map(e=>{let t=e.replaceAll(` `,` `),n=r.exec(t);return n?{marker:s(n[1]),lineNumber:n[2].trim(),content:n[3]}:{marker:` `,lineNumber:``,content:t}})}function l(e,t,n,r){if(t!==void 0){let n=r(e.map(e=>e.content).join(`
3
+ `),t);if(n.length===e.length)return n}return e.map(e=>n.fg(o(e.marker)?`toolOutput`:`toolDiffContext`,e.content))}var u=class{rows;contents;theme;gutter;constructor(e,t,n){this.rows=e,this.contents=t,this.theme=n,this.gutter=e.reduce((e,t)=>Math.max(e,t.lineNumber.length),1)}render(e){return e<=0?[]:this.rows.map((t,n)=>this.renderRow(t,this.contents[n]??t.content,e))}invalidate(){}renderRow({marker:n,lineNumber:r},s,c){let l=t.renderLineNumber(r,this.gutter,this.theme,o(n));if(!o(n))return e.fitStyledLine(`${l} ${s}`,c);let u=e.fitStyledLine(`${l} ${this.theme.fg(a[n],n)}${s}`,c);return this.theme.bg(i[n],u)}};function d(e,t,r,i=n.highlightCode){let a=c(e);return new u(a,l(a,r,t,i),t)}exports.renderDoomDiff=d;
4
+ //# sourceMappingURL=diffRender.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diffRender.cjs","names":["renderLineNumber","fitStyledLine","highlightCode"],"sources":["../../src/tui/diffRender.ts"],"sourcesContent":["import { highlightCode, type Theme, type ThemeColor } from '@earendil-works/pi-coding-agent';\nimport type { Component } from '@earendil-works/pi-tui';\nimport { renderLineNumber } from './codeGutter.ts';\nimport { fitStyledLine } from './rendering.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\n/** Pi emits `+12 text`, `-12 text`, ` 12 text`, and a blank-numbered ` ... ` elision between hunks. */\nconst DIFF_ROW_PATTERN = /^([+\\- ])( *\\d*) (.*)$/u;\nconst TAB_REPLACEMENT = ' ';\nconst CONTEXT_MARKER = ' ';\n\ntype ChangedMarker = '+' | '-';\ntype DiffMarker = ChangedMarker | typeof CONTEXT_MARKER;\n\ninterface DiffRow {\n marker: DiffMarker;\n lineNumber: string;\n content: string;\n}\n\n/**\n * Doom Emacs carries a diff on the background and leaves the foreground to\n * syntax highlighting, the way Magit's `magit-diff-added`/`-removed` faces do.\n * The theme's success and error backgrounds are its green- and red-tinted\n * washes, so any theme yields a readable added/removed pair.\n */\nconst ROW_BACKGROUND: Readonly<Record<ChangedMarker, ThemeBg>> = {\n '+': 'toolSuccessBg',\n '-': 'toolErrorBg',\n};\n\nconst MARKER_COLOR: Readonly<Record<ChangedMarker, ThemeColor>> = {\n '+': 'toolDiffAdded',\n '-': 'toolDiffRemoved',\n};\n\ntype Highlighter = (code: string, language?: string) => string[];\n\nfunction isChangedMarker(marker: DiffMarker): marker is ChangedMarker {\n return marker !== CONTEXT_MARKER;\n}\n\nfunction toDiffMarker(value: string): DiffMarker {\n return value === '+' || value === '-' ? value : CONTEXT_MARKER;\n}\n\nfunction parseDiffRows(diff: string): DiffRow[] {\n return diff.split('\\n').map((line) => {\n const expanded = line.replaceAll('\\t', TAB_REPLACEMENT);\n const match = DIFF_ROW_PATTERN.exec(expanded);\n if (!match) return { marker: CONTEXT_MARKER, lineNumber: '', content: expanded };\n return { marker: toDiffMarker(match[1]!), lineNumber: match[2]!.trim(), content: match[3]! };\n });\n}\n\n/**\n * Highlight the diff as one block so multi-line constructs keep their context.\n * Interleaved removals make the block imperfect source, which the highlighter\n * tolerates; a line count mismatch falls back to flat diff coloring.\n */\nfunction decorateRows(\n rows: readonly DiffRow[],\n language: string | undefined,\n theme: Theme,\n highlight: Highlighter,\n): string[] {\n if (language !== undefined) {\n const highlighted = highlight(rows.map((row) => row.content).join('\\n'), language);\n if (highlighted.length === rows.length) return highlighted;\n }\n return rows.map((row) => theme.fg(isChangedMarker(row.marker) ? 'toolOutput' : 'toolDiffContext', row.content));\n}\n\n/** A width-aware diff whose added and removed rows carry a full-width color band. */\nclass DoomDiff implements Component {\n private readonly gutter: number;\n\n constructor(\n private readonly rows: readonly DiffRow[],\n private readonly contents: readonly string[],\n private readonly theme: Theme,\n ) {\n // Pi already pads its numbers, so the widest one sizes the column.\n this.gutter = rows.reduce((widest, row) => Math.max(widest, row.lineNumber.length), 1);\n }\n\n render(width: number): string[] {\n if (width <= 0) return [];\n return this.rows.map((row, index) => this.renderRow(row, this.contents[index] ?? row.content, width));\n }\n\n invalidate(): void {\n // Rows are immutable; only the color band width follows the terminal.\n }\n\n private renderRow({ marker, lineNumber }: DiffRow, content: string, width: number): string {\n const number = renderLineNumber(lineNumber, this.gutter, this.theme, isChangedMarker(marker));\n if (!isChangedMarker(marker)) return fitStyledLine(`${number} ${CONTEXT_MARKER}${content}`, width);\n\n const band = fitStyledLine(`${number} ${this.theme.fg(MARKER_COLOR[marker], marker)}${content}`, width);\n return this.theme.bg(ROW_BACKGROUND[marker], band);\n }\n}\n\n/** Render Pi's display diff with Doom's background-banded added and removed rows. */\nexport function renderDoomDiff(\n diff: string,\n theme: Theme,\n language: string | undefined,\n highlight: Highlighter = highlightCode,\n): Component {\n const rows = parseDiffRows(diff);\n return new DoomDiff(rows, decorateRows(rows, language, theme, highlight), theme);\n}\n"],"mappings":"kHASA,MAAM,EAAmB,0BAmBnB,EAA2D,CAC/D,IAAK,gBACL,IAAK,aACP,EAEM,EAA4D,CAChE,IAAK,gBACL,IAAK,iBACP,EAIA,SAAS,EAAgB,EAA6C,CACpE,OAAO,IAAW,GACpB,CAEA,SAAS,EAAa,EAA2B,CAC/C,OAAO,IAAU,KAAO,IAAU,IAAM,EAAQ,GAClD,CAEA,SAAS,EAAc,EAAyB,CAC9C,OAAO,EAAK,MAAM;CAAI,CAAC,CAAC,IAAK,GAAS,CACpC,IAAM,EAAW,EAAK,WAAW,IAAM,IAAe,EAChD,EAAQ,EAAiB,KAAK,CAAQ,EAE5C,OADK,EACE,CAAE,OAAQ,EAAa,EAAM,EAAG,EAAG,WAAY,EAAM,EAAE,CAAE,KAAK,EAAG,QAAS,EAAM,EAAI,EADxE,CAAE,OAAQ,IAAgB,WAAY,GAAI,QAAS,CAAS,CAEjF,CAAC,CACH,CAOA,SAAS,EACP,EACA,EACA,EACA,EACU,CACV,GAAI,IAAa,IAAA,GAAW,CAC1B,IAAM,EAAc,EAAU,EAAK,IAAK,GAAQ,EAAI,OAAO,CAAC,CAAC,KAAK;CAAI,EAAG,CAAQ,EACjF,GAAI,EAAY,SAAW,EAAK,OAAQ,OAAO,CACjD,CACA,OAAO,EAAK,IAAK,GAAQ,EAAM,GAAG,EAAgB,EAAI,MAAM,EAAI,aAAe,kBAAmB,EAAI,OAAO,CAAC,CAChH,CAGA,IAAM,EAAN,KAAoC,CAIf,KACA,SACA,MALnB,OAEA,YACE,EACA,EACA,EACA,CAHiB,KAAA,KAAA,EACA,KAAA,SAAA,EACA,KAAA,MAAA,EAGjB,KAAK,OAAS,EAAK,QAAQ,EAAQ,IAAQ,KAAK,IAAI,EAAQ,EAAI,WAAW,MAAM,EAAG,CAAC,CACvF,CAEA,OAAO,EAAyB,CAE9B,OADI,GAAS,EAAU,CAAC,EACjB,KAAK,KAAK,KAAK,EAAK,IAAU,KAAK,UAAU,EAAK,KAAK,SAAS,IAAU,EAAI,QAAS,CAAK,CAAC,CACtG,CAEA,YAAmB,CAEnB,CAEA,UAAkB,CAAE,SAAQ,cAAuB,EAAiB,EAAuB,CACzF,IAAM,EAASA,EAAAA,iBAAiB,EAAY,KAAK,OAAQ,KAAK,MAAO,EAAgB,CAAM,CAAC,EAC5F,GAAI,CAAC,EAAgB,CAAM,EAAG,OAAOC,EAAAA,cAAc,GAAG,EAAO,IAAoB,IAAW,CAAK,EAEjG,IAAM,EAAOA,EAAAA,cAAc,GAAG,EAAO,GAAG,KAAK,MAAM,GAAG,EAAa,GAAS,CAAM,IAAI,IAAW,CAAK,EACtG,OAAO,KAAK,MAAM,GAAG,EAAe,GAAS,CAAI,CACnD,CACF,EAGA,SAAgB,EACd,EACA,EACA,EACA,EAAyBC,EAAAA,cACd,CACX,IAAM,EAAO,EAAc,CAAI,EAC/B,OAAO,IAAI,EAAS,EAAM,EAAa,EAAM,EAAU,EAAO,CAAS,EAAG,CAAK,CACjF"}
@@ -0,0 +1,4 @@
1
+ import{fitStyledLine as e}from"./rendering.mjs";import{renderLineNumber as t}from"./codeGutter.mjs";import{highlightCode as n}from"@earendil-works/pi-coding-agent";const r=/^([+\- ])( *\d*) (.*)$/u,i={"+":`toolSuccessBg`,"-":`toolErrorBg`},a={"+":`toolDiffAdded`,"-":`toolDiffRemoved`};function o(e){return e!==` `}function s(e){return e===`+`||e===`-`?e:` `}function c(e){return e.split(`
2
+ `).map(e=>{let t=e.replaceAll(` `,` `),n=r.exec(t);return n?{marker:s(n[1]),lineNumber:n[2].trim(),content:n[3]}:{marker:` `,lineNumber:``,content:t}})}function l(e,t,n,r){if(t!==void 0){let n=r(e.map(e=>e.content).join(`
3
+ `),t);if(n.length===e.length)return n}return e.map(e=>n.fg(o(e.marker)?`toolOutput`:`toolDiffContext`,e.content))}var u=class{rows;contents;theme;gutter;constructor(e,t,n){this.rows=e,this.contents=t,this.theme=n,this.gutter=e.reduce((e,t)=>Math.max(e,t.lineNumber.length),1)}render(e){return e<=0?[]:this.rows.map((t,n)=>this.renderRow(t,this.contents[n]??t.content,e))}invalidate(){}renderRow({marker:n,lineNumber:r},s,c){let l=t(r,this.gutter,this.theme,o(n));if(!o(n))return e(`${l} ${s}`,c);let u=e(`${l} ${this.theme.fg(a[n],n)}${s}`,c);return this.theme.bg(i[n],u)}};function d(e,t,r,i=n){let a=c(e);return new u(a,l(a,r,t,i),t)}export{d as renderDoomDiff};
4
+ //# sourceMappingURL=diffRender.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diffRender.mjs","names":[],"sources":["../../src/tui/diffRender.ts"],"sourcesContent":["import { highlightCode, type Theme, type ThemeColor } from '@earendil-works/pi-coding-agent';\nimport type { Component } from '@earendil-works/pi-tui';\nimport { renderLineNumber } from './codeGutter.ts';\nimport { fitStyledLine } from './rendering.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\n/** Pi emits `+12 text`, `-12 text`, ` 12 text`, and a blank-numbered ` ... ` elision between hunks. */\nconst DIFF_ROW_PATTERN = /^([+\\- ])( *\\d*) (.*)$/u;\nconst TAB_REPLACEMENT = ' ';\nconst CONTEXT_MARKER = ' ';\n\ntype ChangedMarker = '+' | '-';\ntype DiffMarker = ChangedMarker | typeof CONTEXT_MARKER;\n\ninterface DiffRow {\n marker: DiffMarker;\n lineNumber: string;\n content: string;\n}\n\n/**\n * Doom Emacs carries a diff on the background and leaves the foreground to\n * syntax highlighting, the way Magit's `magit-diff-added`/`-removed` faces do.\n * The theme's success and error backgrounds are its green- and red-tinted\n * washes, so any theme yields a readable added/removed pair.\n */\nconst ROW_BACKGROUND: Readonly<Record<ChangedMarker, ThemeBg>> = {\n '+': 'toolSuccessBg',\n '-': 'toolErrorBg',\n};\n\nconst MARKER_COLOR: Readonly<Record<ChangedMarker, ThemeColor>> = {\n '+': 'toolDiffAdded',\n '-': 'toolDiffRemoved',\n};\n\ntype Highlighter = (code: string, language?: string) => string[];\n\nfunction isChangedMarker(marker: DiffMarker): marker is ChangedMarker {\n return marker !== CONTEXT_MARKER;\n}\n\nfunction toDiffMarker(value: string): DiffMarker {\n return value === '+' || value === '-' ? value : CONTEXT_MARKER;\n}\n\nfunction parseDiffRows(diff: string): DiffRow[] {\n return diff.split('\\n').map((line) => {\n const expanded = line.replaceAll('\\t', TAB_REPLACEMENT);\n const match = DIFF_ROW_PATTERN.exec(expanded);\n if (!match) return { marker: CONTEXT_MARKER, lineNumber: '', content: expanded };\n return { marker: toDiffMarker(match[1]!), lineNumber: match[2]!.trim(), content: match[3]! };\n });\n}\n\n/**\n * Highlight the diff as one block so multi-line constructs keep their context.\n * Interleaved removals make the block imperfect source, which the highlighter\n * tolerates; a line count mismatch falls back to flat diff coloring.\n */\nfunction decorateRows(\n rows: readonly DiffRow[],\n language: string | undefined,\n theme: Theme,\n highlight: Highlighter,\n): string[] {\n if (language !== undefined) {\n const highlighted = highlight(rows.map((row) => row.content).join('\\n'), language);\n if (highlighted.length === rows.length) return highlighted;\n }\n return rows.map((row) => theme.fg(isChangedMarker(row.marker) ? 'toolOutput' : 'toolDiffContext', row.content));\n}\n\n/** A width-aware diff whose added and removed rows carry a full-width color band. */\nclass DoomDiff implements Component {\n private readonly gutter: number;\n\n constructor(\n private readonly rows: readonly DiffRow[],\n private readonly contents: readonly string[],\n private readonly theme: Theme,\n ) {\n // Pi already pads its numbers, so the widest one sizes the column.\n this.gutter = rows.reduce((widest, row) => Math.max(widest, row.lineNumber.length), 1);\n }\n\n render(width: number): string[] {\n if (width <= 0) return [];\n return this.rows.map((row, index) => this.renderRow(row, this.contents[index] ?? row.content, width));\n }\n\n invalidate(): void {\n // Rows are immutable; only the color band width follows the terminal.\n }\n\n private renderRow({ marker, lineNumber }: DiffRow, content: string, width: number): string {\n const number = renderLineNumber(lineNumber, this.gutter, this.theme, isChangedMarker(marker));\n if (!isChangedMarker(marker)) return fitStyledLine(`${number} ${CONTEXT_MARKER}${content}`, width);\n\n const band = fitStyledLine(`${number} ${this.theme.fg(MARKER_COLOR[marker], marker)}${content}`, width);\n return this.theme.bg(ROW_BACKGROUND[marker], band);\n }\n}\n\n/** Render Pi's display diff with Doom's background-banded added and removed rows. */\nexport function renderDoomDiff(\n diff: string,\n theme: Theme,\n language: string | undefined,\n highlight: Highlighter = highlightCode,\n): Component {\n const rows = parseDiffRows(diff);\n return new DoomDiff(rows, decorateRows(rows, language, theme, highlight), theme);\n}\n"],"mappings":"oKASA,MAAM,EAAmB,0BAmBnB,EAA2D,CAC/D,IAAK,gBACL,IAAK,aACP,EAEM,EAA4D,CAChE,IAAK,gBACL,IAAK,iBACP,EAIA,SAAS,EAAgB,EAA6C,CACpE,OAAO,IAAW,GACpB,CAEA,SAAS,EAAa,EAA2B,CAC/C,OAAO,IAAU,KAAO,IAAU,IAAM,EAAQ,GAClD,CAEA,SAAS,EAAc,EAAyB,CAC9C,OAAO,EAAK,MAAM;CAAI,CAAC,CAAC,IAAK,GAAS,CACpC,IAAM,EAAW,EAAK,WAAW,IAAM,IAAe,EAChD,EAAQ,EAAiB,KAAK,CAAQ,EAE5C,OADK,EACE,CAAE,OAAQ,EAAa,EAAM,EAAG,EAAG,WAAY,EAAM,EAAE,CAAE,KAAK,EAAG,QAAS,EAAM,EAAI,EADxE,CAAE,OAAQ,IAAgB,WAAY,GAAI,QAAS,CAAS,CAEjF,CAAC,CACH,CAOA,SAAS,EACP,EACA,EACA,EACA,EACU,CACV,GAAI,IAAa,IAAA,GAAW,CAC1B,IAAM,EAAc,EAAU,EAAK,IAAK,GAAQ,EAAI,OAAO,CAAC,CAAC,KAAK;CAAI,EAAG,CAAQ,EACjF,GAAI,EAAY,SAAW,EAAK,OAAQ,OAAO,CACjD,CACA,OAAO,EAAK,IAAK,GAAQ,EAAM,GAAG,EAAgB,EAAI,MAAM,EAAI,aAAe,kBAAmB,EAAI,OAAO,CAAC,CAChH,CAGA,IAAM,EAAN,KAAoC,CAIf,KACA,SACA,MALnB,OAEA,YACE,EACA,EACA,EACA,CAHiB,KAAA,KAAA,EACA,KAAA,SAAA,EACA,KAAA,MAAA,EAGjB,KAAK,OAAS,EAAK,QAAQ,EAAQ,IAAQ,KAAK,IAAI,EAAQ,EAAI,WAAW,MAAM,EAAG,CAAC,CACvF,CAEA,OAAO,EAAyB,CAE9B,OADI,GAAS,EAAU,CAAC,EACjB,KAAK,KAAK,KAAK,EAAK,IAAU,KAAK,UAAU,EAAK,KAAK,SAAS,IAAU,EAAI,QAAS,CAAK,CAAC,CACtG,CAEA,YAAmB,CAEnB,CAEA,UAAkB,CAAE,SAAQ,cAAuB,EAAiB,EAAuB,CACzF,IAAM,EAAS,EAAiB,EAAY,KAAK,OAAQ,KAAK,MAAO,EAAgB,CAAM,CAAC,EAC5F,GAAI,CAAC,EAAgB,CAAM,EAAG,OAAO,EAAc,GAAG,EAAO,IAAoB,IAAW,CAAK,EAEjG,IAAM,EAAO,EAAc,GAAG,EAAO,GAAG,KAAK,MAAM,GAAG,EAAa,GAAS,CAAM,IAAI,IAAW,CAAK,EACtG,OAAO,KAAK,MAAM,GAAG,EAAe,GAAS,CAAI,CACnD,CACF,EAGA,SAAgB,EACd,EACA,EACA,EACA,EAAyB,EACd,CACX,IAAM,EAAO,EAAc,CAAI,EAC/B,OAAO,IAAI,EAAS,EAAM,EAAa,EAAM,EAAU,EAAO,CAAS,EAAG,CAAK,CACjF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../services/leader/leaderRegistry.cjs"),t=require("./rendering.cjs"),n=require("../types/harnessMetadata.cjs");let r=require("@earendil-works/pi-tui"),i=require("@earendil-works/pi-coding-agent");const a=`borderAccent`,o=`accent`,s={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},c={active:!1,prefix:[],label:``,options:[]};var l=class extends i.CustomEditor{uiState;onSnapshot;chromeTheme;leaderRegistry;majorMode;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,r,i,a,o,c,l=s,u={}){super(t,r,i,c),this.uiState=a,this.onSnapshot=o,this.chromeTheme=l,this.leaderRegistry=u.registry??new e.DoomLeaderRegistry,this.majorMode=u.majorMode?.trim()||n.readDoomHarnessMetadata().majorMode,this.isCommandAvailable=u.isCommandAvailable??(()=>!0),this.onUnavailableCommand=u.onUnavailableCommand??(()=>void 0),this.onUnavailableAction=u.onUnavailableAction??(()=>void 0),this.onLeaderAction=u.onLeaderAction,this.unsubscribeLeaderRegistry=this.leaderRegistry.subscribe(()=>{this.leaderActive&&this.publishLeader()}),this.unsubscribeUiState=this.uiState.subscribe(()=>t.requestRender())}handleInput(e){let t=(0,r.parseKey)(e);if(this.leaderActive){this.handleLeaderInput(t);return}if(t===`space`&&this.getText().length===0||t===`ctrl+space`){this.startLeader();return}super.handleInput(e)}render(e){if(e<12)return super.render(e);let n=e-4,r=super.render(n);if(r.length<3)return r.map(n=>t.fitLine(n,e));let i=this.findBottomBorder(r,n),s=r.slice(1,i),c=r.slice(i+1),l=this.chromeTheme.fg(a,`│`),u=this.chromeTheme.fg(a,`╭${`─`.repeat(e-2)}╮`),d=this.chromeTheme.fg(a,`╰${`─`.repeat(e-2)}╯`),f=this.editorLine(t.alignLine(` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,this.chromeTheme.fg(`dim`,`${this.editorStatus()} `),e-2),e,l),p=s.map((t,n)=>{let r=n===0?this.chromeTheme.bold(this.chromeTheme.fg(o,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,l)});return[u,f,`${this.chromeTheme.fg(a,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(a,`┤`)}`,...p,d,...c.map(n=>t.fitLine(` ${n}`,e))]}dispose(){this.unsubscribeLeaderRegistry(),this.unsubscribeUiState(),this.cancelLeader()}startLeader(){this.leaderActive=!0,this.leaderPath=[],this.publishLeader()}cancelLeader(){this.leaderActive=!1,this.leaderPath=[],this.publish(c)}handleLeaderInput(e){if(e===`escape`){this.cancelLeader();return}if(e===`backspace`){this.leaderPath.length===0?this.cancelLeader():(this.leaderPath.pop(),this.publishLeader());return}if(!e||e.length!==1){this.cancelLeader();return}let t=e.toLowerCase(),n=this.currentGroup()?.options.find(e=>e.key===t);if(!n){this.cancelLeader();return}if(n.hasChildren){this.leaderPath.push(t),this.publishLeader();return}if(!n.action){this.cancelLeader();return}this.cancelLeader(),this.execute(n.action)}currentGroup(){return this.leaderRegistry.getGroup(this.leaderPath)}publishLeader(){let e=this.currentGroup();if(!e){this.cancelLeader();return}let t=this.leaderRegistry.getGroup([]);this.publish({active:!0,prefix:[`SPC`,...this.leaderPath],label:e.label,options:e.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}})),rootOptions:t?.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}}))??[]})}publish(e){this.uiState.setLeader(e),this.onSnapshot(e),this.tui.requestRender()}execute(e){if(e.type===`app`){let t=this.actionHandlers.get(e.action);if(t){t();return}if(e.action===`app.exit`){this.onCtrlD?.();return}this.onUnavailableAction(e.action);return}if(e.type===`command`){this.submitCommand(e.command.name,e.command.args);return}this.onLeaderAction?this.onLeaderAction(e.source,e.action.name):this.onUnavailableAction(`${e.source}:${e.action.name}`)}submitCommand(e,t){if(!this.isCommandAvailable(e)){this.onUnavailableCommand(e);return}let n=this.getText(),r=`/${e}${t?` ${t}`:``}`;this.setText(``);try{this.onSubmit?.(r)}finally{this.setText(n)}}modeLabel(){return this.leaderActive?`LEADER ${[`SPC`,...this.leaderPath].join(` `)}`:this.majorMode.toUpperCase()}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:o;return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(t,e)))}modeNames(){let e=this.uiState.getModes();if(e.length===0)return``;let t=this.chromeTheme.fg(`dim`,` · `);return` ${e.map(e=>this.chromeTheme.fg(o,e.label.toLowerCase())).join(t)}`}domainNames(){let e=n.readDoomHarnessMetadata().domains;return e.length===0?``:this.chromeTheme.fg(`dim`,` ${e.join(`, `)}`)}editorStatus(){if(this.leaderActive)return`draft preserved · esc cancel`;let e=this.getText().length;return e===0?`draft 0 · ^SPC leader · SPC shortcut`:`draft ${e} · ^SPC leader`}editorLine(e,n,r){return t.frameLine(t.padLine(e,n-2),n,r,r)}findBottomBorder(e,t){let n=Math.max(1,t-10),r=e.findIndex((e,t)=>t!==0&&e.split(`─`).length-1>=n);return r>0?r:e.length-1}};exports.DoomEditor=l;
2
+ //# sourceMappingURL=doomEditor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomEditor.cjs","names":["CustomEditor","DoomLeaderRegistry","readDoomHarnessMetadata","parseKey","fitLine","alignLine","frameLine","padLine"],"sources":["../../src/tui/doomEditor.ts"],"sourcesContent":["import { type AppKeybinding, CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { type EditorOptions, type EditorTheme, parseKey, type TUI } from '@earendil-works/pi-tui';\nimport { type DoomLeaderAction, type DoomLeaderGroup, DoomLeaderRegistry } from '../services/leader/leaderRegistry.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../services/state/uiState.ts';\nimport { readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, frameLine, padLine } from './rendering.ts';\n\nexport interface DoomEditorLeaderOptions {\n /** Overrides the harness major mode shown while the leader is inactive. */\n majorMode?: string;\n registry?: DoomLeaderRegistry;\n isCommandAvailable?: (commandName: string) => boolean;\n onUnavailableCommand?: (commandName: string) => void;\n onUnavailableAction?: (action: string) => void;\n onLeaderAction?: (source: string, actionName: string) => void;\n}\n\ntype SnapshotHandler = (snapshot: LeaderSnapshot) => void;\ntype ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;\n\nconst MIN_CHROME_WIDTH = 12;\nconst EDITOR_CHROME_INSET = 4;\nconst FRAME_OVERHEAD = 2;\nconst BORDER_THRESHOLD_INSET = 10;\nconst MIN_BORDER_SEGMENTS = 1;\nconst APP_EXIT_ACTION = 'app.exit';\nconst ESCAPE_KEY = 'escape';\nconst BACKSPACE_KEY = 'backspace';\nconst SPACE_KEY = 'space';\nconst GLOBAL_LEADER_KEY = 'ctrl+space';\nconst LEADER_PREFIX = 'SPC';\nconst BORDER_COLOR = 'borderAccent';\nconst DIM_COLOR = 'dim';\nconst ACCENT_COLOR = 'accent';\nconst HEADING_COLOR = 'mdHeading';\nconst MUTED_BORDER_COLOR = 'borderMuted';\n/** Between mode names in the badge row. */\nconst MODE_SEPARATOR = ' · ';\nconst DOMAIN_SEPARATOR = ', ';\nconst VERTICAL_BORDER = '│';\nconst HORIZONTAL_BORDER = '─';\nconst PLAIN_CHROME_THEME: ChromeTheme = {\n bg: (_color, text) => text,\n bold: (text) => text,\n fg: (_color, text) => text,\n inverse: (text) => text,\n};\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nexport class DoomEditor extends CustomEditor {\n private readonly chromeTheme: ChromeTheme;\n private readonly leaderRegistry: DoomLeaderRegistry;\n private readonly majorMode: string;\n private readonly isCommandAvailable: (commandName: string) => boolean;\n private readonly onUnavailableCommand: (commandName: string) => void;\n private readonly onUnavailableAction: (action: string) => void;\n private readonly onLeaderAction: ((source: string, actionName: string) => void) | undefined;\n private readonly unsubscribeLeaderRegistry: () => void;\n private readonly unsubscribeUiState: () => void;\n private leaderActive = false;\n private leaderPath: string[] = [];\n\n constructor(\n tui: TUI,\n editorTheme: EditorTheme,\n keybindings: KeybindingsManager,\n private readonly uiState: DoomUiState,\n private readonly onSnapshot: SnapshotHandler,\n options?: EditorOptions,\n chromeTheme: ChromeTheme = PLAIN_CHROME_THEME,\n leaderOptions: DoomEditorLeaderOptions = {},\n ) {\n super(tui, editorTheme, keybindings, options);\n this.chromeTheme = chromeTheme;\n this.leaderRegistry = leaderOptions.registry ?? new DoomLeaderRegistry();\n this.majorMode = leaderOptions.majorMode?.trim() || readDoomHarnessMetadata().majorMode;\n this.isCommandAvailable = leaderOptions.isCommandAvailable ?? (() => true);\n this.onUnavailableCommand = leaderOptions.onUnavailableCommand ?? (() => undefined);\n this.onUnavailableAction = leaderOptions.onUnavailableAction ?? (() => undefined);\n this.onLeaderAction = leaderOptions.onLeaderAction;\n this.unsubscribeLeaderRegistry = this.leaderRegistry.subscribe(() => {\n if (this.leaderActive) this.publishLeader();\n });\n // A mode toggling changes the badge row and nothing else about the editor,\n // so without this the new name waits for the next keystroke to appear.\n this.unsubscribeUiState = this.uiState.subscribe(() => tui.requestRender());\n }\n\n handleInput(data: string): void {\n const key = parseKey(data);\n\n if (this.leaderActive) {\n this.handleLeaderInput(key);\n return;\n }\n\n if ((key === SPACE_KEY && this.getText().length === 0) || key === GLOBAL_LEADER_KEY) {\n this.startLeader();\n return;\n }\n\n super.handleInput(data);\n }\n\n render(width: number): string[] {\n if (width < MIN_CHROME_WIDTH) return super.render(width);\n\n const editorWidth = width - EDITOR_CHROME_INSET;\n const base = super.render(editorWidth);\n if (base.length < 3) return base.map((line) => fitLine(line, width));\n\n const bottomIndex = this.findBottomBorder(base, editorWidth);\n const contentLines = base.slice(1, bottomIndex);\n const autocompleteLines = base.slice(bottomIndex + 1);\n const borderSide = this.chromeTheme.fg(BORDER_COLOR, VERTICAL_BORDER);\n const top = this.chromeTheme.fg(BORDER_COLOR, `╭${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╮`);\n const bottom = this.chromeTheme.fg(BORDER_COLOR, `╰${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╯`);\n const metadata = this.editorLine(\n alignLine(\n ` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,\n this.chromeTheme.fg(DIM_COLOR, `${this.editorStatus()} `),\n width - FRAME_OVERHEAD,\n ),\n width,\n borderSide,\n );\n const promptLines = contentLines.map((line, index) => {\n const prompt = index === 0 ? this.chromeTheme.bold(this.chromeTheme.fg(ACCENT_COLOR, '❯ ')) : ' ';\n return this.editorLine(`${prompt}${line}`, width, borderSide);\n });\n // The badge and the prompt are separate registers; a rule separates them\n // without the mode label reading as a caption on the input. Junctions take\n // the frame colour so the border stays unbroken through the row.\n const divider = `${this.chromeTheme.fg(BORDER_COLOR, '├')}${this.chromeTheme.fg(\n MUTED_BORDER_COLOR,\n HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD),\n )}${this.chromeTheme.fg(BORDER_COLOR, '┤')}`;\n\n return [\n top,\n metadata,\n divider,\n ...promptLines,\n bottom,\n ...autocompleteLines.map((line) => fitLine(` ${line}`, width)),\n ];\n }\n\n dispose(): void {\n this.unsubscribeLeaderRegistry();\n this.unsubscribeUiState();\n this.cancelLeader();\n }\n\n private startLeader(): void {\n this.leaderActive = true;\n this.leaderPath = [];\n this.publishLeader();\n }\n\n private cancelLeader(): void {\n this.leaderActive = false;\n this.leaderPath = [];\n this.publish(INACTIVE_SNAPSHOT);\n }\n\n private handleLeaderInput(key: string | undefined): void {\n if (key === ESCAPE_KEY) {\n this.cancelLeader();\n return;\n }\n if (key === BACKSPACE_KEY) {\n if (this.leaderPath.length === 0) this.cancelLeader();\n else {\n this.leaderPath.pop();\n this.publishLeader();\n }\n return;\n }\n if (!key || key.length !== 1) {\n this.cancelLeader();\n return;\n }\n\n const lowerKey = key.toLowerCase();\n const option = this.currentGroup()?.options.find((candidate) => candidate.key === lowerKey);\n if (!option) {\n this.cancelLeader();\n return;\n }\n if (option.hasChildren) {\n this.leaderPath.push(lowerKey);\n this.publishLeader();\n return;\n }\n if (!option.action) {\n this.cancelLeader();\n return;\n }\n\n this.cancelLeader();\n this.execute(option.action);\n }\n\n private currentGroup(): DoomLeaderGroup | undefined {\n return this.leaderRegistry.getGroup(this.leaderPath);\n }\n\n private publishLeader(): void {\n const group = this.currentGroup();\n if (!group) {\n this.cancelLeader();\n return;\n }\n const root = this.leaderRegistry.getGroup([]);\n this.publish({\n active: true,\n prefix: [LEADER_PREFIX, ...this.leaderPath],\n label: group.label,\n options: group.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })),\n rootOptions:\n root?.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })) ?? [],\n });\n }\n\n private publish(snapshot: LeaderSnapshot): void {\n this.uiState.setLeader(snapshot);\n this.onSnapshot(snapshot);\n this.tui.requestRender();\n }\n\n private execute(action: DoomLeaderAction): void {\n if (action.type === 'app') {\n const handler = this.actionHandlers.get(action.action as AppKeybinding);\n if (handler) {\n handler();\n return;\n }\n if (action.action === APP_EXIT_ACTION) {\n this.onCtrlD?.();\n return;\n }\n this.onUnavailableAction(action.action);\n return;\n }\n if (action.type === 'command') {\n this.submitCommand(action.command.name, action.command.args);\n return;\n }\n if (this.onLeaderAction) this.onLeaderAction(action.source, action.action.name);\n else this.onUnavailableAction(`${action.source}:${action.action.name}`);\n }\n\n private submitCommand(commandName: string, args?: string): void {\n if (!this.isCommandAvailable(commandName)) {\n this.onUnavailableCommand(commandName);\n return;\n }\n const draft = this.getText();\n const command = `/${commandName}${args ? ` ${args}` : ''}`;\n this.setText('');\n try {\n this.onSubmit?.(command);\n } finally {\n this.setText(draft);\n }\n }\n\n private modeLabel(): string {\n if (this.leaderActive) return `LEADER ${[LEADER_PREFIX, ...this.leaderPath].join(' ')}`;\n return this.majorMode.toUpperCase();\n }\n\n private modeBadge(): string {\n const label = ` ${this.modeLabel()} `;\n const color = this.leaderActive ? HEADING_COLOR : ACCENT_COLOR;\n return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(color, label)));\n }\n\n /**\n * The enabled modes, names only, beside the editor's own mode badge.\n *\n * Names only because this row is glanced at, not read: which modes are on is\n * the whole question here, and their sub-state belongs where there is room to\n * explain it. The leader panel carries that longer form.\n *\n * Empty when nothing is on, so an ordinary session pays no columns for a\n * feature it is not using.\n */\n private modeNames(): string {\n const modes = this.uiState.getModes();\n if (modes.length === 0) return '';\n const separator = this.chromeTheme.fg(DIM_COLOR, MODE_SEPARATOR);\n const names = modes.map((mode) => this.chromeTheme.fg(ACCENT_COLOR, mode.label.toLowerCase())).join(separator);\n return ` ${names}`;\n }\n\n private domainNames(): string {\n const domains = readDoomHarnessMetadata().domains;\n if (domains.length === 0) return '';\n return this.chromeTheme.fg(DIM_COLOR, ` ${domains.join(DOMAIN_SEPARATOR)}`);\n }\n\n private editorStatus(): string {\n if (this.leaderActive) return 'draft preserved · esc cancel';\n const draftLength = this.getText().length;\n return draftLength === 0 ? 'draft 0 · ^SPC leader · SPC shortcut' : `draft ${draftLength} · ^SPC leader`;\n }\n\n private editorLine(content: string, width: number, side: string): string {\n // No background fill: the frame alone delimits the editor, and a painted\n // interior reads as a shadow behind the prompt.\n return frameLine(padLine(content, width - FRAME_OVERHEAD), width, side, side);\n }\n\n private findBottomBorder(lines: string[], editorWidth: number): number {\n const threshold = Math.max(MIN_BORDER_SEGMENTS, editorWidth - BORDER_THRESHOLD_INSET);\n const index = lines.findIndex((line, lineIndex) => {\n if (lineIndex === 0) return false;\n return line.split(HORIZONTAL_BORDER).length - MIN_BORDER_SEGMENTS >= threshold;\n });\n return index > 0 ? index : lines.length - 1;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { DoomLeaderRegistry } from "../services/leader/leaderRegistry.cjs";
2
+ import { DoomUiState, LeaderSnapshot } from "../services/state/uiState.cjs";
3
+ import { CustomEditor, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
4
+ import { EditorOptions, EditorTheme, TUI } from "@earendil-works/pi-tui";
5
+ //#region src/tui/doomEditor.d.ts
6
+ interface DoomEditorLeaderOptions {
7
+ /** Overrides the harness major mode shown while the leader is inactive. */
8
+ majorMode?: string;
9
+ registry?: DoomLeaderRegistry;
10
+ isCommandAvailable?: (commandName: string) => boolean;
11
+ onUnavailableCommand?: (commandName: string) => void;
12
+ onUnavailableAction?: (action: string) => void;
13
+ onLeaderAction?: (source: string, actionName: string) => void;
14
+ }
15
+ type SnapshotHandler = (snapshot: LeaderSnapshot) => void;
16
+ type ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;
17
+ declare class DoomEditor extends CustomEditor {
18
+ private readonly uiState;
19
+ private readonly onSnapshot;
20
+ private readonly chromeTheme;
21
+ private readonly leaderRegistry;
22
+ private readonly majorMode;
23
+ private readonly isCommandAvailable;
24
+ private readonly onUnavailableCommand;
25
+ private readonly onUnavailableAction;
26
+ private readonly onLeaderAction;
27
+ private readonly unsubscribeLeaderRegistry;
28
+ private readonly unsubscribeUiState;
29
+ private leaderActive;
30
+ private leaderPath;
31
+ constructor(tui: TUI, editorTheme: EditorTheme, keybindings: KeybindingsManager, uiState: DoomUiState, onSnapshot: SnapshotHandler, options?: EditorOptions, chromeTheme?: ChromeTheme, leaderOptions?: DoomEditorLeaderOptions);
32
+ handleInput(data: string): void;
33
+ render(width: number): string[];
34
+ dispose(): void;
35
+ private startLeader;
36
+ private cancelLeader;
37
+ private handleLeaderInput;
38
+ private currentGroup;
39
+ private publishLeader;
40
+ private publish;
41
+ private execute;
42
+ private submitCommand;
43
+ private modeLabel;
44
+ private modeBadge;
45
+ /**
46
+ * The enabled modes, names only, beside the editor's own mode badge.
47
+ *
48
+ * Names only because this row is glanced at, not read: which modes are on is
49
+ * the whole question here, and their sub-state belongs where there is room to
50
+ * explain it. The leader panel carries that longer form.
51
+ *
52
+ * Empty when nothing is on, so an ordinary session pays no columns for a
53
+ * feature it is not using.
54
+ */
55
+ private modeNames;
56
+ private domainNames;
57
+ private editorStatus;
58
+ private editorLine;
59
+ private findBottomBorder;
60
+ }
61
+ //#endregion
62
+ export { DoomEditor, DoomEditorLeaderOptions };
63
+ //# sourceMappingURL=doomEditor.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomEditor.d.cts","names":[],"sources":["../../src/tui/doomEditor.ts"],"mappings":";;;;;UAOiB;;EAEf;EACA,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAoCX,mBAAmB;mBAiBX;mBACA;mBAjBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAGN,YAAA,KAAK,KACL,aAAa,aACb,aAAa,oBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAkBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;UAyBA;UAMA;UAsBA;UAeA;UAKA;;;;;;;;;;;UAgBA;UAQA;UAMA;UAMA;UAMA"}
@@ -0,0 +1,63 @@
1
+ import { DoomLeaderRegistry } from "../services/leader/leaderRegistry.mjs";
2
+ import { DoomUiState, LeaderSnapshot } from "../services/state/uiState.mjs";
3
+ import { EditorOptions, EditorTheme, TUI } from "@earendil-works/pi-tui";
4
+ import { CustomEditor, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
5
+ //#region src/tui/doomEditor.d.ts
6
+ interface DoomEditorLeaderOptions {
7
+ /** Overrides the harness major mode shown while the leader is inactive. */
8
+ majorMode?: string;
9
+ registry?: DoomLeaderRegistry;
10
+ isCommandAvailable?: (commandName: string) => boolean;
11
+ onUnavailableCommand?: (commandName: string) => void;
12
+ onUnavailableAction?: (action: string) => void;
13
+ onLeaderAction?: (source: string, actionName: string) => void;
14
+ }
15
+ type SnapshotHandler = (snapshot: LeaderSnapshot) => void;
16
+ type ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;
17
+ declare class DoomEditor extends CustomEditor {
18
+ private readonly uiState;
19
+ private readonly onSnapshot;
20
+ private readonly chromeTheme;
21
+ private readonly leaderRegistry;
22
+ private readonly majorMode;
23
+ private readonly isCommandAvailable;
24
+ private readonly onUnavailableCommand;
25
+ private readonly onUnavailableAction;
26
+ private readonly onLeaderAction;
27
+ private readonly unsubscribeLeaderRegistry;
28
+ private readonly unsubscribeUiState;
29
+ private leaderActive;
30
+ private leaderPath;
31
+ constructor(tui: TUI, editorTheme: EditorTheme, keybindings: KeybindingsManager, uiState: DoomUiState, onSnapshot: SnapshotHandler, options?: EditorOptions, chromeTheme?: ChromeTheme, leaderOptions?: DoomEditorLeaderOptions);
32
+ handleInput(data: string): void;
33
+ render(width: number): string[];
34
+ dispose(): void;
35
+ private startLeader;
36
+ private cancelLeader;
37
+ private handleLeaderInput;
38
+ private currentGroup;
39
+ private publishLeader;
40
+ private publish;
41
+ private execute;
42
+ private submitCommand;
43
+ private modeLabel;
44
+ private modeBadge;
45
+ /**
46
+ * The enabled modes, names only, beside the editor's own mode badge.
47
+ *
48
+ * Names only because this row is glanced at, not read: which modes are on is
49
+ * the whole question here, and their sub-state belongs where there is room to
50
+ * explain it. The leader panel carries that longer form.
51
+ *
52
+ * Empty when nothing is on, so an ordinary session pays no columns for a
53
+ * feature it is not using.
54
+ */
55
+ private modeNames;
56
+ private domainNames;
57
+ private editorStatus;
58
+ private editorLine;
59
+ private findBottomBorder;
60
+ }
61
+ //#endregion
62
+ export { DoomEditor, DoomEditorLeaderOptions };
63
+ //# sourceMappingURL=doomEditor.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomEditor.d.mts","names":[],"sources":["../../src/tui/doomEditor.ts"],"mappings":";;;;;UAOiB;;EAEf;EACA,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAoCX,mBAAmB;mBAiBX;mBACA;mBAjBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAGN,YAAA,KAAK,KACL,aAAa,aACb,aAAa,oBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAkBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;UAyBA;UAMA;UAsBA;UAeA;UAKA;;;;;;;;;;;UAgBA;UAQA;UAMA;UAMA;UAMA"}
@@ -0,0 +1,2 @@
1
+ import{DoomLeaderRegistry as e}from"../services/leader/leaderRegistry.mjs";import{alignLine as t,fitLine as n,frameLine as r,padLine as i}from"./rendering.mjs";import{readDoomHarnessMetadata as a}from"../types/harnessMetadata.mjs";import{parseKey as o}from"@earendil-works/pi-tui";import{CustomEditor as s}from"@earendil-works/pi-coding-agent";const c=`borderAccent`,l=`accent`,u={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},d={active:!1,prefix:[],label:``,options:[]};var f=class extends s{uiState;onSnapshot;chromeTheme;leaderRegistry;majorMode;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,n,r,i,o,s,c=u,l={}){super(t,n,r,s),this.uiState=i,this.onSnapshot=o,this.chromeTheme=c,this.leaderRegistry=l.registry??new e,this.majorMode=l.majorMode?.trim()||a().majorMode,this.isCommandAvailable=l.isCommandAvailable??(()=>!0),this.onUnavailableCommand=l.onUnavailableCommand??(()=>void 0),this.onUnavailableAction=l.onUnavailableAction??(()=>void 0),this.onLeaderAction=l.onLeaderAction,this.unsubscribeLeaderRegistry=this.leaderRegistry.subscribe(()=>{this.leaderActive&&this.publishLeader()}),this.unsubscribeUiState=this.uiState.subscribe(()=>t.requestRender())}handleInput(e){let t=o(e);if(this.leaderActive){this.handleLeaderInput(t);return}if(t===`space`&&this.getText().length===0||t===`ctrl+space`){this.startLeader();return}super.handleInput(e)}render(e){if(e<12)return super.render(e);let r=e-4,i=super.render(r);if(i.length<3)return i.map(t=>n(t,e));let a=this.findBottomBorder(i,r),o=i.slice(1,a),s=i.slice(a+1),u=this.chromeTheme.fg(c,`│`),d=this.chromeTheme.fg(c,`╭${`─`.repeat(e-2)}╮`),f=this.chromeTheme.fg(c,`╰${`─`.repeat(e-2)}╯`),p=this.editorLine(t(` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,this.chromeTheme.fg(`dim`,`${this.editorStatus()} `),e-2),e,u),m=o.map((t,n)=>{let r=n===0?this.chromeTheme.bold(this.chromeTheme.fg(l,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,u)});return[d,p,`${this.chromeTheme.fg(c,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(c,`┤`)}`,...m,f,...s.map(t=>n(` ${t}`,e))]}dispose(){this.unsubscribeLeaderRegistry(),this.unsubscribeUiState(),this.cancelLeader()}startLeader(){this.leaderActive=!0,this.leaderPath=[],this.publishLeader()}cancelLeader(){this.leaderActive=!1,this.leaderPath=[],this.publish(d)}handleLeaderInput(e){if(e===`escape`){this.cancelLeader();return}if(e===`backspace`){this.leaderPath.length===0?this.cancelLeader():(this.leaderPath.pop(),this.publishLeader());return}if(!e||e.length!==1){this.cancelLeader();return}let t=e.toLowerCase(),n=this.currentGroup()?.options.find(e=>e.key===t);if(!n){this.cancelLeader();return}if(n.hasChildren){this.leaderPath.push(t),this.publishLeader();return}if(!n.action){this.cancelLeader();return}this.cancelLeader(),this.execute(n.action)}currentGroup(){return this.leaderRegistry.getGroup(this.leaderPath)}publishLeader(){let e=this.currentGroup();if(!e){this.cancelLeader();return}let t=this.leaderRegistry.getGroup([]);this.publish({active:!0,prefix:[`SPC`,...this.leaderPath],label:e.label,options:e.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}})),rootOptions:t?.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}}))??[]})}publish(e){this.uiState.setLeader(e),this.onSnapshot(e),this.tui.requestRender()}execute(e){if(e.type===`app`){let t=this.actionHandlers.get(e.action);if(t){t();return}if(e.action===`app.exit`){this.onCtrlD?.();return}this.onUnavailableAction(e.action);return}if(e.type===`command`){this.submitCommand(e.command.name,e.command.args);return}this.onLeaderAction?this.onLeaderAction(e.source,e.action.name):this.onUnavailableAction(`${e.source}:${e.action.name}`)}submitCommand(e,t){if(!this.isCommandAvailable(e)){this.onUnavailableCommand(e);return}let n=this.getText(),r=`/${e}${t?` ${t}`:``}`;this.setText(``);try{this.onSubmit?.(r)}finally{this.setText(n)}}modeLabel(){return this.leaderActive?`LEADER ${[`SPC`,...this.leaderPath].join(` `)}`:this.majorMode.toUpperCase()}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:l;return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(t,e)))}modeNames(){let e=this.uiState.getModes();if(e.length===0)return``;let t=this.chromeTheme.fg(`dim`,` · `);return` ${e.map(e=>this.chromeTheme.fg(l,e.label.toLowerCase())).join(t)}`}domainNames(){let e=a().domains;return e.length===0?``:this.chromeTheme.fg(`dim`,` ${e.join(`, `)}`)}editorStatus(){if(this.leaderActive)return`draft preserved · esc cancel`;let e=this.getText().length;return e===0?`draft 0 · ^SPC leader · SPC shortcut`:`draft ${e} · ^SPC leader`}editorLine(e,t,n){return r(i(e,t-2),t,n,n)}findBottomBorder(e,t){let n=Math.max(1,t-10),r=e.findIndex((e,t)=>t!==0&&e.split(`─`).length-1>=n);return r>0?r:e.length-1}};export{f as DoomEditor};
2
+ //# sourceMappingURL=doomEditor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomEditor.mjs","names":[],"sources":["../../src/tui/doomEditor.ts"],"sourcesContent":["import { type AppKeybinding, CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { type EditorOptions, type EditorTheme, parseKey, type TUI } from '@earendil-works/pi-tui';\nimport { type DoomLeaderAction, type DoomLeaderGroup, DoomLeaderRegistry } from '../services/leader/leaderRegistry.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../services/state/uiState.ts';\nimport { readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, frameLine, padLine } from './rendering.ts';\n\nexport interface DoomEditorLeaderOptions {\n /** Overrides the harness major mode shown while the leader is inactive. */\n majorMode?: string;\n registry?: DoomLeaderRegistry;\n isCommandAvailable?: (commandName: string) => boolean;\n onUnavailableCommand?: (commandName: string) => void;\n onUnavailableAction?: (action: string) => void;\n onLeaderAction?: (source: string, actionName: string) => void;\n}\n\ntype SnapshotHandler = (snapshot: LeaderSnapshot) => void;\ntype ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;\n\nconst MIN_CHROME_WIDTH = 12;\nconst EDITOR_CHROME_INSET = 4;\nconst FRAME_OVERHEAD = 2;\nconst BORDER_THRESHOLD_INSET = 10;\nconst MIN_BORDER_SEGMENTS = 1;\nconst APP_EXIT_ACTION = 'app.exit';\nconst ESCAPE_KEY = 'escape';\nconst BACKSPACE_KEY = 'backspace';\nconst SPACE_KEY = 'space';\nconst GLOBAL_LEADER_KEY = 'ctrl+space';\nconst LEADER_PREFIX = 'SPC';\nconst BORDER_COLOR = 'borderAccent';\nconst DIM_COLOR = 'dim';\nconst ACCENT_COLOR = 'accent';\nconst HEADING_COLOR = 'mdHeading';\nconst MUTED_BORDER_COLOR = 'borderMuted';\n/** Between mode names in the badge row. */\nconst MODE_SEPARATOR = ' · ';\nconst DOMAIN_SEPARATOR = ', ';\nconst VERTICAL_BORDER = '│';\nconst HORIZONTAL_BORDER = '─';\nconst PLAIN_CHROME_THEME: ChromeTheme = {\n bg: (_color, text) => text,\n bold: (text) => text,\n fg: (_color, text) => text,\n inverse: (text) => text,\n};\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nexport class DoomEditor extends CustomEditor {\n private readonly chromeTheme: ChromeTheme;\n private readonly leaderRegistry: DoomLeaderRegistry;\n private readonly majorMode: string;\n private readonly isCommandAvailable: (commandName: string) => boolean;\n private readonly onUnavailableCommand: (commandName: string) => void;\n private readonly onUnavailableAction: (action: string) => void;\n private readonly onLeaderAction: ((source: string, actionName: string) => void) | undefined;\n private readonly unsubscribeLeaderRegistry: () => void;\n private readonly unsubscribeUiState: () => void;\n private leaderActive = false;\n private leaderPath: string[] = [];\n\n constructor(\n tui: TUI,\n editorTheme: EditorTheme,\n keybindings: KeybindingsManager,\n private readonly uiState: DoomUiState,\n private readonly onSnapshot: SnapshotHandler,\n options?: EditorOptions,\n chromeTheme: ChromeTheme = PLAIN_CHROME_THEME,\n leaderOptions: DoomEditorLeaderOptions = {},\n ) {\n super(tui, editorTheme, keybindings, options);\n this.chromeTheme = chromeTheme;\n this.leaderRegistry = leaderOptions.registry ?? new DoomLeaderRegistry();\n this.majorMode = leaderOptions.majorMode?.trim() || readDoomHarnessMetadata().majorMode;\n this.isCommandAvailable = leaderOptions.isCommandAvailable ?? (() => true);\n this.onUnavailableCommand = leaderOptions.onUnavailableCommand ?? (() => undefined);\n this.onUnavailableAction = leaderOptions.onUnavailableAction ?? (() => undefined);\n this.onLeaderAction = leaderOptions.onLeaderAction;\n this.unsubscribeLeaderRegistry = this.leaderRegistry.subscribe(() => {\n if (this.leaderActive) this.publishLeader();\n });\n // A mode toggling changes the badge row and nothing else about the editor,\n // so without this the new name waits for the next keystroke to appear.\n this.unsubscribeUiState = this.uiState.subscribe(() => tui.requestRender());\n }\n\n handleInput(data: string): void {\n const key = parseKey(data);\n\n if (this.leaderActive) {\n this.handleLeaderInput(key);\n return;\n }\n\n if ((key === SPACE_KEY && this.getText().length === 0) || key === GLOBAL_LEADER_KEY) {\n this.startLeader();\n return;\n }\n\n super.handleInput(data);\n }\n\n render(width: number): string[] {\n if (width < MIN_CHROME_WIDTH) return super.render(width);\n\n const editorWidth = width - EDITOR_CHROME_INSET;\n const base = super.render(editorWidth);\n if (base.length < 3) return base.map((line) => fitLine(line, width));\n\n const bottomIndex = this.findBottomBorder(base, editorWidth);\n const contentLines = base.slice(1, bottomIndex);\n const autocompleteLines = base.slice(bottomIndex + 1);\n const borderSide = this.chromeTheme.fg(BORDER_COLOR, VERTICAL_BORDER);\n const top = this.chromeTheme.fg(BORDER_COLOR, `╭${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╮`);\n const bottom = this.chromeTheme.fg(BORDER_COLOR, `╰${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╯`);\n const metadata = this.editorLine(\n alignLine(\n ` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,\n this.chromeTheme.fg(DIM_COLOR, `${this.editorStatus()} `),\n width - FRAME_OVERHEAD,\n ),\n width,\n borderSide,\n );\n const promptLines = contentLines.map((line, index) => {\n const prompt = index === 0 ? this.chromeTheme.bold(this.chromeTheme.fg(ACCENT_COLOR, '❯ ')) : ' ';\n return this.editorLine(`${prompt}${line}`, width, borderSide);\n });\n // The badge and the prompt are separate registers; a rule separates them\n // without the mode label reading as a caption on the input. Junctions take\n // the frame colour so the border stays unbroken through the row.\n const divider = `${this.chromeTheme.fg(BORDER_COLOR, '├')}${this.chromeTheme.fg(\n MUTED_BORDER_COLOR,\n HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD),\n )}${this.chromeTheme.fg(BORDER_COLOR, '┤')}`;\n\n return [\n top,\n metadata,\n divider,\n ...promptLines,\n bottom,\n ...autocompleteLines.map((line) => fitLine(` ${line}`, width)),\n ];\n }\n\n dispose(): void {\n this.unsubscribeLeaderRegistry();\n this.unsubscribeUiState();\n this.cancelLeader();\n }\n\n private startLeader(): void {\n this.leaderActive = true;\n this.leaderPath = [];\n this.publishLeader();\n }\n\n private cancelLeader(): void {\n this.leaderActive = false;\n this.leaderPath = [];\n this.publish(INACTIVE_SNAPSHOT);\n }\n\n private handleLeaderInput(key: string | undefined): void {\n if (key === ESCAPE_KEY) {\n this.cancelLeader();\n return;\n }\n if (key === BACKSPACE_KEY) {\n if (this.leaderPath.length === 0) this.cancelLeader();\n else {\n this.leaderPath.pop();\n this.publishLeader();\n }\n return;\n }\n if (!key || key.length !== 1) {\n this.cancelLeader();\n return;\n }\n\n const lowerKey = key.toLowerCase();\n const option = this.currentGroup()?.options.find((candidate) => candidate.key === lowerKey);\n if (!option) {\n this.cancelLeader();\n return;\n }\n if (option.hasChildren) {\n this.leaderPath.push(lowerKey);\n this.publishLeader();\n return;\n }\n if (!option.action) {\n this.cancelLeader();\n return;\n }\n\n this.cancelLeader();\n this.execute(option.action);\n }\n\n private currentGroup(): DoomLeaderGroup | undefined {\n return this.leaderRegistry.getGroup(this.leaderPath);\n }\n\n private publishLeader(): void {\n const group = this.currentGroup();\n if (!group) {\n this.cancelLeader();\n return;\n }\n const root = this.leaderRegistry.getGroup([]);\n this.publish({\n active: true,\n prefix: [LEADER_PREFIX, ...this.leaderPath],\n label: group.label,\n options: group.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })),\n rootOptions:\n root?.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })) ?? [],\n });\n }\n\n private publish(snapshot: LeaderSnapshot): void {\n this.uiState.setLeader(snapshot);\n this.onSnapshot(snapshot);\n this.tui.requestRender();\n }\n\n private execute(action: DoomLeaderAction): void {\n if (action.type === 'app') {\n const handler = this.actionHandlers.get(action.action as AppKeybinding);\n if (handler) {\n handler();\n return;\n }\n if (action.action === APP_EXIT_ACTION) {\n this.onCtrlD?.();\n return;\n }\n this.onUnavailableAction(action.action);\n return;\n }\n if (action.type === 'command') {\n this.submitCommand(action.command.name, action.command.args);\n return;\n }\n if (this.onLeaderAction) this.onLeaderAction(action.source, action.action.name);\n else this.onUnavailableAction(`${action.source}:${action.action.name}`);\n }\n\n private submitCommand(commandName: string, args?: string): void {\n if (!this.isCommandAvailable(commandName)) {\n this.onUnavailableCommand(commandName);\n return;\n }\n const draft = this.getText();\n const command = `/${commandName}${args ? ` ${args}` : ''}`;\n this.setText('');\n try {\n this.onSubmit?.(command);\n } finally {\n this.setText(draft);\n }\n }\n\n private modeLabel(): string {\n if (this.leaderActive) return `LEADER ${[LEADER_PREFIX, ...this.leaderPath].join(' ')}`;\n return this.majorMode.toUpperCase();\n }\n\n private modeBadge(): string {\n const label = ` ${this.modeLabel()} `;\n const color = this.leaderActive ? HEADING_COLOR : ACCENT_COLOR;\n return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(color, label)));\n }\n\n /**\n * The enabled modes, names only, beside the editor's own mode badge.\n *\n * Names only because this row is glanced at, not read: which modes are on is\n * the whole question here, and their sub-state belongs where there is room to\n * explain it. The leader panel carries that longer form.\n *\n * Empty when nothing is on, so an ordinary session pays no columns for a\n * feature it is not using.\n */\n private modeNames(): string {\n const modes = this.uiState.getModes();\n if (modes.length === 0) return '';\n const separator = this.chromeTheme.fg(DIM_COLOR, MODE_SEPARATOR);\n const names = modes.map((mode) => this.chromeTheme.fg(ACCENT_COLOR, mode.label.toLowerCase())).join(separator);\n return ` ${names}`;\n }\n\n private domainNames(): string {\n const domains = readDoomHarnessMetadata().domains;\n if (domains.length === 0) return '';\n return this.chromeTheme.fg(DIM_COLOR, ` ${domains.join(DOMAIN_SEPARATOR)}`);\n }\n\n private editorStatus(): string {\n if (this.leaderActive) return 'draft preserved · esc cancel';\n const draftLength = this.getText().length;\n return draftLength === 0 ? 'draft 0 · ^SPC leader · SPC shortcut' : `draft ${draftLength} · ^SPC leader`;\n }\n\n private editorLine(content: string, width: number, side: string): string {\n // No background fill: the frame alone delimits the editor, and a painted\n // interior reads as a shadow behind the prompt.\n return frameLine(padLine(content, width - FRAME_OVERHEAD), width, side, side);\n }\n\n private findBottomBorder(lines: string[], editorWidth: number): number {\n const threshold = Math.max(MIN_BORDER_SEGMENTS, editorWidth - BORDER_THRESHOLD_INSET);\n const index = lines.findIndex((line, lineIndex) => {\n if (lineIndex === 0) return false;\n return line.split(HORIZONTAL_BORDER).length - MIN_BORDER_SEGMENTS >= threshold;\n });\n return index > 0 ? index : lines.length - 1;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./rendering.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("@earendil-works/pi-tui");const i=` · `,a=`muted`,o=`toolPendingBg`,s=`modeline`,c={off:`thinkingOff`,minimal:`thinkingMinimal`,low:`thinkingLow`,medium:`thinkingMedium`,high:`thinkingHigh`,xhigh:`thinkingXhigh`,max:`thinkingMax`};function l(e){return e.replace(/[\r\n\t]+/gu,` `).replace(/ {2,}/gu,` `).trim()}var u=class{theme;context;footerData;footerStatuses;uiState;unsubscribeBranch;unsubscribeFooterStatuses;unsubscribeState;constructor(e,t,n,r,i,a){this.theme=t,this.context=n,this.footerData=r,this.footerStatuses=i,this.uiState=a,this.unsubscribeBranch=r.onBranchChange(()=>e.requestRender()),this.unsubscribeFooterStatuses=i.subscribe(()=>e.requestRender()),this.unsubscribeState=a.subscribe(()=>e.requestRender())}dispose(){this.unsubscribeBranch(),this.unsubscribeFooterStatuses(),this.unsubscribeState()}invalidate(){}render(e){if(e<=0)return[];let i=e<90,a=this.uiState.getSnapshot(),s=a.active?a.prefix.join(` `):i?`I`:`INSERT`,c=a.active?this.theme.inverse(this.theme.bold(this.theme.fg(`mdHeading`,` ${s} `))):this.theme.inverse(this.theme.bold(this.theme.fg(`accent`,` ${s} `))),l=n.default.basename(this.context.cwd)||this.context.cwd,u=this.footerData.getGitBranch(),d=`${c}${this.locationSegment(l,u,i)}`,f=this.footerStatusSummary(`beforeModel`,i),p=this.runtimeSegment(i),m=this.runtimeSegment(i,f),h=f&&(0,r.visibleWidth)(c)+(0,r.visibleWidth)(m)<=e?m:p;if((0,r.visibleWidth)(d)+(0,r.visibleWidth)(h)>e)return[t.fitLine(`${c}${h}`,e)];let g=e-(0,r.visibleWidth)(d)-(0,r.visibleWidth)(h),_=this.modelineSummary(Math.max(0,g-1),i),v=this.theme.bg(o,t.padLine(` ${_}`,g));return[t.fitLine(`${d}${v}${h}`,e)]}locationSegment(e,t,n){let r=this.theme.bg(o,this.theme.fg(`text`,` ${e}`));if(!t)return`${r}${this.theme.bg(o,` `)}`;let i=n?` `:`  `;return`${r}${this.theme.bg(o,this.theme.fg(`success`,`${i}${t} `))}`}runtimeSegment(e,n=``){let r=this.context.getContextUsage()?.percent,a=r==null?e?`?`:`ctx ?`:e?`${Math.round(r)}%`:`ctx ${Math.round(r)}%`,s=e?18:30,c=t.fitLine(this.compactModel(this.context.model?.id??`no-model`),s),l=this.context.thinkingLevel??`off`,u=[...n?[n]:[],this.theme.fg(`warning`,c),this.theme.fg(this.thinkingColor(l),l),this.theme.fg(this.contextColor(r),a)].join(i);return this.theme.bg(o,` ${u} `)}thinkingColor(e){return c[e]??`thinkingText`}contextColor(e){return e==null?a:e>=85?`error`:e>=60?`warning`:a}modelineSummary(e,n){return e<=0?``:t.fitLine(this.footerStatusSummary(s,n),e)}footerStatusSummary(e,t){return this.footerStatuses.getStatuses().filter(t=>(t.placement??s)===e).map(e=>{let n=t?e.compactSegments:e.fullSegments;return n?.length?this.renderFooterSegments(n):this.theme.fg(`dim`,l(t?e.compactText:e.fullText))}).filter(Boolean).join(this.theme.fg(`dim`,i))}renderFooterSegments(e){return e.map(e=>{let t=e.text.replace(/[\r\n\t]+/gu,` `);return this.theme.fg(e.color??`dim`,t)}).join(``)}compactModel(e){return(e.split(`/`).at(-1)??e).replace(/^claude-/,``)}};exports.DoomFooter=u;
2
+ //# sourceMappingURL=doomFooter.cjs.map