@effect-tui/react 0.15.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +11 -2
  2. package/dist/src/codeblock.d.ts +1 -1
  3. package/dist/src/codeblock.d.ts.map +1 -1
  4. package/dist/src/codeblock.js +2 -2
  5. package/dist/src/codeblock.js.map +1 -1
  6. package/dist/src/components/ListView.d.ts +4 -4
  7. package/dist/src/components/ListView.d.ts.map +1 -1
  8. package/dist/src/components/ListView.js +16 -17
  9. package/dist/src/components/ListView.js.map +1 -1
  10. package/dist/src/components/Markdown.js +3 -3
  11. package/dist/src/components/Markdown.js.map +1 -1
  12. package/dist/src/components/MultilineTextInput.d.ts.map +1 -1
  13. package/dist/src/components/MultilineTextInput.js +133 -305
  14. package/dist/src/components/MultilineTextInput.js.map +1 -1
  15. package/dist/src/components/TextInput.d.ts.map +1 -1
  16. package/dist/src/components/TextInput.js +51 -98
  17. package/dist/src/components/TextInput.js.map +1 -1
  18. package/dist/src/components/text-editing.d.ts +61 -0
  19. package/dist/src/components/text-editing.d.ts.map +1 -1
  20. package/dist/src/components/text-editing.js +131 -0
  21. package/dist/src/components/text-editing.js.map +1 -1
  22. package/dist/src/console/ConsolePopover.d.ts +7 -1
  23. package/dist/src/console/ConsolePopover.d.ts.map +1 -1
  24. package/dist/src/console/ConsolePopover.js +55 -74
  25. package/dist/src/console/ConsolePopover.js.map +1 -1
  26. package/dist/src/debug/DebugOverlay.d.ts.map +1 -1
  27. package/dist/src/debug/DebugOverlay.js +3 -57
  28. package/dist/src/debug/DebugOverlay.js.map +1 -1
  29. package/dist/src/debug/DiagnosticsPanel.js +1 -1
  30. package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
  31. package/dist/src/dev.d.ts +5 -117
  32. package/dist/src/dev.d.ts.map +1 -1
  33. package/dist/src/dev.js +3 -333
  34. package/dist/src/dev.js.map +1 -1
  35. package/dist/src/hooks/use-scroll.d.ts +31 -35
  36. package/dist/src/hooks/use-scroll.d.ts.map +1 -1
  37. package/dist/src/hooks/use-scroll.js +51 -90
  38. package/dist/src/hooks/use-scroll.js.map +1 -1
  39. package/dist/src/hosts/base.d.ts +13 -2
  40. package/dist/src/hosts/base.d.ts.map +1 -1
  41. package/dist/src/hosts/base.js +74 -2
  42. package/dist/src/hosts/base.js.map +1 -1
  43. package/dist/src/hosts/box.d.ts +2 -2
  44. package/dist/src/hosts/box.d.ts.map +1 -1
  45. package/dist/src/hosts/box.js +29 -2
  46. package/dist/src/hosts/box.js.map +1 -1
  47. package/dist/src/hosts/canvas.d.ts +24 -4
  48. package/dist/src/hosts/canvas.d.ts.map +1 -1
  49. package/dist/src/hosts/canvas.js +107 -41
  50. package/dist/src/hosts/canvas.js.map +1 -1
  51. package/dist/src/hosts/codeblock.d.ts +10 -12
  52. package/dist/src/hosts/codeblock.d.ts.map +1 -1
  53. package/dist/src/hosts/codeblock.js +38 -35
  54. package/dist/src/hosts/codeblock.js.map +1 -1
  55. package/dist/src/hosts/flex-container.d.ts +3 -3
  56. package/dist/src/hosts/flex-container.d.ts.map +1 -1
  57. package/dist/src/hosts/flex-container.js +20 -5
  58. package/dist/src/hosts/flex-container.js.map +1 -1
  59. package/dist/src/hosts/index.d.ts +3 -2
  60. package/dist/src/hosts/index.d.ts.map +1 -1
  61. package/dist/src/hosts/index.js +2 -1
  62. package/dist/src/hosts/index.js.map +1 -1
  63. package/dist/src/hosts/layout-helpers.d.ts +10 -0
  64. package/dist/src/hosts/layout-helpers.d.ts.map +1 -0
  65. package/dist/src/hosts/layout-helpers.js +10 -0
  66. package/dist/src/hosts/layout-helpers.js.map +1 -0
  67. package/dist/src/hosts/leaf.d.ts +14 -0
  68. package/dist/src/hosts/leaf.d.ts.map +1 -0
  69. package/dist/src/hosts/leaf.js +31 -0
  70. package/dist/src/hosts/leaf.js.map +1 -0
  71. package/dist/src/hosts/overlay-item.d.ts +2 -2
  72. package/dist/src/hosts/overlay-item.d.ts.map +1 -1
  73. package/dist/src/hosts/overlay-item.js +7 -2
  74. package/dist/src/hosts/overlay-item.js.map +1 -1
  75. package/dist/src/hosts/overlay.d.ts +2 -2
  76. package/dist/src/hosts/overlay.d.ts.map +1 -1
  77. package/dist/src/hosts/overlay.js +6 -9
  78. package/dist/src/hosts/overlay.js.map +1 -1
  79. package/dist/src/hosts/scroll.d.ts +54 -26
  80. package/dist/src/hosts/scroll.d.ts.map +1 -1
  81. package/dist/src/hosts/scroll.js +185 -87
  82. package/dist/src/hosts/scroll.js.map +1 -1
  83. package/dist/src/hosts/single-child.d.ts.map +1 -1
  84. package/dist/src/hosts/single-child.js +2 -0
  85. package/dist/src/hosts/single-child.js.map +1 -1
  86. package/dist/src/hosts/spacer.d.ts +3 -3
  87. package/dist/src/hosts/spacer.d.ts.map +1 -1
  88. package/dist/src/hosts/spacer.js +8 -3
  89. package/dist/src/hosts/spacer.js.map +1 -1
  90. package/dist/src/hosts/text.d.ts +22 -18
  91. package/dist/src/hosts/text.d.ts.map +1 -1
  92. package/dist/src/hosts/text.js +108 -131
  93. package/dist/src/hosts/text.js.map +1 -1
  94. package/dist/src/hosts/vstack.js +1 -1
  95. package/dist/src/hosts/vstack.js.map +1 -1
  96. package/dist/src/hosts/zstack.d.ts +3 -3
  97. package/dist/src/hosts/zstack.d.ts.map +1 -1
  98. package/dist/src/hosts/zstack.js +13 -8
  99. package/dist/src/hosts/zstack.js.map +1 -1
  100. package/dist/src/index.d.ts +2 -2
  101. package/dist/src/index.d.ts.map +1 -1
  102. package/dist/src/internal/dev/hmr.d.ts +20 -0
  103. package/dist/src/internal/dev/hmr.d.ts.map +1 -0
  104. package/dist/src/internal/dev/hmr.js +93 -0
  105. package/dist/src/internal/dev/hmr.js.map +1 -0
  106. package/dist/src/internal/dev/runtime.d.ts +24 -0
  107. package/dist/src/internal/dev/runtime.d.ts.map +1 -0
  108. package/dist/src/internal/dev/runtime.js +135 -0
  109. package/dist/src/internal/dev/runtime.js.map +1 -0
  110. package/dist/src/internal/dev/ui.d.ts +13 -0
  111. package/dist/src/internal/dev/ui.d.ts.map +1 -0
  112. package/dist/src/internal/dev/ui.js +51 -0
  113. package/dist/src/internal/dev/ui.js.map +1 -0
  114. package/dist/src/internal/renderer/context.d.ts +9 -0
  115. package/dist/src/internal/renderer/context.d.ts.map +1 -0
  116. package/dist/src/internal/renderer/context.js +22 -0
  117. package/dist/src/internal/renderer/context.js.map +1 -0
  118. package/dist/src/internal/renderer/core/FrameBuilder.d.ts +18 -0
  119. package/dist/src/internal/renderer/core/FrameBuilder.d.ts.map +1 -0
  120. package/dist/src/internal/renderer/core/FrameBuilder.js +40 -0
  121. package/dist/src/internal/renderer/core/FrameBuilder.js.map +1 -0
  122. package/dist/src/internal/renderer/core/RendererState.d.ts +41 -0
  123. package/dist/src/internal/renderer/core/RendererState.d.ts.map +1 -0
  124. package/dist/src/internal/renderer/core/RendererState.js +70 -0
  125. package/dist/src/internal/renderer/core/RendererState.js.map +1 -0
  126. package/dist/src/internal/renderer/core/index.d.ts +3 -0
  127. package/dist/src/internal/renderer/core/index.d.ts.map +1 -0
  128. package/dist/src/internal/renderer/core/index.js +3 -0
  129. package/dist/src/internal/renderer/core/index.js.map +1 -0
  130. package/dist/src/internal/renderer/index.d.ts +40 -0
  131. package/dist/src/internal/renderer/index.d.ts.map +1 -0
  132. package/dist/src/internal/renderer/index.js +543 -0
  133. package/dist/src/internal/renderer/index.js.map +1 -0
  134. package/dist/src/internal/renderer/input/InputProcessor.d.ts +30 -0
  135. package/dist/src/internal/renderer/input/InputProcessor.d.ts.map +1 -0
  136. package/dist/src/internal/renderer/input/InputProcessor.js +122 -0
  137. package/dist/src/internal/renderer/input/InputProcessor.js.map +1 -0
  138. package/dist/src/internal/renderer/input/index.d.ts +2 -0
  139. package/dist/src/internal/renderer/input/index.d.ts.map +1 -0
  140. package/dist/src/internal/renderer/input/index.js +2 -0
  141. package/dist/src/internal/renderer/input/index.js.map +1 -0
  142. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts +42 -0
  143. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts.map +1 -0
  144. package/dist/src/internal/renderer/lifecycle/EventBus.js +97 -0
  145. package/dist/src/internal/renderer/lifecycle/EventBus.js.map +1 -0
  146. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts +13 -0
  147. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts.map +1 -0
  148. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js +111 -0
  149. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js.map +1 -0
  150. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts +3 -0
  151. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts.map +1 -0
  152. package/dist/src/internal/renderer/lifecycle/RenderCache.js +9 -0
  153. package/dist/src/internal/renderer/lifecycle/RenderCache.js.map +1 -0
  154. package/dist/src/internal/renderer/lifecycle/index.d.ts +4 -0
  155. package/dist/src/internal/renderer/lifecycle/index.d.ts.map +1 -0
  156. package/dist/src/internal/renderer/lifecycle/index.js +4 -0
  157. package/dist/src/internal/renderer/lifecycle/index.js.map +1 -0
  158. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts +12 -0
  159. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
  160. package/dist/src/internal/renderer/modes/FullscreenRenderer.js +54 -0
  161. package/dist/src/internal/renderer/modes/FullscreenRenderer.js.map +1 -0
  162. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts +25 -0
  163. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts.map +1 -0
  164. package/dist/src/internal/renderer/modes/InlineRenderer.js +166 -0
  165. package/dist/src/internal/renderer/modes/InlineRenderer.js.map +1 -0
  166. package/dist/src/internal/renderer/modes/RendererMode.d.ts +42 -0
  167. package/dist/src/internal/renderer/modes/RendererMode.d.ts.map +1 -0
  168. package/dist/src/internal/renderer/modes/RendererMode.js +2 -0
  169. package/dist/src/internal/renderer/modes/RendererMode.js.map +1 -0
  170. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts +25 -0
  171. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
  172. package/dist/src/internal/renderer/modes/StaticContentRenderer.js +49 -0
  173. package/dist/src/internal/renderer/modes/StaticContentRenderer.js.map +1 -0
  174. package/dist/src/internal/renderer/modes/index.d.ts +5 -0
  175. package/dist/src/internal/renderer/modes/index.d.ts.map +1 -0
  176. package/dist/src/internal/renderer/modes/index.js +4 -0
  177. package/dist/src/internal/renderer/modes/index.js.map +1 -0
  178. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts +13 -0
  179. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts.map +1 -0
  180. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js +75 -0
  181. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js.map +1 -0
  182. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts +29 -0
  183. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts.map +1 -0
  184. package/dist/src/internal/renderer/terminal/TerminalSetup.js +82 -0
  185. package/dist/src/internal/renderer/terminal/TerminalSetup.js.map +1 -0
  186. package/dist/src/internal/renderer/terminal/index.d.ts +3 -0
  187. package/dist/src/internal/renderer/terminal/index.d.ts.map +1 -0
  188. package/dist/src/internal/renderer/terminal/index.js +3 -0
  189. package/dist/src/internal/renderer/terminal/index.js.map +1 -0
  190. package/dist/src/internal/renderer/types.d.ts +122 -0
  191. package/dist/src/internal/renderer/types.d.ts.map +1 -0
  192. package/dist/src/internal/renderer/types.js +2 -0
  193. package/dist/src/internal/renderer/types.js.map +1 -0
  194. package/dist/src/motion/hooks.d.ts +1 -1
  195. package/dist/src/motion/hooks.js +1 -1
  196. package/dist/src/reconciler/host-config.js +2 -2
  197. package/dist/src/reconciler/host-config.js.map +1 -1
  198. package/dist/src/reconciler/types.d.ts +5 -1
  199. package/dist/src/reconciler/types.d.ts.map +1 -1
  200. package/dist/src/renderer-context.d.ts +1 -8
  201. package/dist/src/renderer-context.d.ts.map +1 -1
  202. package/dist/src/renderer-context.js +1 -21
  203. package/dist/src/renderer-context.js.map +1 -1
  204. package/dist/src/renderer-types.d.ts +1 -115
  205. package/dist/src/renderer-types.d.ts.map +1 -1
  206. package/dist/src/renderer.d.ts +1 -31
  207. package/dist/src/renderer.d.ts.map +1 -1
  208. package/dist/src/renderer.js +1 -495
  209. package/dist/src/renderer.js.map +1 -1
  210. package/dist/src/test/render-tui.d.ts +3 -3
  211. package/dist/src/test/render-tui.d.ts.map +1 -1
  212. package/dist/src/test/render-tui.js +16 -9
  213. package/dist/src/test/render-tui.js.map +1 -1
  214. package/dist/src/utils/alignment.d.ts +1 -1
  215. package/dist/src/utils/alignment.d.ts.map +1 -1
  216. package/dist/src/utils/alignment.js +0 -2
  217. package/dist/src/utils/alignment.js.map +1 -1
  218. package/dist/src/utils/border.d.ts +1 -1
  219. package/dist/src/utils/border.d.ts.map +1 -1
  220. package/dist/src/utils/border.js +2 -0
  221. package/dist/src/utils/border.js.map +1 -1
  222. package/dist/src/utils/console-helpers.d.ts +19 -0
  223. package/dist/src/utils/console-helpers.d.ts.map +1 -0
  224. package/dist/src/utils/console-helpers.js +61 -0
  225. package/dist/src/utils/console-helpers.js.map +1 -0
  226. package/dist/src/utils/index.d.ts +2 -1
  227. package/dist/src/utils/index.d.ts.map +1 -1
  228. package/dist/src/utils/index.js +2 -1
  229. package/dist/src/utils/index.js.map +1 -1
  230. package/dist/src/utils/styles.d.ts +8 -1
  231. package/dist/src/utils/styles.d.ts.map +1 -1
  232. package/dist/src/utils/styles.js +10 -8
  233. package/dist/src/utils/styles.js.map +1 -1
  234. package/dist/src/utils/text-layout.d.ts +22 -0
  235. package/dist/src/utils/text-layout.d.ts.map +1 -0
  236. package/dist/src/utils/text-layout.js +37 -0
  237. package/dist/src/utils/text-layout.js.map +1 -0
  238. package/dist/src/utils/text-wrap.d.ts +31 -1
  239. package/dist/src/utils/text-wrap.d.ts.map +1 -1
  240. package/dist/src/utils/text-wrap.js +205 -48
  241. package/dist/src/utils/text-wrap.js.map +1 -1
  242. package/dist/src/visualize/index.js +1 -1
  243. package/dist/src/visualize/index.js.map +1 -1
  244. package/dist/tsconfig.tsbuildinfo +1 -1
  245. package/package.json +2 -2
  246. package/src/codeblock.tsx +2 -2
  247. package/src/components/ListView.tsx +21 -23
  248. package/src/components/Markdown.tsx +3 -3
  249. package/src/components/MultilineTextInput.tsx +138 -344
  250. package/src/components/TextInput.tsx +54 -99
  251. package/src/components/text-editing.ts +180 -0
  252. package/src/console/ConsolePopover.tsx +124 -107
  253. package/src/debug/DebugOverlay.ts +15 -74
  254. package/src/debug/DiagnosticsPanel.tsx +1 -1
  255. package/src/dev.tsx +5 -458
  256. package/src/hooks/use-scroll.ts +85 -145
  257. package/src/hosts/base.ts +86 -3
  258. package/src/hosts/box.ts +37 -2
  259. package/src/hosts/canvas.ts +128 -42
  260. package/src/hosts/codeblock.ts +48 -35
  261. package/src/hosts/flex-container.ts +25 -6
  262. package/src/hosts/index.ts +11 -2
  263. package/src/hosts/layout-helpers.ts +20 -0
  264. package/src/hosts/leaf.ts +36 -0
  265. package/src/hosts/overlay-item.ts +8 -2
  266. package/src/hosts/overlay.ts +13 -11
  267. package/src/hosts/scroll.ts +228 -106
  268. package/src/hosts/single-child.ts +2 -0
  269. package/src/hosts/spacer.ts +8 -3
  270. package/src/hosts/text.ts +126 -132
  271. package/src/hosts/vstack.ts +1 -1
  272. package/src/hosts/zstack.ts +14 -9
  273. package/src/index.ts +2 -2
  274. package/src/internal/dev/hmr.ts +101 -0
  275. package/src/internal/dev/runtime.ts +170 -0
  276. package/src/internal/dev/ui.tsx +87 -0
  277. package/src/internal/renderer/context.ts +27 -0
  278. package/src/{renderer → internal/renderer}/core/FrameBuilder.ts +2 -2
  279. package/src/internal/renderer/index.ts +689 -0
  280. package/src/{renderer → internal/renderer}/input/InputProcessor.ts +10 -1
  281. package/src/{renderer → internal/renderer}/lifecycle/EventBus.ts +9 -1
  282. package/src/internal/renderer/lifecycle/ProcessLifecycle.ts +125 -0
  283. package/src/internal/renderer/lifecycle/index.ts +3 -0
  284. package/src/{renderer → internal/renderer}/modes/InlineRenderer.ts +5 -2
  285. package/src/{renderer → internal/renderer}/modes/RendererMode.ts +1 -1
  286. package/src/{renderer → internal/renderer}/modes/StaticContentRenderer.ts +5 -2
  287. package/src/internal/renderer/terminal/KeyboardCapabilityProbe.ts +91 -0
  288. package/src/{renderer/lifecycle → internal/renderer/terminal}/TerminalSetup.ts +4 -22
  289. package/src/internal/renderer/terminal/index.ts +2 -0
  290. package/src/internal/renderer/types.ts +129 -0
  291. package/src/motion/hooks.ts +1 -1
  292. package/src/reconciler/host-config.ts +2 -2
  293. package/src/reconciler/types.ts +7 -1
  294. package/src/renderer-context.ts +1 -27
  295. package/src/renderer-types.ts +10 -123
  296. package/src/renderer.ts +1 -619
  297. package/src/test/render-tui.ts +16 -10
  298. package/src/utils/alignment.ts +1 -3
  299. package/src/utils/border.ts +11 -1
  300. package/src/utils/console-helpers.ts +86 -0
  301. package/src/utils/index.ts +15 -1
  302. package/src/utils/styles.ts +16 -4
  303. package/src/utils/text-layout.ts +65 -0
  304. package/src/utils/text-wrap.ts +261 -48
  305. package/src/visualize/index.tsx +1 -1
  306. package/src/renderer/lifecycle/ResizeManager.ts +0 -65
  307. package/src/renderer/lifecycle/index.ts +0 -4
  308. /package/src/{renderer → internal/renderer}/core/RendererState.ts +0 -0
  309. /package/src/{renderer → internal/renderer}/core/index.ts +0 -0
  310. /package/src/{renderer → internal/renderer}/input/index.ts +0 -0
  311. /package/src/{renderer → internal/renderer}/lifecycle/RenderCache.ts +0 -0
  312. /package/src/{renderer → internal/renderer}/modes/FullscreenRenderer.ts +0 -0
  313. /package/src/{renderer → internal/renderer}/modes/index.ts +0 -0
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { Rect, Size } from "../reconciler/types.js"
6
6
 
