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,251 @@
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
+ # Mutable state object for Table widgets.
10
+ #
11
+ # When using {Frame#render_stateful_widget}, the State object is the
12
+ # *single source of truth* for selection and scroll offset. Widget
13
+ # properties (+selected_row+, +selected_column+, +offset+) are *ignored*
14
+ # in stateful mode.
15
+ #
16
+ # == Example
17
+ #
18
+ #--
19
+ # SPDX-SnippetBegin
20
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
21
+ # SPDX-License-Identifier: MIT-0
22
+ #++
23
+ # @table_state = RatatuiRuby::TableState.new
24
+ # @table_state.select(1) # Select second row
25
+ # @table_state.select_column(0) # Select first column
26
+ #
27
+ # RatatuiRuby.draw do |frame|
28
+ # table = RatatuiRuby::Widgets::Table.new(rows: [...], widths: [...])
29
+ # frame.render_stateful_widget(table, frame.area, @table_state)
30
+ # end
31
+ #
32
+ #--
33
+ # SPDX-SnippetEnd
34
+ #++
35
+ class TableState
36
+ ##
37
+ # :method: new
38
+ # :call-seq: new(selected = nil) -> TableState
39
+ #
40
+ # Creates a new TableState with optional initial row selection.
41
+ #
42
+ # (Native method implemented in Rust)
43
+
44
+ ##
45
+ # :method: select
46
+ # :call-seq: select(index) -> nil
47
+ #
48
+ # Sets the selected row index. Pass +nil+ to deselect.
49
+ #
50
+ # (Native method implemented in Rust)
51
+
52
+ ##
53
+ # :method: selected
54
+ # :call-seq: selected() -> Integer or nil
55
+ #
56
+ # Returns the currently selected row index.
57
+ #
58
+ # (Native method implemented in Rust)
59
+
60
+ ##
61
+ # :method: select_column
62
+ # :call-seq: select_column(index) -> nil
63
+ #
64
+ # Sets the selected column index. Pass +nil+ to deselect.
65
+ #
66
+ # (Native method implemented in Rust)
67
+
68
+ ##
69
+ # :method: selected_column
70
+ # :call-seq: selected_column() -> Integer or nil
71
+ #
72
+ # Returns the currently selected column index.
73
+ #
74
+ # (Native method implemented in Rust)
75
+
76
+ ##
77
+ # :method: offset
78
+ # :call-seq: offset() -> Integer
79
+ #
80
+ # Returns the current scroll offset.
81
+ #
82
+ # (Native method implemented in Rust)
83
+
84
+ ##
85
+ # :method: scroll_down_by
86
+ # :call-seq: scroll_down_by(n) -> nil
87
+ #
88
+ # Scrolls down by +n+ rows.
89
+ #
90
+ # (Native method implemented in Rust)
91
+
92
+ ##
93
+ # :method: scroll_up_by
94
+ # :call-seq: scroll_up_by(n) -> nil
95
+ #
96
+ # Scrolls up by +n+ rows.
97
+ #
98
+ # (Native method implemented in Rust)
99
+
100
+ ##
101
+ # :method: selected_cell
102
+ # :call-seq: selected_cell() -> Array or nil
103
+ #
104
+ # Returns the currently selected cell as <tt>[row, column]</tt>.
105
+ # Returns +nil+ if either row or column is not selected.
106
+ #
107
+ # (Native method implemented in Rust)
108
+
109
+ ##
110
+ # :method: select_next_column
111
+ # :call-seq: select_next_column() -> nil
112
+ #
113
+ # Selects the next column, or column 0 if none selected.
114
+ #
115
+ # (Native method implemented in Rust)
116
+
117
+ ##
118
+ # :method: select_previous_column
119
+ # :call-seq: select_previous_column() -> nil
120
+ #
121
+ # Selects the previous column. Saturates at 0.
122
+ #
123
+ # (Native method implemented in Rust)
124
+
125
+ ##
126
+ # :method: select_first_column
127
+ # :call-seq: select_first_column() -> nil
128
+ #
129
+ # Selects column 0.
130
+ #
131
+ # (Native method implemented in Rust)
132
+
133
+ ##
134
+ # :method: select_last_column
135
+ # :call-seq: select_last_column() -> nil
136
+ #
137
+ # Selects the last column. The index is clamped during rendering.
138
+ #
139
+ # (Native method implemented in Rust)
140
+
141
+ ##
142
+ # :method: select_next
143
+ # :call-seq: select_next() -> nil
144
+ #
145
+ # Moves selection to the next row. Selects first row if nothing selected.
146
+ #
147
+ # === Optimistic Indexing
148
+ #
149
+ # Increments the index immediately, even past table bounds. The renderer
150
+ # clamps to valid range on draw. Reading <tt>selected</tt> between this
151
+ # call and render may return an out-of-bounds value.
152
+ #
153
+ # To detect actual selection changes, check bounds first:
154
+ #
155
+ #--
156
+ # SPDX-SnippetBegin
157
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
158
+ # SPDX-License-Identifier: MIT-0
159
+ #++
160
+ # max_index = rows.size - 1
161
+ # return if (state.selected || 0) >= max_index
162
+ # state.select_next
163
+ #
164
+ #--
165
+ # SPDX-SnippetEnd
166
+ #++
167
+ # (Native method implemented in Rust)
168
+
169
+ ##
170
+ # :method: select_previous
171
+ # :call-seq: select_previous() -> nil
172
+ #
173
+ # Moves selection to the previous row. Selects last row if nothing selected.
174
+ #
175
+ # === Optimistic Indexing
176
+ #
177
+ # At index 0, does nothing. With no selection, sets index to maximum value;
178
+ # the renderer clamps to actual last row on draw.
179
+ #
180
+ # To detect actual selection changes, check bounds first:
181
+ #
182
+ #--
183
+ # SPDX-SnippetBegin
184
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
185
+ # SPDX-License-Identifier: MIT-0
186
+ #++
187
+ # return if (state.selected || 0) <= 0
188
+ # state.select_previous
189
+ #
190
+ #--
191
+ # SPDX-SnippetEnd
192
+ #++
193
+ # (Native method implemented in Rust)
194
+
195
+ ##
196
+ # :method: select_first
197
+ # :call-seq: select_first() -> nil
198
+ #
199
+ # Jumps selection to the first row (index 0).
200
+ #
201
+ # To detect actual selection changes:
202
+ #
203
+ #--
204
+ # SPDX-SnippetBegin
205
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
206
+ # SPDX-License-Identifier: MIT-0
207
+ #++
208
+ # return if (state.selected || 0) == 0
209
+ # state.select_first
210
+ #
211
+ #--
212
+ # SPDX-SnippetEnd
213
+ #++
214
+ # (Native method implemented in Rust)
215
+
216
+ ##
217
+ # :method: select_last
218
+ # :call-seq: select_last() -> nil
219
+ #
220
+ # Jumps selection to the last row.
221
+ #
222
+ # === Optimistic Indexing
223
+ #
224
+ # Sets index to maximum possible value. The renderer clamps to actual last
225
+ # row on draw. To get or check the real last index, track row count:
226
+ #
227
+ #--
228
+ # SPDX-SnippetBegin
229
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
230
+ # SPDX-License-Identifier: MIT-0
231
+ #++
232
+ # max_index = rows.size - 1
233
+ # return if (state.selected || 0) == max_index
234
+ # state.select(max_index)
235
+ #
236
+ #--
237
+ # SPDX-SnippetEnd
238
+ #++
239
+ # (Native method implemented in Rust)
240
+
241
+ ##
242
+ # :singleton-method: with_selected_cell
243
+ # :call-seq: with_selected_cell(cell) -> TableState
244
+ #
245
+ # Creates a new TableState with both row and column selected.
246
+ #
247
+ # [cell] <tt>[row, column]</tt> array, or +nil+.
248
+ #
249
+ # (Native method implemented in Rust)
250
+ end
251
+ end
@@ -0,0 +1,316 @@
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
+ require "timeout"
9
+
10
+ module RatatuiRuby
11
+ class Terminal
12
+ # Environment-based terminal capability detection.
13
+ #
14
+ # TUI applications need to know what the terminal supports. Color depth
15
+ # varies. Some terminals lack escape sequence support entirely. Users
16
+ # set environment variables like <tt>NO_COLOR</tt> to express preferences.
17
+ #
18
+ # This module detects terminal capabilities from environment variables.
19
+ # It checks <tt>TERM</tt>, <tt>COLORTERM</tt>, <tt>NO_COLOR</tt>, and
20
+ # <tt>FORCE_COLOR</tt> to determine what the terminal supports.
21
+ #
22
+ # Use these methods before initializing a Terminal instance to decide
23
+ # whether TUI mode is appropriate for the current environment.
24
+ #
25
+ # === Example
26
+ #
27
+ #--
28
+ # SPDX-SnippetBegin
29
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
30
+ # SPDX-License-Identifier: MIT-0
31
+ #++
32
+ # if RatatuiRuby::Terminal.interactive?
33
+ # RatatuiRuby.run { |tui| ... }
34
+ # else
35
+ # puts "TUI mode not available"
36
+ # end
37
+ #--
38
+ # SPDX-SnippetEnd
39
+ #++
40
+ module Capabilities
41
+ # Checks if stdout connects to a terminal device.
42
+ #
43
+ # Terminal apps render escape sequences. Piped output or log files
44
+ # cannot interpret them. If your app writes ANSI codes to a non-TTY,
45
+ # logs fill with garbage like <tt>[32m</tt> instead of green text.
46
+ #
47
+ # This method checks <tt>$stdout.tty?</tt>. Use it to skip TUI mode
48
+ # when output is redirected. Print plain text instead.
49
+ #
50
+ # === Example
51
+ #
52
+ #--
53
+ # SPDX-SnippetBegin
54
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
55
+ # SPDX-License-Identifier: MIT-0
56
+ #++
57
+ # if RatatuiRuby::Terminal.tty?
58
+ # start_tui
59
+ # else
60
+ # print_plain_output
61
+ # end
62
+ #--
63
+ # SPDX-SnippetEnd
64
+ #++
65
+ def tty?
66
+ $stdout.tty?
67
+ end
68
+
69
+ # Checks if the terminal declared itself "dumb."
70
+ #
71
+ # Dumb terminals exist. Emacs shell-mode sets <tt>TERM=dumb</tt>.
72
+ # Serial consoles do too. These terminals cannot interpret escape
73
+ # sequences. If your app sends cursor movements or colors, output
74
+ # becomes unreadable.
75
+ #
76
+ # This method checks for explicit <tt>TERM=dumb</tt>. Empty or unset
77
+ # <tt>TERM</tt> means "unknown," not "dumb." Use it to fall back to
78
+ # plain text rendering.
79
+ #
80
+ # === Example
81
+ #
82
+ #--
83
+ # SPDX-SnippetBegin
84
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
85
+ # SPDX-License-Identifier: MIT-0
86
+ #++
87
+ # if RatatuiRuby::Terminal.dumb?
88
+ # render_plain_table(data)
89
+ # else
90
+ # render_styled_table(data)
91
+ # end
92
+ #--
93
+ # SPDX-SnippetEnd
94
+ #++
95
+ def dumb?
96
+ ENV["TERM"] == "dumb"
97
+ end
98
+
99
+ # Checks if the user disabled color output.
100
+ #
101
+ # Users with visual impairments or screen readers often disable
102
+ # colors. The NO_COLOR standard (no-color.org) provides a universal
103
+ # way to request this. Ignoring it frustrates accessibility-conscious
104
+ # users.
105
+ #
106
+ # This method checks for <tt>NO_COLOR</tt> in the environment. The
107
+ # value does not matter; presence alone disables color. Respect it.
108
+ #
109
+ # === Example
110
+ #
111
+ #--
112
+ # SPDX-SnippetBegin
113
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
114
+ # SPDX-License-Identifier: MIT-0
115
+ #++
116
+ # style = RatatuiRuby::Terminal.no_color? ? :plain : :colored
117
+ #--
118
+ # SPDX-SnippetEnd
119
+ #++
120
+ def no_color?
121
+ ENV.key?("NO_COLOR")
122
+ end
123
+
124
+ # Checks if color output is explicitly forced.
125
+ #
126
+ # Some CI systems and logging pipelines detect non-TTY and strip
127
+ # colors. Users want colors anyway for readability. <tt>FORCE_COLOR</tt>
128
+ # overrides the TTY check.
129
+ #
130
+ # This method checks for <tt>FORCE_COLOR</tt> in the environment.
131
+ # When set, your app should emit colors even when <tt>tty?</tt>
132
+ # returns false.
133
+ #
134
+ # === Example
135
+ #
136
+ #--
137
+ # SPDX-SnippetBegin
138
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
139
+ # SPDX-License-Identifier: MIT-0
140
+ #++
141
+ # use_color = RatatuiRuby::Terminal.tty? ||
142
+ # RatatuiRuby::Terminal.force_color?
143
+ #--
144
+ # SPDX-SnippetEnd
145
+ #++
146
+ def force_color?
147
+ ENV.key?("FORCE_COLOR")
148
+ end
149
+
150
+ # Checks if the terminal supports interactive TUI mode.
151
+ #
152
+ # A TUI needs a real terminal. Piped output breaks cursor control.
153
+ # Dumb terminals corrupt escape sequences. Starting TUI mode in
154
+ # these environments wastes resources and confuses users.
155
+ #
156
+ # This method combines <tt>tty?</tt> and <tt>dumb?</tt> checks.
157
+ # Returns +true+ only when both conditions allow TUI operation.
158
+ # Use it as the gatekeeper before calling <tt>run</tt>.
159
+ #
160
+ # === Example
161
+ #
162
+ #--
163
+ # SPDX-SnippetBegin
164
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
165
+ # SPDX-License-Identifier: MIT-0
166
+ #++
167
+ # if RatatuiRuby::Terminal.interactive?
168
+ # RatatuiRuby.run { |tui| main_loop(tui) }
169
+ # else
170
+ # abort "Interactive terminal required"
171
+ # end
172
+ #--
173
+ # SPDX-SnippetEnd
174
+ #++
175
+ def interactive?
176
+ tty? && !dumb?
177
+ end
178
+
179
+ # Queries how many colors the terminal can display.
180
+ #
181
+ # Modern terminals vary wildly in capability. Some only support 8 ANSI
182
+ # colors. Others display 256. High-end terminals render 16 million
183
+ # truecolor shades. If your app uses rich color palettes without
184
+ # checking, users on basic terminals see garbled output or crashes.
185
+ #
186
+ # This method queries crossterm (which checks <tt>COLORTERM</tt> and
187
+ # <tt>TERM</tt>) and returns the raw count. Use it to select color
188
+ # palettes or degrade gracefully.
189
+ #
190
+ # Returns 8, 256, or 65535 (truecolor).
191
+ #
192
+ # === Example
193
+ #
194
+ #--
195
+ # SPDX-SnippetBegin
196
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
197
+ # SPDX-License-Identifier: MIT-0
198
+ #++
199
+ # colors = RatatuiRuby::Terminal.available_color_count
200
+ # palette = colors >= 256 ? :rich : :basic
201
+ #--
202
+ # SPDX-SnippetEnd
203
+ #++
204
+ def available_color_count
205
+ _available_color_count
206
+ end
207
+
208
+ # Returns the terminal's color capability as a symbol.
209
+ #
210
+ # Comparing integers is annoying. You want to know: can I use
211
+ # gradients? Do I need a fallback palette? This method translates
212
+ # the raw count into semantic symbols.
213
+ #
214
+ # Returns <tt>:none</tt> when <tt>NO_COLOR</tt> is set or terminal is
215
+ # dumb. Returns <tt>:basic</tt> (8 colors), <tt>:ansi256</tt> (256),
216
+ # or <tt>:truecolor</tt> (16M) based on capability.
217
+ #
218
+ # Use it to switch rendering strategies or skip color entirely.
219
+ #
220
+ # === Example
221
+ #
222
+ #--
223
+ # SPDX-SnippetBegin
224
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
225
+ # SPDX-License-Identifier: MIT-0
226
+ #++
227
+ # case RatatuiRuby::Terminal.color_support
228
+ # when :truecolor then use_gradient_theme
229
+ # when :ansi256 then use_256_palette
230
+ # when :basic then use_ansi_colors
231
+ # when :none then use_monochrome
232
+ # end
233
+ #--
234
+ # SPDX-SnippetEnd
235
+ #++
236
+ def color_support
237
+ return :none if no_color?
238
+ return :none if dumb?
239
+
240
+ count = available_color_count
241
+ return :truecolor if count >= 65_535
242
+ return :ansi256 if count >= 256
243
+
244
+ :basic
245
+ end
246
+
247
+ # Checks for Kitty keyboard protocol support.
248
+ #
249
+ # Standard terminal input is ambiguous. Escape key and arrow keys
250
+ # share prefixes. Modifier keys get lost. Applications that need
251
+ # precise key handling (editors, games) struggle with the limitations.
252
+ #
253
+ # The Kitty keyboard protocol solves this. Terminals that support it
254
+ # report key presses unambiguously, with full modifier information.
255
+ # This method queries support so you can enable enhanced input or
256
+ # fall back gracefully.
257
+ #
258
+ # Returns <tt>false</tt> immediately if <tt>tty?</tt> returns false.
259
+ # Otherwise queries crossterm with a 0.5s timeout.
260
+ # Returns <tt>true</tt> only if the terminal responds affirmatively.
261
+ #
262
+ # === Example
263
+ #
264
+ #--
265
+ # SPDX-SnippetBegin
266
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
267
+ # SPDX-License-Identifier: MIT-0
268
+ #++
269
+ # if RatatuiRuby::Terminal.supports_keyboard_enhancement?
270
+ # enable_vim_style_keybindings
271
+ # else
272
+ # use_simple_navigation
273
+ # end
274
+ #--
275
+ # SPDX-SnippetEnd
276
+ #++
277
+ def supports_keyboard_enhancement?
278
+ return false unless tty?
279
+
280
+ Timeout.timeout(0.5) { _supports_keyboard_enhancement }
281
+ rescue
282
+ false
283
+ end
284
+
285
+ # Globally override NO_COLOR detection.
286
+ #
287
+ # The NO_COLOR environment variable tells applications to disable color.
288
+ # Sometimes users want colors anyway, like in CI pipelines that log to
289
+ # files but display logs with color. Passing +true+ forces color output
290
+ # regardless of NO_COLOR.
291
+ #
292
+ # This method calls crossterm's global override. The effect is immediate
293
+ # and affects all subsequent color detection queries. Pass +false+ to
294
+ # restore normal NO_COLOR behavior.
295
+ #
296
+ # === Example
297
+ #
298
+ #--
299
+ # SPDX-SnippetBegin
300
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
301
+ # SPDX-License-Identifier: MIT-0
302
+ #++
303
+ # if options[:color] == :always
304
+ # RatatuiRuby::Terminal.force_color_output(true)
305
+ # end
306
+ #--
307
+ # SPDX-SnippetEnd
308
+ #++
309
+ def force_color_output(enable)
310
+ _force_color_output(enable)
311
+ end
312
+ end
313
+
314
+ extend Capabilities
315
+ end
316
+ end
@@ -0,0 +1,80 @@
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 configuration and viewport settings.
11
+ #
12
+ # Your app needs to choose how it occupies the terminal.
13
+ # Fullscreen apps take over the whole screen and clear on exit.
14
+ # Inline apps run in a fixed region and persist in scrollback.
15
+ # Configuring this manually is error-prone.
16
+ #
17
+ # This module handles the choice. It defines viewport modes and their parameters.
18
+ #
19
+ # @see Terminal::Viewport
20
+ class Terminal
21
+ ##
22
+ # Viewport configuration for terminal initialization.
23
+ #
24
+ # Determines how RatatuiRuby interacts with the terminal:
25
+ # - **Fullscreen**: Uses alternate screen, clears on exit (default)
26
+ # - **Inline**: Fixed-height region, persists in scrollback after exit
27
+ #
28
+ # === Example
29
+ #
30
+ #--
31
+ # SPDX-SnippetBegin
32
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
33
+ # SPDX-License-Identifier: MIT-0
34
+ #++
35
+ # # Fullscreen (default behavior)
36
+ # RatatuiRuby.run { |tui| ... }
37
+ #
38
+ # # Inline with 8 lines
39
+ # RatatuiRuby.run(viewport: :inline, height: 8) { |tui| ... }
40
+ #--
41
+ # SPDX-SnippetEnd
42
+ #++
43
+ class Viewport < Data.define(:type, :height)
44
+ ##
45
+ # Creates a fullscreen viewport (alternate screen).
46
+ def self.fullscreen
47
+ new(type: :fullscreen)
48
+ end
49
+
50
+ ##
51
+ # Creates an inline viewport with the given height.
52
+ def self.inline(height)
53
+ new(type: :inline, height:)
54
+ end
55
+
56
+ ##
57
+ # Creates a new viewport configuration.
58
+ #
59
+ # [type] Symbol representing viewport type (:fullscreen or :inline).
60
+ # [height] Integer height in lines (required for :inline, ignored for :fullscreen).
61
+ #
62
+ # Most developers use {.fullscreen} or {.inline} factory methods instead.
63
+ def initialize(type:, height: nil)
64
+ super
65
+ end
66
+
67
+ ##
68
+ # Returns true if this is a fullscreen viewport.
69
+ def fullscreen?
70
+ type == :fullscreen
71
+ end
72
+
73
+ ##
74
+ # Returns true if this is an inline viewport.
75
+ def inline?
76
+ type == :inline
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,66 @@
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
+ require_relative "terminal/capabilities"
9
+
10
+ module RatatuiRuby
11
+ # Terminal object for managing terminal lifecycle and rendering.
12
+ #
13
+ # Instance-based API aligned with upstream Ratatui Terminal struct.
14
+ #
15
+ # === Example
16
+ #
17
+ #--
18
+ # SPDX-SnippetBegin
19
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
20
+ # SPDX-License-Identifier: MIT-0
21
+ #++
22
+ # terminal = RatatuiRuby::Terminal.new
23
+ # terminal.draw { |frame| ... }
24
+ # terminal.restore
25
+ #--
26
+ # SPDX-SnippetEnd
27
+ #++
28
+ class Terminal
29
+ ##
30
+ # :attr_reader: terminal_id
31
+ # Unique identifier for this terminal instance in Rust (Integer).
32
+ attr_reader :terminal_id
33
+
34
+ # Creates a new Terminal instance.
35
+ #
36
+ # [viewport] Symbol or Viewport object (:fullscreen or :inline)
37
+ # [height] Integer height for inline viewports
38
+ def initialize(viewport: :fullscreen, height: nil)
39
+ @viewport = resolve_viewport(viewport, height)
40
+
41
+ # Call Rust FFI to create instance and get ID
42
+ # For now, only test backend is supported (real crossterm coming later)
43
+ @terminal_id = self.class._init_test_terminal_instance(
44
+ 80, # default width for test
45
+ 24, # default height for test
46
+ @viewport.type.to_s,
47
+ @viewport.height
48
+ )
49
+ end
50
+
51
+ # Returns the terminal size as a Layout::Rect
52
+ # Rust constructs the Rect object directly (not a hash!)
53
+ def size
54
+ self.class._get_terminal_size_instance(@terminal_id)
55
+ end
56
+
57
+ private def resolve_viewport(viewport, height)
58
+ case viewport
59
+ when nil, :fullscreen then Terminal::Viewport.fullscreen
60
+ when :inline then Terminal::Viewport.inline(height || 8)
61
+ when Terminal::Viewport then viewport
62
+ else raise ArgumentError, "Unknown viewport: #{viewport.inspect}"
63
+ end
64
+ end
65
+ end
66
+ end