ratatui_ruby 1.4.0-x86_64-linux

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 (292) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +15 -0
  3. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  4. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  5. data/LICENSES/CC0-1.0.txt +121 -0
  6. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  7. data/LICENSES/MIT-0.txt +16 -0
  8. data/LICENSES/MIT.txt +21 -0
  9. data/REUSE.toml +42 -0
  10. data/exe/.gitkeep +0 -0
  11. data/ext/ratatui_ruby/.cargo/config.toml +13 -0
  12. data/ext/ratatui_ruby/.gitignore +4 -0
  13. data/ext/ratatui_ruby/Cargo.lock +1737 -0
  14. data/ext/ratatui_ruby/Cargo.toml +24 -0
  15. data/ext/ratatui_ruby/clippy.toml +7 -0
  16. data/ext/ratatui_ruby/extconf.rb +21 -0
  17. data/ext/ratatui_ruby/src/color.rs +82 -0
  18. data/ext/ratatui_ruby/src/errors.rs +28 -0
  19. data/ext/ratatui_ruby/src/events.rs +700 -0
  20. data/ext/ratatui_ruby/src/frame.rs +241 -0
  21. data/ext/ratatui_ruby/src/lib.rs +343 -0
  22. data/ext/ratatui_ruby/src/lib_header.rs +11 -0
  23. data/ext/ratatui_ruby/src/rendering.rs +158 -0
  24. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  25. data/ext/ratatui_ruby/src/style.rs +469 -0
  26. data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
  27. data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
  28. data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
  29. data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
  30. data/ext/ratatui_ruby/src/terminal/queries.rs +231 -0
  31. data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
  32. data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
  33. data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
  34. data/ext/ratatui_ruby/src/text.rs +225 -0
  35. data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
  36. data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
  37. data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
  38. data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
  39. data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
  40. data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
  41. data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
  42. data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
  43. data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
  44. data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
  45. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
  46. data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
  47. data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
  48. data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
  49. data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
  50. data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
  51. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  52. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
  53. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
  54. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  55. data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
  56. data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
  57. data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
  58. data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
  59. data/lib/ratatui_ruby/backend/window_size.rb +50 -0
  60. data/lib/ratatui_ruby/backend.rb +59 -0
  61. data/lib/ratatui_ruby/buffer/cell.rb +212 -0
  62. data/lib/ratatui_ruby/buffer.rb +149 -0
  63. data/lib/ratatui_ruby/cell.rb +208 -0
  64. data/lib/ratatui_ruby/debug.rb +215 -0
  65. data/lib/ratatui_ruby/draw.rb +63 -0
  66. data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
  67. data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
  68. data/lib/ratatui_ruby/event/key/character.rb +53 -0
  69. data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
  70. data/lib/ratatui_ruby/event/key/media.rb +46 -0
  71. data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
  72. data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
  73. data/lib/ratatui_ruby/event/key/system.rb +47 -0
  74. data/lib/ratatui_ruby/event/key.rb +479 -0
  75. data/lib/ratatui_ruby/event/mouse.rb +291 -0
  76. data/lib/ratatui_ruby/event/none.rb +53 -0
  77. data/lib/ratatui_ruby/event/paste.rb +130 -0
  78. data/lib/ratatui_ruby/event/resize.rb +221 -0
  79. data/lib/ratatui_ruby/event/sync.rb +52 -0
  80. data/lib/ratatui_ruby/event.rb +163 -0
  81. data/lib/ratatui_ruby/frame.rb +257 -0
  82. data/lib/ratatui_ruby/labs/a11y.rb +182 -0
  83. data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
  84. data/lib/ratatui_ruby/labs.rb +47 -0
  85. data/lib/ratatui_ruby/layout/alignment.rb +91 -0
  86. data/lib/ratatui_ruby/layout/constraint.rb +337 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +258 -0
  88. data/lib/ratatui_ruby/layout/position.rb +81 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +733 -0
  90. data/lib/ratatui_ruby/layout/size.rb +62 -0
  91. data/lib/ratatui_ruby/layout.rb +29 -0
  92. data/lib/ratatui_ruby/list_state.rb +201 -0
  93. data/lib/ratatui_ruby/output_guard.rb +171 -0
  94. data/lib/ratatui_ruby/ratatui_ruby.so +0 -0
  95. data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
  96. data/lib/ratatui_ruby/style/color.rb +149 -0
  97. data/lib/ratatui_ruby/style/style.rb +147 -0
  98. data/lib/ratatui_ruby/style.rb +19 -0
  99. data/lib/ratatui_ruby/symbols.rb +435 -0
  100. data/lib/ratatui_ruby/synthetic_events.rb +106 -0
  101. data/lib/ratatui_ruby/table_state.rb +251 -0
  102. data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
  103. data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
  104. data/lib/ratatui_ruby/terminal.rb +66 -0
  105. data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
  106. data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
  107. data/lib/ratatui_ruby/test_helper/event_injection.rb +241 -0
  108. data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
  109. data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
  110. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  111. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  112. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  113. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  114. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  115. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  116. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  117. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  118. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  119. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  120. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  121. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  122. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  123. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  124. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  125. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  126. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  127. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  128. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  129. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  130. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  131. data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
  132. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  133. data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
  134. data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
  135. data/lib/ratatui_ruby/test_helper.rb +115 -0
  136. data/lib/ratatui_ruby/text/line.rb +245 -0
  137. data/lib/ratatui_ruby/text/span.rb +158 -0
  138. data/lib/ratatui_ruby/text.rb +99 -0
  139. data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
  140. data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
  141. data/lib/ratatui_ruby/tui/core.rb +67 -0
  142. data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
  143. data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
  144. data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
  145. data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
  146. data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
  147. data/lib/ratatui_ruby/tui.rb +106 -0
  148. data/lib/ratatui_ruby/version.rb +12 -0
  149. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
  150. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
  151. data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
  152. data/lib/ratatui_ruby/widgets/block.rb +266 -0
  153. data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
  154. data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
  155. data/lib/ratatui_ruby/widgets/cell.rb +59 -0
  156. data/lib/ratatui_ruby/widgets/center.rb +71 -0
  157. data/lib/ratatui_ruby/widgets/chart.rb +172 -0
  158. data/lib/ratatui_ruby/widgets/clear.rb +66 -0
  159. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  160. data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
  161. data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
  162. data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
  163. data/lib/ratatui_ruby/widgets/list.rb +252 -0
  164. data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
  165. data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
  166. data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
  167. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
  168. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
  169. data/lib/ratatui_ruby/widgets/row.rb +123 -0
  170. data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
  171. data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
  172. data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
  173. data/lib/ratatui_ruby/widgets/table.rb +213 -0
  174. data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
  175. data/lib/ratatui_ruby/widgets.rb +43 -0
  176. data/lib/ratatui_ruby.rb +555 -0
  177. data/sig/examples/app_all_events/app.rbs +11 -0
  178. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  179. data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
  180. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  181. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  182. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  183. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  184. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  185. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  186. data/sig/examples/app_all_events/view.rbs +14 -0
  187. data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
  188. data/sig/examples/app_color_picker/app.rbs +17 -0
  189. data/sig/examples/app_external_editor/app.rbs +12 -0
  190. data/sig/examples/app_login_form/app.rbs +11 -0
  191. data/sig/examples/app_stateful_interaction/app.rbs +39 -0
  192. data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
  193. data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
  194. data/sig/examples/verify_readme_usage/app.rbs +17 -0
  195. data/sig/examples/widget_block_demo/app.rbs +38 -0
  196. data/sig/examples/widget_box_demo/app.rbs +17 -0
  197. data/sig/examples/widget_calendar_demo/app.rbs +17 -0
  198. data/sig/examples/widget_cell_demo/app.rbs +17 -0
  199. data/sig/examples/widget_chart_demo/app.rbs +17 -0
  200. data/sig/examples/widget_gauge_demo/app.rbs +17 -0
  201. data/sig/examples/widget_layout_split/app.rbs +16 -0
  202. data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
  203. data/sig/examples/widget_list_demo/app.rbs +17 -0
  204. data/sig/examples/widget_map_demo/app.rbs +17 -0
  205. data/sig/examples/widget_popup_demo/app.rbs +17 -0
  206. data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
  207. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
  208. data/sig/examples/widget_rect/app.rbs +18 -0
  209. data/sig/examples/widget_render/app.rbs +16 -0
  210. data/sig/examples/widget_rich_text/app.rbs +17 -0
  211. data/sig/examples/widget_scroll_text/app.rbs +17 -0
  212. data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
  213. data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
  214. data/sig/examples/widget_style_colors/app.rbs +20 -0
  215. data/sig/examples/widget_table_demo/app.rbs +17 -0
  216. data/sig/examples/widget_text_width/app.rbs +16 -0
  217. data/sig/generated/event_key_predicates.rbs +1348 -0
  218. data/sig/manifest.yaml +5 -0
  219. data/sig/patches/data.rbs +26 -0
  220. data/sig/patches/debugger__.rbs +8 -0
  221. data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
  222. data/sig/ratatui_ruby/backend.rbs +12 -0
  223. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  224. data/sig/ratatui_ruby/buffer.rbs +18 -0
  225. data/sig/ratatui_ruby/cell.rbs +44 -0
  226. data/sig/ratatui_ruby/clear.rbs +18 -0
  227. data/sig/ratatui_ruby/constraint.rbs +26 -0
  228. data/sig/ratatui_ruby/debug.rbs +45 -0
  229. data/sig/ratatui_ruby/draw.rbs +30 -0
  230. data/sig/ratatui_ruby/event.rbs +249 -0
  231. data/sig/ratatui_ruby/frame.rbs +23 -0
  232. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  233. data/sig/ratatui_ruby/labs.rbs +90 -0
  234. data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
  235. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  236. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  237. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  238. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  239. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  240. data/sig/ratatui_ruby/list_state.rbs +23 -0
  241. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  242. data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
  243. data/sig/ratatui_ruby/rect.rbs +17 -0
  244. data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
  245. data/sig/ratatui_ruby/session.rbs +93 -0
  246. data/sig/ratatui_ruby/style/color.rbs +22 -0
  247. data/sig/ratatui_ruby/style/style.rbs +29 -0
  248. data/sig/ratatui_ruby/symbols.rbs +141 -0
  249. data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
  250. data/sig/ratatui_ruby/table_state.rbs +27 -0
  251. data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
  252. data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
  253. data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
  254. data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
  255. data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
  256. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
  257. data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
  258. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
  259. data/sig/ratatui_ruby/test_helper.rbs +18 -0
  260. data/sig/ratatui_ruby/text/line.rbs +27 -0
  261. data/sig/ratatui_ruby/text/span.rbs +23 -0
  262. data/sig/ratatui_ruby/text.rbs +12 -0
  263. data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
  264. data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
  265. data/sig/ratatui_ruby/tui/core.rbs +23 -0
  266. data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
  267. data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
  268. data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
  269. data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
  270. data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
  271. data/sig/ratatui_ruby/tui.rbs +25 -0
  272. data/sig/ratatui_ruby/version.rbs +12 -0
  273. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  274. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  275. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  276. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  277. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  278. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  279. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  280. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  281. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  282. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  283. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  284. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  285. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  286. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  287. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  288. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  289. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  290. data/sig/ratatui_ruby/widgets.rbs +16 -0
  291. data/vendor/goodcop/base.yml +1047 -0
  292. metadata +729 -0
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ ##
10
+ # Terminal lifecycle management for TUI sessions.
11
+ #
12
+ # This module provides methods to initialize, restore, and manage the terminal
13
+ # state for TUI applications. It handles raw mode, alternate screen, and ensures
14
+ # proper cleanup on exit.
15
+ #
16
+ # @see init_terminal
17
+ # @see restore_terminal
18
+ # @see run
19
+ module TerminalLifecycle
20
+ ##
21
+ # Whether a TUI session is currently active.
22
+ #
23
+ # Writing to stdout/stderr during a TUI session corrupts the display.
24
+ # Use this to defer logging, warnings, or debug output until
25
+ # after the session ends.
26
+ #
27
+ # === Example
28
+ #
29
+ #--
30
+ # SPDX-SnippetBegin
31
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
32
+ # SPDX-License-Identifier: MIT-0
33
+ #++
34
+ # def log(message)
35
+ # if RatatuiRuby.terminal_active?
36
+ # @deferred_logs << message
37
+ # else
38
+ # puts message
39
+ # end
40
+ # end
41
+ #--
42
+ # SPDX-SnippetEnd
43
+ #++
44
+ def terminal_active?
45
+ @tui_session_active
46
+ end
47
+
48
+ ##
49
+ # Initializes the terminal for TUI mode.
50
+ # Enters alternate screen and enables raw mode.
51
+ #
52
+ # In headless mode ({headless!}), this method raises {Error::Invariant}.
53
+ # Use headless mode for batch/CLI apps.
54
+ #
55
+ # [focus_events] whether to enable focus gain/loss events (default: true).
56
+ # [bracketed_paste] whether to enable bracketed paste mode (default: true).
57
+ #
58
+ # @raise [Error::Invariant] if headless mode is enabled or a session is already active
59
+ # @see headless!
60
+ def init_terminal(focus_events: true, bracketed_paste: true, viewport: nil, height: nil)
61
+ if @headless_mode
62
+ raise Error::Invariant, "Cannot initialize terminal: headless mode is enabled"
63
+ end
64
+ if @tui_session_active
65
+ raise Error::Invariant, "Cannot initialize terminal: TUI session already active"
66
+ end
67
+
68
+ # Show A11Y lab prompt before launching TUI (stdout visible now, not after)
69
+ if Labs.enabled?(:a11y)
70
+ puts Labs::A11y.startup_message
71
+ $stdin.gets
72
+ end
73
+
74
+ @tui_session_active = true
75
+
76
+ viewport_obj = resolve_viewport(viewport, height)
77
+ _init_terminal(focus_events, bracketed_paste, viewport_obj.type.to_s, viewport_obj.height)
78
+ end
79
+
80
+ ##
81
+ # Initializes a test terminal for unit testing.
82
+ # Sets session active state like init_terminal.
83
+ #
84
+ # [width] Integer width of the test terminal.
85
+ # [height] Integer height of the test terminal.
86
+ #
87
+ # @raise [Error::Invariant] if headless mode is enabled or a session is already active
88
+ def init_test_terminal(width, height, viewport_type = "fullscreen", viewport_height = nil)
89
+ if @headless_mode
90
+ raise Error::Invariant, "Cannot initialize terminal: headless mode is enabled"
91
+ end
92
+ if @tui_session_active
93
+ raise Error::Invariant, "Cannot initialize terminal: TUI session already active"
94
+ end
95
+ @tui_session_active = true
96
+ _init_test_terminal(width, height, viewport_type, viewport_height)
97
+ end
98
+
99
+ ##
100
+ # Restores the terminal to its original state.
101
+ # Leaves alternate screen and disables raw mode.
102
+ # Also flushes any deferred warnings and panic info that were queued during the session.
103
+ #
104
+ # In headless mode ({headless!}), this method is a silent no-op since
105
+ # no terminal was ever initialized.
106
+ #
107
+ # @see headless!
108
+ def restore_terminal
109
+ return if @headless_mode
110
+
111
+ _restore_terminal
112
+ ensure
113
+ @tui_session_active = false
114
+ flush_warnings
115
+ flush_panic_info
116
+ end
117
+
118
+ ##
119
+ # Starts the TUI application lifecycle.
120
+ #
121
+ # Managing generic setup/teardown (raw mode, alternate screen) manually is error-prone.
122
+ # If your app crashes, the terminal might be left in a broken state.
123
+ #
124
+ # This method handles the safety net. It initializes the terminal, yields a {TUI},
125
+ # and ensures the terminal state is restored even if exceptions occur.
126
+ #
127
+ # In headless mode ({headless!}), this method raises {Error::Invariant} immediately
128
+ # and the block is never executed. Use headless mode for batch/CLI apps.
129
+ #
130
+ # === Example
131
+ #
132
+ #--
133
+ # SPDX-SnippetBegin
134
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
135
+ # SPDX-License-Identifier: MIT-0
136
+ #++
137
+ # RatatuiRuby.run(focus_events: false) do |tui|
138
+ # tui.draw(tui.paragraph(text: "Hi"))
139
+ # sleep 1
140
+ # end
141
+ #
142
+ #--
143
+ # SPDX-SnippetEnd
144
+ #++
145
+ # @raise [Error::Invariant] if headless mode is enabled
146
+ # @see headless!
147
+ def run(focus_events: true, bracketed_paste: true, viewport: nil, height: nil)
148
+ init_terminal(focus_events:, bracketed_paste:, viewport:, height:)
149
+ yield TUI.new
150
+ ensure
151
+ restore_terminal
152
+ end
153
+
154
+ ##
155
+ # Inserts content above an inline viewport into scrollback.
156
+ #
157
+ # Your inline TUI runs at the bottom of the terminal. Users scroll back
158
+ # to see earlier CLI output. You want to add new content to that scrollback
159
+ # without disrupting the running TUI viewport.
160
+ #
161
+ # Calling <tt>draw</tt> only updates the viewport itself. Content drawn there
162
+ # disappears into scrollback when the viewport moves. You have no way to insert
163
+ # historical content directly into the scrollback buffer above your running TUI.
164
+ #
165
+ # This method inserts content above the viewport. The viewport stays live and interactive.
166
+ # New content appears above it in scrollback history. If the viewport isn't yet at the
167
+ # bottom of the screen, it shifts down. Once at the bottom, inserted content scrolls
168
+ # the scrollback buffer upward.
169
+ #
170
+ # Use it to log status updates, progress messages, or diagnostic output while your
171
+ # inline TUI continues running.
172
+ #
173
+ # === Behavior
174
+ #
175
+ # The method renders a widget into a temporary buffer of <tt>height</tt> lines,
176
+ # then inserts that buffer above the viewport. The viewport may shift position
177
+ # on screen to accommodate the new lines.
178
+ #
179
+ # When the viewport has room to move down (not yet at screen bottom):
180
+ # - Inserted lines appear above the viewport
181
+ # - The viewport shifts down by <tt>height</tt> lines
182
+ # - The viewport's visual position changes
183
+ #
184
+ # When the viewport is already at the screen bottom:
185
+ # - Inserted lines push existing scrollback upward
186
+ # - The viewport stays at the bottom
187
+ # - The viewport's content may be disturbed by scrolling
188
+ #
189
+ # After calling this method, you should call <tt>draw</tt> to redraw the viewport's
190
+ # content over its new position. The viewport area may have been clobbered by
191
+ # the insertion or scrolling.
192
+ #
193
+ # This method has no effect when called on a fullscreen viewport. Fullscreen
194
+ # viewports have no scrollback buffer.
195
+ #
196
+ # [height] Number of lines to insert (Integer). Must be positive.
197
+ # [widget] Widget to render into the inserted buffer, or <tt>nil</tt> if using block.
198
+ # [block] Optional block that returns a widget to render.
199
+ #
200
+ # === Examples
201
+ #
202
+ # ==== Insert a status line while TUI runs
203
+ #
204
+ #--
205
+ # SPDX-SnippetBegin
206
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
207
+ # SPDX-License-Identifier: MIT-0
208
+ #++
209
+ # RatatuiRuby.run(viewport: :inline, height: 3) do |tui|
210
+ # loop do
211
+ # tui.draw { |f| f.render_widget(tui.paragraph(text: "TUI Running"), f.area) }
212
+ #
213
+ # event = tui.poll_event
214
+ # if event[:type] == :key && event[:code] == "l"
215
+ # # Insert a log line above the running TUI
216
+ # tui.insert_before(1, tui.paragraph(text: "[#{Time.now}] User pressed 'l'"))
217
+ # # Redraw viewport to restore its content
218
+ # tui.draw { |f| f.render_widget(tui.paragraph(text: "TUI Running"), f.area) }
219
+ # end
220
+ # end
221
+ # end
222
+ #--
223
+ # SPDX-SnippetEnd
224
+ #++
225
+ #
226
+ # @raise [Error::Invariant] if the current viewport is not inline
227
+ def insert_before(height, widget = nil, &block)
228
+ # Validate we're in an inline viewport
229
+ viewport_type = RatatuiRuby._get_viewport_type
230
+ unless viewport_type == "inline"
231
+ raise Error::Invariant, "insert_before requires an inline viewport"
232
+ end
233
+
234
+ content = widget || block&.call
235
+ _insert_before(height, content)
236
+ end
237
+
238
+ private def resolve_viewport(viewport, height)
239
+ case viewport
240
+ when nil, :fullscreen then Terminal::Viewport.fullscreen
241
+ when :inline then Terminal::Viewport.inline(height || 8)
242
+ when Terminal::Viewport then viewport
243
+ else raise ArgumentError, "Unknown viewport: #{viewport.inspect}"
244
+ end
245
+ end
246
+
247
+ # Sets the cursor position using a Position object or coordinates.
248
+ #
249
+ # This is a convenience alias for +set_cursor_position+ that works with
250
+ # Position objects or raw arrays rather than separate x/y arguments.
251
+ #
252
+ # [position] A Layout::Position, Array of [x, y], or anything that responds to +x+ and +y+.
253
+ #
254
+ # === Examples
255
+ #
256
+ #--
257
+ # SPDX-SnippetBegin
258
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
259
+ # SPDX-License-Identifier: MIT-0
260
+ #++
261
+ # # With Position object
262
+ # pos = RatatuiRuby::Layout::Position.new(x: 10, y: 5)
263
+ # RatatuiRuby.cursor_position = pos
264
+ #
265
+ # # With array shorthand
266
+ # RatatuiRuby.cursor_position = 10, 5
267
+ #--
268
+ # SPDX-SnippetEnd
269
+ #++
270
+ def cursor_position=(position)
271
+ if position.is_a?(Array)
272
+ x, y = position
273
+ set_cursor_position(x, y)
274
+ else
275
+ set_cursor_position(position.x, position.y)
276
+ end
277
+ end
278
+
279
+ # Gets the cursor position as a Position object.
280
+ #
281
+ # This is a convenience alias for +get_cursor_position+ that returns
282
+ # a Position object rather than raw coordinates.
283
+ #
284
+ # Returns:: A Layout::Position with the current cursor coordinates.
285
+ #
286
+ # === Example
287
+ #
288
+ #--
289
+ # SPDX-SnippetBegin
290
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
291
+ # SPDX-License-Identifier: MIT-0
292
+ #++
293
+ # pos = RatatuiRuby.cursor_position
294
+ # puts "Cursor at #{pos.x}, #{pos.y}"
295
+ #--
296
+ # SPDX-SnippetEnd
297
+ #++
298
+ def cursor_position
299
+ x, y = get_cursor_position
300
+ Layout::Position.new(x:, y:)
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ ##
10
+ # Terminal lifecycle management for TUI sessions.
11
+ #
12
+ # This module provides methods to initialize, restore, and manage the terminal
13
+ # state for TUI applications. It handles raw mode, alternate screen, and ensures
14
+ # proper cleanup on exit.
15
+ #
16
+ # @see init_terminal
17
+ # @see restore_terminal
18
+ # @see run
19
+ module TerminalLifecycle
20
+ ##
21
+ # Whether a TUI session is currently active.
22
+ #
23
+ # Writing to stdout/stderr during a TUI session corrupts the display.
24
+ # Use this to defer logging, warnings, or debug output until
25
+ # after the session ends.
26
+ #
27
+ # === Example
28
+ #
29
+ #--
30
+ # SPDX-SnippetBegin
31
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
32
+ # SPDX-License-Identifier: MIT-0
33
+ #++
34
+ # def log(message)
35
+ # if RatatuiRuby.terminal_active?
36
+ # @deferred_logs << message
37
+ # else
38
+ # puts message
39
+ # end
40
+ # end
41
+ #--
42
+ # SPDX-SnippetEnd
43
+ #++
44
+ def terminal_active?
45
+ @tui_session_active
46
+ end
47
+
48
+ ##
49
+ # Initializes the terminal for TUI mode.
50
+ # Enters alternate screen and enables raw mode.
51
+ #
52
+ # In headless mode ({headless!}), this method raises {Error::Invariant}.
53
+ # Use headless mode for batch/CLI apps.
54
+ #
55
+ # [focus_events] whether to enable focus gain/loss events (default: true).
56
+ # [bracketed_paste] whether to enable bracketed paste mode (default: true).
57
+ #
58
+ # @raise [Error::Invariant] if headless mode is enabled or a session is already active
59
+ # @see headless!
60
+ def init_terminal(focus_events: true, bracketed_paste: true, viewport: nil, height: nil)
61
+ if @headless_mode
62
+ raise Error::Invariant, "Cannot initialize terminal: headless mode is enabled"
63
+ end
64
+ if @tui_session_active
65
+ raise Error::Invariant, "Cannot initialize terminal: TUI session already active"
66
+ end
67
+
68
+ # Show A11Y lab prompt before launching TUI (stdout visible now, not after)
69
+ if Labs.enabled?(:a11y)
70
+ puts Labs::A11y.startup_message
71
+ $stdin.gets
72
+ end
73
+
74
+ @tui_session_active = true
75
+
76
+ viewport_obj = resolve_viewport(viewport, height)
77
+ _init_terminal(focus_events, bracketed_paste, viewport_obj.type.to_s, viewport_obj.height)
78
+ end
79
+
80
+ ##
81
+ # Initializes a test terminal for unit testing.
82
+ # Sets session active state like init_terminal.
83
+ #
84
+ # [width] Integer width of the test terminal.
85
+ # [height] Integer height of the test terminal.
86
+ #
87
+ # @raise [Error::Invariant] if headless mode is enabled or a session is already active
88
+ def init_test_terminal(width, height, viewport_type = "fullscreen", viewport_height = nil)
89
+ if @headless_mode
90
+ raise Error::Invariant, "Cannot initialize terminal: headless mode is enabled"
91
+ end
92
+ if @tui_session_active
93
+ raise Error::Invariant, "Cannot initialize terminal: TUI session already active"
94
+ end
95
+ @tui_session_active = true
96
+ _init_test_terminal(width, height, viewport_type, viewport_height)
97
+ end
98
+
99
+ ##
100
+ # Restores the terminal to its original state.
101
+ # Leaves alternate screen and disables raw mode.
102
+ # Also flushes any deferred warnings and panic info that were queued during the session.
103
+ #
104
+ # In headless mode ({headless!}), this method is a silent no-op since
105
+ # no terminal was ever initialized.
106
+ #
107
+ # @see headless!
108
+ def restore_terminal
109
+ return if @headless_mode
110
+
111
+ _restore_terminal
112
+ ensure
113
+ @tui_session_active = false
114
+ flush_warnings
115
+ flush_panic_info
116
+ end
117
+
118
+ ##
119
+ # Starts the TUI application lifecycle.
120
+ #
121
+ # Managing generic setup/teardown (raw mode, alternate screen) manually is error-prone.
122
+ # If your app crashes, the terminal might be left in a broken state.
123
+ #
124
+ # This method handles the safety net. It initializes the terminal, yields a {TUI},
125
+ # and ensures the terminal state is restored even if exceptions occur.
126
+ #
127
+ # In headless mode ({headless!}), this method raises {Error::Invariant} immediately
128
+ # and the block is never executed. Use headless mode for batch/CLI apps.
129
+ #
130
+ # === Example
131
+ #
132
+ #--
133
+ # SPDX-SnippetBegin
134
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
135
+ # SPDX-License-Identifier: MIT-0
136
+ #++
137
+ # RatatuiRuby.run(focus_events: false) do |tui|
138
+ # tui.draw(tui.paragraph(text: "Hi"))
139
+ # sleep 1
140
+ # end
141
+ #
142
+ #--
143
+ # SPDX-SnippetEnd
144
+ #++
145
+ # @raise [Error::Invariant] if headless mode is enabled
146
+ # @see headless!
147
+ def run(focus_events: true, bracketed_paste: true, viewport: nil, height: nil)
148
+ init_terminal(focus_events:, bracketed_paste:, viewport:, height:)
149
+ yield TUI.new
150
+ ensure
151
+ restore_terminal
152
+ end
153
+
154
+ ##
155
+ # Inserts content above an inline viewport.
156
+ #
157
+ # Only works with inline viewports. Content is rendered above the TUI
158
+ # and becomes part of terminal scrollback history.
159
+ #
160
+ # [height] Number of lines to insert (Integer)
161
+ # [widget] Widget to render, or nil if using block
162
+ # [block] Optional block that returns a widget
163
+ #
164
+ # === Example
165
+ #
166
+ #--
167
+ # SPDX-SnippetBegin
168
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
169
+ # SPDX-License-Identifier: MIT-0
170
+ #++
171
+ # RatatuiRuby.insert_before(1, Widgets::Paragraph.new(text: "Status: Done"))
172
+ #--
173
+ # SPDX-SnippetEnd
174
+ #++
175
+ def insert_before(height, widget = nil, &block)
176
+ # Validate we're in an inline viewport
177
+ viewport_type = _get_viewport_type
178
+ unless viewport_type == "inline"
179
+ raise Error::Invariant, "insert_before requires an inline viewport"
180
+ end
181
+
182
+ content = widget || block&.call
183
+ _insert_before(height, content)
184
+ end
185
+
186
+ private
187
+
188
+ def resolve_viewport(viewport, height)
189
+ case viewport
190
+ when nil, :fullscreen then Terminal::Viewport.fullscreen
191
+ when :inline then Terminal::Viewport.inline(height || 8)
192
+ when Terminal::Viewport then viewport
193
+ else raise ArgumentError, "Unknown viewport: #{viewport.inspect}"
194
+ end
195
+ end
196
+ end
197
+ end