7
- export type HAlign = "left" | "center" | "right" | "leading" | "trailing"
7
+ export type HAlign = "left" | "center" | "right"
8
8
  export type VAlign = "top" | "center" | "bottom"
9
9
 
10
10
  /**
@@ -22,14 +22,12 @@ export function alignInRect(
22
22
 
23
23
  switch (hAlign) {
24
24
  case "left":
25
- case "leading":
26
25
  x = rect.x
27
26
  break
28
27
  case "center":
29
28
  x = rect.x + Math.floor((rect.w - size.w) / 2)
30
29
  break
31
30
  case "right":
32
- case "trailing":
33
31
  x = rect.x + Math.max(0, rect.w - size.w)
34
32
  break
35
33
  }
@@ -4,7 +4,15 @@
4
4
 
5
5
  import type { CellBuffer } from "@effect-tui/core"
6
6
 
7
- export type BorderKind = "none" | "rounded" | "square" | "double" | "heavy" | "dashed" | "ascii"
7
+ export type BorderKind =
8
+ | "none"
9
+ | "rounded"
10
+ | "round"
11
+ | "square"
12
+ | "double"
13
+ | "heavy"
14
+ | "dashed"
15
+ | "ascii"
8
16
 
9
17
  export interface BorderChars {
10
18
  tl: string // top-left
@@ -33,6 +41,7 @@ export interface TableBorderChars extends BorderChars {
33
41
  export function borderChars(kind: BorderKind): BorderChars {
34
42
  switch (kind) {
35
43
  case "rounded":
44
+ case "round":
36
45
  return { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│" }
37
46
  case "square":
38
47
  return { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│" }
@@ -55,6 +64,7 @@ export function borderChars(kind: BorderKind): BorderChars {
55
64
  export function tableBorderChars(kind: BorderKind): TableBorderChars {
56
65
  switch (kind) {
57
66
  case "rounded":
67
+ case "round":
58
68
  return { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│", tt: "┬", bt: "┴", lt: "├", rt: "┤", cross: "┼" }
59
69
  case "square":
60
70
  return { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", tt: "┬", bt: "┴", lt: "├", rt: "┤", cross: "┼" }
@@ -0,0 +1,86 @@
1
+ export interface SelectionPoint {
2
+ line: number
3
+ col: number
4
+ }
5
+
6
+ export function formatTimestamp(date: Date): string {
7
+ const h = date.getHours().toString().padStart(2, "0")
8
+ const m = date.getMinutes().toString().padStart(2, "0")
9
+ const s = date.getSeconds().toString().padStart(2, "0")
10
+ return `${h}:${m}:${s}`
11
+ }
12
+
13
+ export function formatLocation(loc?: { file: string; line: number }): string {
14
+ if (!loc) return ""
15
+ const parts = loc.file.split("/")
16
+ const filename = parts[parts.length - 1]
17
+ return `${filename}:${loc.line}`
18
+ }
19
+
20
+ export function wrapLine(text: string, maxWidth: number): string[] {
21
+ if (text.length <= maxWidth) return [text]
22
+
23
+ const lines: string[] = []
24
+ let remaining = text
25
+
26
+ while (remaining.length > 0) {
27
+ if (remaining.length <= maxWidth) {
28
+ lines.push(remaining)
29
+ break
30
+ }
31
+
32
+ let breakAt = maxWidth
33
+ const lastSpace = remaining.lastIndexOf(" ", maxWidth)
34
+ if (lastSpace > maxWidth * 0.5) {
35
+ breakAt = lastSpace
36
+ }
37
+
38
+ lines.push(remaining.slice(0, breakAt))
39
+ remaining = remaining.slice(breakAt).trimStart()
40
+ }
41
+
42
+ return lines
43
+ }
44
+
45
+ /** Normalize selection so start is before end */
46
+ export function normalizeSelection(
47
+ anchor: SelectionPoint,
48
+ head: SelectionPoint,
49
+ ): [SelectionPoint, SelectionPoint] {
50
+ if (anchor.line < head.line || (anchor.line === head.line && anchor.col <= head.col)) {
51
+ return [anchor, head]
52
+ }
53
+ return [head, anchor]
54
+ }
55
+
56
+ /** Get selection bounds for a specific line */
57
+ export function getLineSelection(
58
+ lineIndex: number,
59
+ lineLength: number,
60
+ start: SelectionPoint,
61
+ end: SelectionPoint,
62
+ ): { startCol: number; endCol: number } | null {
63
+ if (lineIndex < start.line || lineIndex > end.line) return null
64
+
65
+ const startCol = lineIndex === start.line ? Math.min(start.col, lineLength) : 0
66
+ const endCol = lineIndex === end.line ? Math.min(end.col, lineLength) : lineLength
67
+
68
+ if (startCol >= endCol && lineIndex === start.line && lineIndex === end.line) return null
69
+
70
+ return { startCol, endCol }
71
+ }
72
+
73
+ export function getSelectionText(
74
+ lines: string[],
75
+ start: SelectionPoint,
76
+ end: SelectionPoint,
77
+ ): string {
78
+ const selected: string[] = []
79
+ for (let i = start.line; i <= end.line && i < lines.length; i++) {
80
+ const lineText = lines[i] ?? ""
81
+ const startCol = i === start.line ? Math.min(start.col, lineText.length) : 0
82
+ const endCol = i === end.line ? Math.min(end.col, lineText.length) : lineText.length
83
+ selected.push(lineText.slice(startCol, endCol))
84
+ }
85
+ return selected.join("\n")
86
+ }
@@ -20,5 +20,19 @@ export {
20
20
  styleSpecFromProps,
21
21
  toColorValue,
22
22
  } from "./styles.js"
