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,208 @@
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
+ # Represents a single cell in the terminal buffer.
10
+ #
11
+ # A terminal grid is made of cells. Each cell contains a character (symbol) and styling (colors, modifiers).
12
+ # When testing, you often need to verify that a specific cell renders correctly.
13
+ #
14
+ # This object encapsulates that state. It provides predicate methods for modifiers, making assertions readable.
15
+ #
16
+ # Use it to inspect the visual state of your application in tests.
17
+ #
18
+ # === Examples
19
+ #
20
+ #--
21
+ # SPDX-SnippetBegin
22
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
23
+ # SPDX-License-Identifier: MIT-0
24
+ #++
25
+ # cell = RatatuiRuby.get_cell_at(0, 0)
26
+ # cell.char # => "H"
27
+ # cell.fg # => :red
28
+ # cell.bold? # => true
29
+ #
30
+ #--
31
+ # SPDX-SnippetEnd
32
+ #++
33
+ class Cell
34
+ # The character displayed in the cell.
35
+ #
36
+ # Named to match Ratatui's Cell::symbol() method.
37
+ attr_reader :symbol
38
+
39
+ # Alias for Rubyists who prefer a shorter name.
40
+ alias char symbol
41
+
42
+ # The foreground color of the cell (e.g., :red, :blue, "#ff0000").
43
+ attr_reader :fg
44
+
45
+ # The background color of the cell (e.g., :black, nil).
46
+ attr_reader :bg
47
+
48
+ # The underline color of the cell.
49
+ #
50
+ # Distinct from foreground color. Some terminals support colored underlines.
51
+ attr_reader :underline_color
52
+
53
+ # The list of active modifiers (e.g., ["bold", "italic"]).
54
+
55
+ # Returns an empty cell (space character, no styles).
56
+ #
57
+ # === Example
58
+ #
59
+ #--
60
+ # SPDX-SnippetBegin
61
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
62
+ # SPDX-License-Identifier: MIT-0
63
+ #++
64
+ # Cell.empty # => #<RatatuiRuby::Cell char=" ">
65
+ #
66
+ #--
67
+ # SPDX-SnippetEnd
68
+ #++
69
+ def self.empty
70
+ new(symbol: " ", fg: nil, bg: nil, underline_color: nil, modifiers: [])
71
+ end
72
+
73
+ # Returns a default cell (alias for empty).
74
+ #
75
+ # === Example
76
+ #
77
+ #--
78
+ # SPDX-SnippetBegin
79
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
80
+ # SPDX-License-Identifier: MIT-0
81
+ #++
82
+ # Cell.default # => #<RatatuiRuby::Cell char=" ">
83
+ #
84
+ #--
85
+ # SPDX-SnippetEnd
86
+ #++
87
+ def self.default
88
+ empty
89
+ end
90
+
91
+ # Returns a cell with a specific character and no styles.
92
+ #
93
+ # [symbol] String (single character).
94
+ #
95
+ # === Example
96
+ #
97
+ #--
98
+ # SPDX-SnippetBegin
99
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
100
+ # SPDX-License-Identifier: MIT-0
101
+ #++
102
+ # Cell.symbol("X") # => #<RatatuiRuby::Cell symbol="X">
103
+ #
104
+ #--
105
+ # SPDX-SnippetEnd
106
+ #++
107
+ def self.symbol(symbol)
108
+ new(symbol:, fg: nil, bg: nil, underline_color: nil, modifiers: [])
109
+ end
110
+
111
+ # Alias for Rubyists who prefer a shorter name.
112
+ def self.char(char)
113
+ symbol(char)
114
+ end
115
+
116
+ # Creates a new Cell.
117
+ #
118
+ # [symbol] String (single character). Aliased as <tt>char:</tt>.
119
+ # [fg] Symbol or String (nullable).
120
+ # [bg] Symbol or String (nullable).
121
+ # [underline_color] Symbol or String (nullable).
122
+ # [modifiers] Array of Strings.
123
+ def initialize(symbol: nil, char: nil, fg: nil, bg: nil, underline_color: nil, modifiers: [])
124
+ @symbol = (symbol || char || " ").freeze
125
+ @fg = fg&.freeze
126
+ @bg = bg&.freeze
127
+ @underline_color = underline_color&.freeze
128
+ @modifiers = modifiers.map(&:freeze).freeze
129
+ freeze
130
+ end
131
+
132
+ # Returns true if the cell has the bold modifier.
133
+ def bold?
134
+ modifiers.include?("bold")
135
+ end
136
+
137
+ # Returns true if the cell has the dim modifier.
138
+ def dim?
139
+ modifiers.include?("dim")
140
+ end
141
+
142
+ # Returns true if the cell has the italic modifier.
143
+ def italic?
144
+ modifiers.include?("italic")
145
+ end
146
+
147
+ # Returns true if the cell has the underlined modifier.
148
+ def underlined?
149
+ modifiers.include?("underlined")
150
+ end
151
+
152
+ # Returns true if the cell has the slow_blink modifier.
153
+ def slow_blink?
154
+ modifiers.include?("slow_blink")
155
+ end
156
+
157
+ # Returns true if the cell has the rapid_blink modifier.
158
+ def rapid_blink?
159
+ modifiers.include?("rapid_blink")
160
+ end
161
+
162
+ # Returns true if the cell has the reversed modifier.
163
+ def reversed?
164
+ modifiers.include?("reversed")
165
+ end
166
+
167
+ # Returns true if the cell has the hidden modifier.
168
+ def hidden?
169
+ modifiers.include?("hidden")
170
+ end
171
+
172
+ # Returns true if the cell has the crossed_out modifier.
173
+ def crossed_out?
174
+ modifiers.include?("crossed_out")
175
+ end
176
+
177
+ # Checks equality with another Cell.
178
+ def ==(other)
179
+ other.is_a?(Cell) &&
180
+ char == other.char &&
181
+ fg == other.fg &&
182
+ bg == other.bg &&
183
+ underline_color == other.underline_color &&
184
+ modifiers == other.modifiers
185
+ end
186
+
187
+ # Returns a string representation of the cell.
188
+ def inspect
189
+ parts = ["symbol=#{symbol.inspect}"]
190
+ parts << "fg=#{fg.inspect}" if fg
191
+ parts << "bg=#{bg.inspect}" if bg
192
+ parts << "underline_color=#{underline_color.inspect}" if underline_color
193
+ parts << "modifiers=#{modifiers.inspect}" unless modifiers.empty?
194
+ "#<#{self.class} #{parts.join(' ')}>"
195
+ end
196
+
197
+ # Returns the cell's character.
198
+ def to_s
199
+ symbol
200
+ end
201
+
202
+ # Support for pattern matching.
203
+ # Supports both <tt>:symbol</tt> and <tt>:char</tt> keys.
204
+ def deconstruct_keys(keys)
205
+ { symbol:, char: symbol, fg:, bg:, underline_color:, modifiers: }
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,215 @@
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
+ # Debug mode control for RatatuiRuby.
11
+ #
12
+ # TUI applications are hard to debug. Rust panics show cryptic stack traces.
13
+ # Ruby exceptions lack Rust context.
14
+ #
15
+ # This module controls debug visibility. Enable Rust backtraces only, or
16
+ # enable full debug mode for both Rust and Ruby-side features.
17
+ #
18
+ # == Activation Methods
19
+ #
20
+ # Three ways to enable debug features:
21
+ #
22
+ # [<tt>RUST_BACKTRACE=1</tt>] Rust backtraces only (no Ruby-side debug).
23
+ # [<tt>RR_DEBUG=1</tt>] Full debug mode (backtraces + Ruby features).
24
+ # [<tt>include RatatuiRuby::TestHelper</tt>] Auto-enables debug mode.
25
+ #
26
+ # === Example
27
+ #
28
+ #--
29
+ # SPDX-SnippetBegin
30
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
31
+ # SPDX-License-Identifier: MIT-0
32
+ #++
33
+ # # Programmatic activation
34
+ # RatatuiRuby::Debug.enable!
35
+ #
36
+ # # Or use the convenience alias
37
+ # RatatuiRuby.debug_mode!
38
+ #
39
+ #--
40
+ # SPDX-SnippetEnd
41
+ #++
42
+ module Debug
43
+ @rust_backtrace_enabled = false
44
+ @debug_mode_enabled = false
45
+
46
+ class << self
47
+ ##
48
+ # Enables Rust backtraces only.
49
+ #
50
+ # Call this to get meaningful stack traces when Rust panics.
51
+ # Does not enable Ruby-side debug features.
52
+ #
53
+ # Safe to call multiple times; subsequent calls are no-ops.
54
+ def enable_rust_backtrace!
55
+ return if @rust_backtrace_enabled
56
+
57
+ @rust_backtrace_enabled = true
58
+ RatatuiRuby.__send__(:_enable_rust_backtrace)
59
+ end
60
+
61
+ ##
62
+ # Enables full debug mode.
63
+ #
64
+ # Activates Rust backtraces plus any Ruby-side debug features.
65
+ # Optionally enables remote debugging via the debug gem.
66
+ #
67
+ # Safe to call multiple times; subsequent calls are no-ops.
68
+ #
69
+ # [source] <tt>:env</tt> if called from RR_DEBUG env var,
70
+ #--
71
+ # SPDX-SnippetBegin
72
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
73
+ # SPDX-License-Identifier: MIT-0
74
+ #++
75
+ # <tt>:test</tt> from TestHelper (skips remote debugging),
76
+ # <tt>:programmatic</tt> otherwise.
77
+ #--
78
+ # SPDX-SnippetEnd
79
+ #++
80
+ def enable!(source: :programmatic)
81
+ return @socket_path if @debug_mode_enabled
82
+
83
+ @debug_mode_enabled = true
84
+ enable_rust_backtrace!
85
+
86
+ # Tests don't need remote debugging — it would cause hangs
87
+ return if source == :test
88
+
89
+ @remote_debugging_mode = (source == :env) ? :open : :open_nonstop
90
+ @socket_path = enable_remote_debugging!
91
+ end
92
+
93
+ # rubocop:disable Lint/Debugger -- intentional debug gem integration
94
+ private def enable_remote_debugging!
95
+ # Suppress the "Debugger can attach via..." message that corrupts TUI displays
96
+ # Only suppress for programmatic activation; RR_DEBUG=1 users need to see it
97
+ old_log_level = ENV["RUBY_DEBUG_LOG_LEVEL"]
98
+ ENV["RUBY_DEBUG_LOG_LEVEL"] = "ERROR" if @remote_debugging_mode == :open_nonstop
99
+
100
+ case @remote_debugging_mode
101
+ when :open
102
+ # Stop at load so user can read socket path before TUI enters raw mode
103
+ ENV["RUBY_DEBUG_STOP_AT_LOAD"] = "1"
104
+ require "debug/open"
105
+ when :open_nonstop
106
+ require "debug/open_nonstop"
107
+ end
108
+
109
+ # Restore log level after require (the require is what prints the message)
110
+ ENV["RUBY_DEBUG_LOG_LEVEL"] = old_log_level if @remote_debugging_mode == :open_nonstop
111
+
112
+ # Return the socket path so apps can display it
113
+ ::DEBUGGER__.create_unix_domain_socket_name
114
+ rescue NameError
115
+ # Windows uses TCP/IP, not Unix sockets — DEBUGGER__ might not have this method
116
+ nil
117
+ # rubocop:enable Lint/Debugger
118
+ rescue LoadError
119
+ return unless @remote_debugging_mode == :open
120
+
121
+ raise LoadError,
122
+ "RR_DEBUG=1 requires the 'debug' gem for remote debugging. " \
123
+ "Add `gem 'debug'` to your Gemfile or install it with `gem install debug`."
124
+ end
125
+
126
+ ##
127
+ # Returns whether full debug mode is enabled.
128
+ public def enabled?
129
+ @debug_mode_enabled
130
+ end
131
+
132
+ ##
133
+ # Returns whether Rust backtraces are enabled.
134
+ public def rust_backtrace_enabled?
135
+ @rust_backtrace_enabled
136
+ end
137
+
138
+ ##
139
+ # Returns the remote debugging mode for debug gem integration.
140
+ #
141
+ # TUI apps run in raw terminal mode, making interactive debugging
142
+ # impossible. The debug gem's remote debugging feature lets you
143
+ # attach from another terminal via UNIX socket.
144
+ #
145
+ # Returns one of:
146
+ # <tt>:open</tt> Stop at program start, wait for debugger attach.
147
+ # Activated when <tt>RR_DEBUG=1</tt> is set at startup.
148
+ # <tt>:open_nonstop</tt> Continue running, attach whenever ready.
149
+ # Activated when <tt>enable!</tt> is called programmatically.
150
+ # <tt>nil</tt> No remote debugging configured.
151
+ public def remote_debugging_mode
152
+ @remote_debugging_mode
153
+ end
154
+
155
+ ##
156
+ # Triggers a Rust panic for backtrace verification.
157
+ #
158
+ # Debugging TUI apps is hard. Rust errors lack context. You want to
159
+ # confirm <tt>RUST_BACKTRACE=1</tt> actually shows stack traces before
160
+ # hitting a real bug.
161
+ #
162
+ # This method deliberately panics. The panic hook catches it and prints
163
+ # the Rust backtrace to stderr. If you see stack frames, your setup works.
164
+ #
165
+ # <b>WARNING</b>: Crashes your process. Use only for debugging.
166
+ #
167
+ # === Example
168
+ #
169
+ #--
170
+ # SPDX-SnippetBegin
171
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
172
+ # SPDX-License-Identifier: MIT-0
173
+ #++
174
+ # RUST_BACKTRACE=1 ruby -e 'require "ratatui_ruby"; RatatuiRuby::Debug.test_panic!'
175
+ #--
176
+ # SPDX-SnippetEnd
177
+ #++
178
+ public def test_panic!
179
+ RatatuiRuby.__send__(:_test_panic)
180
+ end
181
+
182
+ ##
183
+ # Temporarily suppresses Ruby-side debug mode checks.
184
+ #
185
+ # Rust backtraces remain enabled if previously activated; only
186
+ # Ruby-side features (like unknown-key errors) are suppressed
187
+ # within the block.
188
+ #
189
+ # === Example
190
+ #
191
+ #--
192
+ # SPDX-SnippetBegin
193
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
194
+ # SPDX-License-Identifier: MIT-0
195
+ #++
196
+ # RatatuiRuby::Debug.suppress_debug_mode do
197
+ # tui.table({ unknown_key: 1 }) # Does not raise
198
+ # end
199
+ #--
200
+ # SPDX-SnippetEnd
201
+ #++
202
+ public def suppress_debug_mode
203
+ old_value = @debug_mode_enabled
204
+ @debug_mode_enabled = false
205
+ yield
206
+ ensure
207
+ @debug_mode_enabled = old_value
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ # Auto-enable based on environment variables
214
+ RatatuiRuby::Debug.enable_rust_backtrace! if ENV["RUST_BACKTRACE"]
215
+ RatatuiRuby::Debug.enable!(source: :env) if ENV["RR_DEBUG"]
@@ -0,0 +1,63 @@
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
+ # Draw commands for custom widgets.
10
+ #
11
+ # Custom widgets return an array of Draw commands instead of writing directly to a buffer.
12
+ # This keeps all pointers safely inside Rust while Ruby works with pure data.
13
+ #
14
+ # === Example
15
+ #
16
+ #--
17
+ # SPDX-SnippetBegin
18
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
19
+ # SPDX-License-Identifier: MIT-0
20
+ #++
21
+ # class MyWidget
22
+ # def render(area)
23
+ # [
24
+ # RatatuiRuby::Draw.string(area.x, area.y, "Hello", {fg: :red}),
25
+ # RatatuiRuby::Draw.cell(area.x + 6, area.y, RatatuiRuby::Cell.char("!"))
26
+ # ]
27
+ # end
28
+ # end
29
+ #--
30
+ # SPDX-SnippetEnd
31
+ #++
32
+ module Draw
33
+ # Command to draw a string at the given coordinates.
34
+ #
35
+ # [x] X coordinate (absolute).
36
+ # [y] Y coordinate (absolute).
37
+ # [string] The text to draw.
38
+ # [style] Style hash or Style object.
39
+ StringCmd = Data.define(:x, :y, :string, :style)
40
+
41
+ # Command to draw a cell at the given coordinates.
42
+ #
43
+ # [x] X coordinate (absolute).
44
+ # [y] Y coordinate (absolute).
45
+ # [cell] The Cell to draw.
46
+ CellCmd = Data.define(:x, :y, :cell)
47
+
48
+ # Creates a string draw command.
49
+ #
50
+ # [x] X coordinate (Integer, duck-typed via +to_int+).
51
+ # [y] Y coordinate (Integer, duck-typed via +to_int+).
52
+ # [string] Text to draw.
53
+ # [style] Optional style (Hash or Style).
54
+ def self.string(x, y, string, style = {}) = StringCmd.new(x: Integer(x), y: Integer(y), string:, style:)
55
+
56
+ # Creates a cell draw command.
57
+ #
58
+ # [x] X coordinate (Integer, duck-typed via +to_int+).
59
+ # [y] Y coordinate (Integer, duck-typed via +to_int+).
60
+ # [cell] Cell to draw.
61
+ def self.cell(x, y, cell) = CellCmd.new(x: Integer(x), y: Integer(y), cell:)
62
+ end
63
+ end
@@ -0,0 +1,125 @@
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
+ class Event
10
+ # Signals that the application is now active.
11
+ #
12
+ # The user interacts with many windows. Your application needs to know when it has their attention.
13
+ #
14
+ # This event confirms visibility. It fires when the terminal window moves to the foreground.
15
+ #
16
+ # Use it to resume paused activities. Restart animations. Refresh data. The user is watching.
17
+ #
18
+ # Only supported by some terminals (e.g. iTerm2, Kitty, newer xterm).
19
+ #
20
+ # === Example
21
+ #
22
+ #--
23
+ # SPDX-SnippetBegin
24
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
25
+ # SPDX-License-Identifier: MIT-0
26
+ #++
27
+ # if event.focus_gained?
28
+ # puts "Focus gained"
29
+ # end
30
+ #--
31
+ # SPDX-SnippetEnd
32
+ #++
33
+ class FocusGained < Event
34
+ # Returns true for FocusGained events.
35
+ #
36
+ #--
37
+ # SPDX-SnippetBegin
38
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
39
+ # SPDX-License-Identifier: MIT-0
40
+ #++
41
+ # event.focus_gained? # => true
42
+ # event.key? # => false
43
+ #--
44
+ # SPDX-SnippetEnd
45
+ #++
46
+ def focus_gained?
47
+ true
48
+ end
49
+
50
+ # Deconstructs the event for pattern matching.
51
+ #
52
+ #--
53
+ # SPDX-SnippetBegin
54
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
55
+ # SPDX-License-Identifier: MIT-0
56
+ #++
57
+ # case event
58
+ # in type: :focus_gained
59
+ # puts "Application gained focus"
60
+ # end
61
+ #--
62
+ # SPDX-SnippetEnd
63
+ #++
64
+ def deconstruct_keys(keys)
65
+ { type: :focus_gained }
66
+ end
67
+
68
+ ##
69
+ # Compares this event with another for equality.
70
+ def ==(other)
71
+ other.is_a?(FocusGained)
72
+ end
73
+
74
+ # =========================================================================
75
+ # DWIM Predicates
76
+ # =========================================================================
77
+
78
+ # Returns true. The terminal window is now in focus.
79
+ #
80
+ # event.focus? # => true
81
+ def focus?
82
+ true
83
+ end
84
+ alias focused? focus?
85
+
86
+ # Returns true. The application gained focus.
87
+ #
88
+ # event.gained? # => true
89
+ def gained?
90
+ true
91
+ end
92
+
93
+ # Returns false. This is not a focus lost event.
94
+ #
95
+ # event.lost? # => false
96
+ def lost?
97
+ false
98
+ end
99
+
100
+ # Returns false. Blur is the opposite of focus gained.
101
+ #
102
+ # event.blur? # => false
103
+ def blur?
104
+ false
105
+ end
106
+ alias blurred? blur?
107
+
108
+ # Returns true. The application is active.
109
+ #
110
+ # event.active? # => true
111
+ def active?
112
+ true
113
+ end
114
+ alias foreground? active?
115
+
116
+ # Returns false. The application is not inactive.
117
+ #
118
+ # event.inactive? # => false
119
+ def inactive?
120
+ false
121
+ end
122
+ alias background? inactive?
123
+ end
124
+ end
125
+ end