23
- export { wrapSpans } from "./text-wrap.js"
23
+ export {
24
+ buildTextLayout,
25
+ type LineLayout,
26
+ type TextLayout,
27
+ type TextLayoutOptions,
28
+ type VisualLine,
29
+ } from "./text-layout.js"
30
+ export {
31
+ splitSpansByNewline,
32
+ spansDisplayWidth,
33
+ wrapLineWithRanges,
34
+ wrapSpans,
35
+ wrapSpansByLine,
36
+ wrapText,
37
+ } from "./text-wrap.js"
24
38
  export { isWhitespace, matchNextWord, matchPrevWord, splitWords } from "./word-boundaries.js"
@@ -83,14 +83,26 @@ export function resolveInheritedBgStyle(
83
83
  /**
84
84
  * Fill a rect with background color, inheriting from parent if needed.
85
85
  */
86
+ export interface FillRectWithInheritedBgOptions {
87
+ /** Skip filling when there is no inherited background value. */
88
+ skipWhenUndefined?: boolean
89
+ }
90
+
86
91
  export function fillRectWithInheritedBg(
87
92
  buffer: CellBuffer,
88
93
  palette: Palette,
89
94
  rect: Rect,
90
95
  explicitBg: Color | undefined,
91
96
  parent: HostInstance | null,
92
- ): void {
93
- if (rect.w <= 0 || rect.h <= 0) return
94
- const { styleId } = resolveInheritedBgStyle(palette, explicitBg, parent)
95
- buffer.fillRect(rect.x, rect.y, rect.w, rect.h, " ".codePointAt(0)!, styleId)
97
+ options?: FillRectWithInheritedBgOptions,
98
+ ): { value?: ColorValue; styleId: number } {
99
+ if (rect.w <= 0 || rect.h <= 0) {
100
+ return { value: undefined, styleId: 0 }
101
+ }
102
+ const resolved = resolveInheritedBgStyle(palette, explicitBg, parent)
103
+ if (options?.skipWhenUndefined && resolved.value === undefined) {
104
+ return resolved
105
+ }
106
+ buffer.fillRect(rect.x, rect.y, rect.w, rect.h, " ".codePointAt(0)!, resolved.styleId)
107
+ return resolved
96
108
  }
@@ -0,0 +1,65 @@
1
+ import { displayWidth, graphemes } from "@effect-tui/core"
2
+ import { wrapLineWithRanges } from "./text-wrap.js"
3
+
4
+ export type VisualLine = {
5
+ logicalRow: number
6
+ startCol: number
7
+ endCol: number
8
+ text: string
9
+ }
10
+
11
+ export type LineLayout = {
12
+ graphemeList: string[]
13
+ widths: number[]
14
+ prefixWidths: number[]
15
+ visualLines: VisualLine[]
16
+ }
17
+
18
+ export type TextLayout = {
19
+ lines: LineLayout[]
20
+ allVisualLines: VisualLine[]
21
+ }
22
+
23
+ export type TextLayoutOptions = {
24
+ wrap?: boolean
25
+ preserveWhitespace?: boolean
26
+ }
27
+
28
+ export function buildTextLayout(text: string, maxWidth: number, options?: TextLayoutOptions): TextLayout {
29
+ const logicalLines = text.split("\n")
30
+ const wrap = options?.wrap ?? true
31
+ const preserveWhitespace = options?.preserveWhitespace ?? false
32
+ const lines: LineLayout[] = []
33
+ const allVisualLines: VisualLine[] = []
34
+
35
+ for (let row = 0; row < logicalLines.length; row++) {
36
+ const line = logicalLines[row]
37
+ const graphemeList = graphemes(line)
38
+ const widths = graphemeList.map((g) => displayWidth(g))
39
+ const prefixWidths: number[] = [0]
40
+ for (let i = 0; i < widths.length; i++) {
41
+ prefixWidths.push(prefixWidths[i] + widths[i])
42
+ }
43
+
44
+ const visualLines: VisualLine[] = wrap
45
+ ? wrapLineWithRanges(line, maxWidth, { preserveWhitespace }).map((range) => ({
46
+ logicalRow: row,
47
+ startCol: range.startCol,
48
+ endCol: range.endCol,
49
+ text: range.text,
50
+ }))
51
+ : [
52
+ {
53
+ logicalRow: row,
54
+ startCol: 0,
55
+ endCol: graphemeList.length,
56
+ text: line,
57
+ },
58
+ ]
59
+
60
+ lines.push({ graphemeList, widths, prefixWidths, visualLines })
61
+ allVisualLines.push(...visualLines)
62
+ }
63
+
64
+ return { lines, allVisualLines }
65
+ }
@@ -1,60 +1,123 @@
1
- import { displayWidth } from "@effect-tui/core"
1
+ import { displayWidth, graphemes } from "@effect-tui/core"
2
2
  import { isWhitespace, splitWords } from "./word-boundaries.js"
3
3
 
4
4
  type SpanLike = { text: string }
5
5
 
6
- /**
7
- * Wrap spans into lines, breaking at word boundaries.
8
- * Preserves span styling by cloning span objects with updated text.
9
- */
10
- export function wrapSpans<T extends SpanLike>(spans: T[], maxWidth: number): T[][] {
11
- const lines: T[][] = [[]]
6
+ type TokenSpec<T> = {
7
+ text: string
8
+ width: number
9
+ graphemeCount: number
10
+ isWhitespace: boolean
11
+ startCol?: number
12
+ segments?: string[]
13
+ make: (text: string, segmentStart: number, segmentEnd: number) => T
14
+ }
15
+
16
+ type LineToken<T> = {
17
+ value: T
18
+ width: number
19
+ isWhitespace: boolean
20
+ }
21
+
22
+ export type WrapOptions = {
23
+ trimTrailingWhitespace?: boolean
24
+ preserveWhitespace?: boolean
25
+ }
26
+
27
+ export function spansDisplayWidth<T extends { text: string }>(spans: T[]): number {
28
+ return spans.reduce((sum, span) => sum + displayWidth(span.text), 0)
29
+ }
30
+
31
+ function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapOptions): T[][] {
32
+ const lines: LineToken<T>[][] = [[]]
12
33
  let lineWidth = 0
13
34
 
14
- for (const span of spans) {
15
- // Split span text into words (keeping whitespace as separate tokens)
16
- const tokens = splitWords(span.text)
35
+ const trimLine = () => {
36
+ if (!options?.trimTrailingWhitespace) return
37
+ const line = lines[lines.length - 1]
38
+ while (line.length > 0 && line[line.length - 1].isWhitespace) {
39
+ const removed = line.pop()
40
+ if (removed) {
41
+ lineWidth -= removed.width
42
+ }
43
+ }
44
+ }
17
45
 
18
- for (const token of tokens) {
19
- if (!token) continue
20
- const tokenWidth = displayWidth(token)
21
- const isWs = isWhitespace(token)
22
-
23
- if (lineWidth + tokenWidth <= maxWidth) {
24
- // Token fits on current line
25
- lines[lines.length - 1].push({ ...span, text: token })
26
- lineWidth += tokenWidth
27
- } else if (isWs) {
28
- // Skip whitespace at line break
29
- continue
30
- } else if (tokenWidth <= maxWidth) {
31
- // Start new line with this token
32
- lines.push([{ ...span, text: token }])
33
- lineWidth = tokenWidth
34
- } else {
35
- // Token is longer than maxWidth - break by character
36
- let charLine = ""
37
- let charLineWidth = 0
38
- for (const ch of token) {
39
- const chWidth = displayWidth(ch)
40
- if (lineWidth + charLineWidth + chWidth > maxWidth && (charLine || lineWidth > 0)) {
41
- if (charLine) {
42
- lines[lines.length - 1].push({ ...span, text: charLine })
43
- }
44
- lines.push([])
45
- lineWidth = 0
46
- charLine = ch
47
- charLineWidth = chWidth
48
- } else {
49
- charLine += ch
50
- charLineWidth += chWidth
51
- }
52
- }
53
- if (charLine) {
54
- lines[lines.length - 1].push({ ...span, text: charLine })
55
- lineWidth += charLineWidth
46
+ const startNewLine = () => {
47
+ lines.push([])
48
+ lineWidth = 0
49
+ }
50
+
51
+ const addToken = (
52
+ token: TokenSpec<T>,
53
+ text: string,
54
+ width: number,
55
+ isWhitespace: boolean,
56
+ segmentStart: number,
57
+ segmentEnd: number,
58
+ ) => {
59
+ lines[lines.length - 1].push({ value: token.make(text, segmentStart, segmentEnd), width, isWhitespace })
60
+ lineWidth += width
61
+ }
62
+
63
+ for (const token of tokens) {
64
+ if (!token.text) continue
65
+ const tokenWidth = token.width
66
+ const isWs = token.isWhitespace
67
+
68
+ if (lineWidth + tokenWidth <= maxWidth) {
69
+ const start = token.startCol ?? 0
70
+ addToken(token, token.text, tokenWidth, isWs, start, start + token.graphemeCount)
71
+ continue
72
+ }
73
+
74
+ if (isWs && !options?.preserveWhitespace) {
75
+ // Skip whitespace at line break
76
+ continue
77
+ }
78
+
79
+ if (tokenWidth <= maxWidth) {
80
+ trimLine()
81
+ if (lines[lines.length - 1].length > 0) {
82
+ startNewLine()
83
+ }
84
+ const start = token.startCol ?? 0
85
+ addToken(token, token.text, tokenWidth, isWs, start, start + token.graphemeCount)
86
+ continue
87
+ }
88
+
89
+ // Token is longer than maxWidth - break by character
90
+ trimLine()
91
+ if (lines[lines.length - 1].length > 0) {
92
+ startNewLine()
93
+ }
94
+
95
+ const segments = token.segments ?? Array.from(token.text)
96
+ const tokenStart = token.startCol ?? 0
97
+ let segment = ""
98
+ let segmentWidth = 0
99
+ let segmentStartOffset = 0
100
+ let segmentOffset = 0
101
+
102
+ for (const ch of segments) {
103
+ const chWidth = displayWidth(ch)
104
+ if (lineWidth + segmentWidth + chWidth > maxWidth && (segment || lineWidth > 0)) {
105
+ if (segment) {
106
+ addToken(token, segment, segmentWidth, false, tokenStart + segmentStartOffset, tokenStart + segmentOffset)
56
107
  }
108
+ startNewLine()
109
+ segment = ch
110
+ segmentWidth = chWidth
111
+ segmentStartOffset = segmentOffset
112
+ } else {
113
+ segment += ch
114
+ segmentWidth += chWidth
57
115
  }
116
+ segmentOffset += 1
117
+ }
118
+
119
+ if (segment) {
120
+ addToken(token, segment, segmentWidth, false, tokenStart + segmentStartOffset, tokenStart + segmentOffset)
58
121
  }
59
122
  }
60
123
 
@@ -63,5 +126,155 @@ export function wrapSpans<T extends SpanLike>(spans: T[], maxWidth: number): T[]
63
126
  lines.pop()
64
127
  }
65
128
 
129
+ return lines.length > 0 ? lines.map((line) => line.map((token) => token.value)) : [[]]
130
+ }
131
+
132
+ export type WrappedLineRange = {
133
+ text: string
134
+ startCol: number
135
+ endCol: number
136
+ }
137
+
138
+ /**
139
+ * Wrap a single line into visual lines with grapheme ranges.
140
+ */
141
+ export function wrapLineWithRanges(line: string, maxWidth: number, options?: WrapOptions): WrappedLineRange[] {
142
+ if (line === "") {
143
+ return [{ text: "", startCol: 0, endCol: 0 }]
144
+ }
145
+
146
+ const tokens: Array<TokenSpec<WrappedLineRange>> = []
147
+ let col = 0
148
+ for (const token of splitWords(line)) {
149
+ if (!token) continue
150
+ const segments = graphemes(token)
151
+ const graphemeCount = segments.length
152
+ const startCol = col
153
+ tokens.push({
154
+ text: token,
155
+ width: displayWidth(token),
156
+ graphemeCount,
157
+ startCol,
158
+ segments,
159
+ isWhitespace: isWhitespace(token),
160
+ make: (text, segmentStart, segmentEnd) => ({ text, startCol: segmentStart, endCol: segmentEnd }),
161
+ })
162
+ col += graphemeCount
163
+ }
164
+
165
+ const wrapped = wrapTokens(tokens, maxWidth, options)
166
+ const lines: WrappedLineRange[] = []
167
+
168
+ for (const lineTokens of wrapped) {
169
+ if (lineTokens.length === 0) {
170
+ lines.push({ text: "", startCol: 0, endCol: 0 })
171
+ continue
172
+ }
173
+ lines.push({
174
+ text: lineTokens.map((token) => token.text).join(""),
175
+ startCol: lineTokens[0].startCol,
176
+ endCol: lineTokens[lineTokens.length - 1].endCol,
177
+ })
178
+ }
179
+
180
+ return lines.length > 0 ? lines : [{ text: "", startCol: 0, endCol: 0 }]
181
+ }
182
+
183
+ /**
184
+ * Wrap spans into lines, breaking at word boundaries.
185
+ * Preserves span styling by cloning span objects with updated text.
186
+ */
187
+ export function wrapSpans<T extends SpanLike>(spans: T[], maxWidth: number, options?: WrapOptions): T[][] {
188
+ const tokens: Array<TokenSpec<T>> = []
189
+ for (const span of spans) {
190
+ for (const token of splitWords(span.text)) {
191
+ if (!token) continue
192
+ const segments = graphemes(token)
193
+ tokens.push({
194
+ text: token,
195
+ width: displayWidth(token),
196
+ graphemeCount: segments.length,
197
+ segments,
198
+ isWhitespace: isWhitespace(token),
199
+ make: (text) => ({ ...span, text }),
200
+ })
201
+ }
202
+ }
203
+
204
+ return wrapTokens(tokens, maxWidth, options)
205
+ }
206
+
207
+ /**
208
+ * Split spans into logical lines at newline boundaries.
209
+ * Preserves empty lines.
210
+ */
211
+ export function splitSpansByNewline<T extends SpanLike>(spans: T[]): T[][] {
212
+ const lines: T[][] = [[]]
213
+
214
+ for (const span of spans) {
215
+ if (!span.text) continue
216
+ const parts = span.text.split("\n")
217
+ for (let i = 0; i < parts.length; i++) {
218
+ if (i > 0) {
219
+ lines.push([])
220
+ }
221
+ const part = parts[i]
222
+ if (part) {
223
+ lines[lines.length - 1].push({ ...span, text: part })
224
+ }
225
+ }
226
+ }
227
+
66
228
  return lines.length > 0 ? lines : [[]]
67
229
  }
230
+
231
+ /**
232
+ * Wrap spans per logical line (newline-aware).
233
+ */
234
+ export function wrapSpansByLine<T extends SpanLike>(spans: T[], maxWidth: number, options?: WrapOptions): T[][] {
235
+ const logicalLines = splitSpansByNewline(spans)
236
+ const lines: T[][] = []
237
+
238
+ for (const line of logicalLines) {
239
+ if (line.length === 0) {
240
+ lines.push([])
241
+ continue
242
+ }
243
+ lines.push(...wrapSpans(line, maxWidth, options))
244
+ }
245
+
246
+ return lines.length > 0 ? lines : [[]]
247
+ }
248
+
249
+ /**
250
+ * Wrap plain text into lines, breaking at word boundaries.
251
+ * Trims trailing whitespace when a line breaks.
252
+ */
253
+ export function wrapText(text: string, maxWidth: number): string[] {
254
+ const lines: string[] = []
255
+ for (const rawLine of text.split("\n")) {
256
+ if (rawLine === "") {
257
+ lines.push("")
258
+ continue
259
+ }
260
+ const tokens: Array<TokenSpec<string>> = []
261
+ for (const token of splitWords(rawLine)) {
262
+ if (!token) continue
263
+ const segments = graphemes(token)
264
+ tokens.push({
265
+ text: token,
266
+ width: displayWidth(token),
267
+ graphemeCount: segments.length,
268
+ segments,
269
+ isWhitespace: isWhitespace(token),
270
+ make: (segment) => segment,
271
+ })
272
+ }
273
+ const wrapped = wrapTokens(tokens, maxWidth, { trimTrailingWhitespace: true })
274
+ for (const line of wrapped) {
275
+ lines.push(line.join(""))
276
+ }
277
+ }
278
+
279
+ return lines.length > 0 ? lines : [""]
280
+ }
@@ -273,7 +273,7 @@ export const visualize = <A, E, R>(
273
273
  : { status: "failure", elapsedMs: Date.now() - startTime, errorSummary: summarizeCause(exit.cause) },
274
274
  )
275
275
  renderer.requestRender()
276
- renderer.flush() // Ensure final state is rendered
276
+ renderer.renderNow() // Ensure final state is rendered
277
277
 
278
278
  // Wait for completion animation
279
279
  yield* Effect.sleep(COMPLETION_DELAY_MS)
@@ -1,65 +0,0 @@
1
- import type { TuiWriteStream } from "../../renderer-types.js"
2
-
3
- export interface ResizeState {
4
- width: number
5
- height: number
6
- lastWidth: number
7
- previousHeight: number // For inline mode
8
- inlineClearHeight: number
9
- }
10
-
11
- export interface ResizeResult {
12
- newWidth: number
13
- newHeight: number
14
- needsFullClear: boolean // Fullscreen mode
15
- inlineClearHeight: number // Inline mode
16
- }
17
-
18
- /**
19
- * Handles terminal resize events and calculates necessary clear/redraw actions.
20
- */
21
- export class ResizeManager {
22
- constructor(
23
- private stdout: TuiWriteStream,
24
- private mode: "fullscreen" | "inline",
25
- ) {}
26
-
27
- /**
28
- * Process a resize event and determine what actions are needed.
29
- */
30
- handleResize(currentState: ResizeState): ResizeResult {
31
- const newWidth = this.stdout.columns || 80
32
- const newHeight = this.stdout.rows || 24
33
-
34
- let needsFullClear = false
35
- let inlineClearHeight = currentState.inlineClearHeight
36
-
37
- if (this.mode === "fullscreen") {
38
- // Fullscreen: defer clear to renderFrame so it's written atomically with content
39
- needsFullClear = true
40
- } else if (newWidth !== currentState.lastWidth || newHeight !== currentState.height) {
41
- // Inline: on dimension change, request clear on next render
42
- const clearHeight = Math.max(currentState.previousHeight, currentState.height, newHeight)
43
- if (clearHeight > inlineClearHeight) {
44
- inlineClearHeight = clearHeight
45
- }
46
- }
47
-
48
- return {
49
- newWidth,
50
- newHeight,
51
- needsFullClear,
52
- inlineClearHeight,
53
- }
54
- }
55
-
56
- /**
57
- * Get current terminal dimensions.
58
- */
59
- getDimensions(): { width: number; height: number } {
60
- return {
61
- width: this.stdout.columns || 80,
62
- height: this.stdout.rows || 24,
63
- }
64
- }
65
- }
@@ -1,4 +0,0 @@
1
- export { EventBus } from "./EventBus.js"
2
- export { getRenderCache, type RenderCache } from "./RenderCache.js"
3
- export { ResizeManager, type ResizeResult, type ResizeState } from "./ResizeManager.js"
4
- export { TerminalSetup, type TerminalSetupConfig } from "./TerminalSetup.js"