ratatui_ruby 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +2 -1
  7. data/CHANGELOG.md +98 -0
  8. data/REUSE.toml +5 -0
  9. data/Rakefile +1 -1
  10. data/Steepfile +49 -0
  11. data/doc/concepts/debugging.md +401 -0
  12. data/doc/getting_started/quickstart.md +8 -3
  13. data/doc/images/app_all_events.png +0 -0
  14. data/doc/images/app_color_picker.png +0 -0
  15. data/doc/images/app_debugging_showcase.gif +0 -0
  16. data/doc/images/app_debugging_showcase.png +0 -0
  17. data/doc/images/app_login_form.png +0 -0
  18. data/doc/images/app_stateful_interaction.png +0 -0
  19. data/doc/images/verify_quickstart_dsl.png +0 -0
  20. data/doc/images/verify_quickstart_layout.png +0 -0
  21. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  22. data/doc/images/verify_readme_usage.png +0 -0
  23. data/doc/images/widget_barchart.png +0 -0
  24. data/doc/images/widget_block.png +0 -0
  25. data/doc/images/widget_box.png +0 -0
  26. data/doc/images/widget_calendar.png +0 -0
  27. data/doc/images/widget_canvas.png +0 -0
  28. data/doc/images/widget_cell.png +0 -0
  29. data/doc/images/widget_center.png +0 -0
  30. data/doc/images/widget_chart.png +0 -0
  31. data/doc/images/widget_gauge.png +0 -0
  32. data/doc/images/widget_layout_split.png +0 -0
  33. data/doc/images/widget_line_gauge.png +0 -0
  34. data/doc/images/widget_list.png +0 -0
  35. data/doc/images/widget_map.png +0 -0
  36. data/doc/images/widget_overlay.png +0 -0
  37. data/doc/images/widget_popup.png +0 -0
  38. data/doc/images/widget_ratatui_logo.png +0 -0
  39. data/doc/images/widget_ratatui_mascot.png +0 -0
  40. data/doc/images/widget_rect.png +0 -0
  41. data/doc/images/widget_render.png +0 -0
  42. data/doc/images/widget_rich_text.png +0 -0
  43. data/doc/images/widget_scroll_text.png +0 -0
  44. data/doc/images/widget_scrollbar.png +0 -0
  45. data/doc/images/widget_sparkline.png +0 -0
  46. data/doc/images/widget_style_colors.png +0 -0
  47. data/doc/images/widget_table.png +0 -0
  48. data/doc/images/widget_tabs.png +0 -0
  49. data/doc/images/widget_text_width.png +0 -0
  50. data/doc/troubleshooting/async.md +4 -0
  51. data/examples/app_debugging_showcase/README.md +119 -0
  52. data/examples/app_debugging_showcase/app.rb +318 -0
  53. data/examples/widget_canvas/app.rb +19 -14
  54. data/examples/widget_gauge/app.rb +18 -3
  55. data/examples/widget_layout_split/app.rb +10 -4
  56. data/examples/widget_list/app.rb +22 -6
  57. data/examples/widget_rect/app.rb +7 -6
  58. data/examples/widget_rich_text/app.rb +62 -37
  59. data/examples/widget_style_colors/app.rb +26 -47
  60. data/examples/widget_table/app.rb +28 -5
  61. data/examples/widget_text_width/app.rb +6 -4
  62. data/ext/ratatui_ruby/Cargo.lock +48 -1
  63. data/ext/ratatui_ruby/Cargo.toml +6 -2
  64. data/ext/ratatui_ruby/src/color.rs +82 -0
  65. data/ext/ratatui_ruby/src/errors.rs +28 -0
  66. data/ext/ratatui_ruby/src/events.rs +15 -14
  67. data/ext/ratatui_ruby/src/lib.rs +56 -0
  68. data/ext/ratatui_ruby/src/rendering.rs +3 -1
  69. data/ext/ratatui_ruby/src/style.rs +48 -21
  70. data/ext/ratatui_ruby/src/terminal.rs +40 -9
  71. data/ext/ratatui_ruby/src/text.rs +21 -9
  72. data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
  73. data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
  74. data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
  75. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  76. data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
  77. data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
  78. data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
  79. data/lib/ratatui_ruby/buffer/cell.rb +25 -15
  80. data/lib/ratatui_ruby/buffer.rb +134 -2
  81. data/lib/ratatui_ruby/cell.rb +13 -5
  82. data/lib/ratatui_ruby/debug.rb +215 -0
  83. data/lib/ratatui_ruby/event/key.rb +3 -2
  84. data/lib/ratatui_ruby/event.rb +1 -1
  85. data/lib/ratatui_ruby/layout/constraint.rb +49 -0
  86. data/lib/ratatui_ruby/layout/layout.rb +119 -13
  87. data/lib/ratatui_ruby/layout/position.rb +55 -0
  88. data/lib/ratatui_ruby/layout/rect.rb +188 -0
  89. data/lib/ratatui_ruby/layout/size.rb +55 -0
  90. data/lib/ratatui_ruby/layout.rb +4 -0
  91. data/lib/ratatui_ruby/style/color.rb +149 -0
  92. data/lib/ratatui_ruby/style/style.rb +51 -4
  93. data/lib/ratatui_ruby/style.rb +2 -0
  94. data/lib/ratatui_ruby/symbols.rb +435 -0
  95. data/lib/ratatui_ruby/synthetic_events.rb +1 -1
  96. data/lib/ratatui_ruby/table_state.rb +51 -0
  97. data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
  98. data/lib/ratatui_ruby/test_helper/event_injection.rb +6 -1
  99. data/lib/ratatui_ruby/test_helper.rb +9 -0
  100. data/lib/ratatui_ruby/text/line.rb +245 -0
  101. data/lib/ratatui_ruby/text/span.rb +158 -0
  102. data/lib/ratatui_ruby/text.rb +99 -0
  103. data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
  104. data/lib/ratatui_ruby/tui/core.rb +13 -2
  105. data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
  106. data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
  107. data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
  108. data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
  109. data/lib/ratatui_ruby/tui.rb +22 -1
  110. data/lib/ratatui_ruby/version.rb +1 -1
  111. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  112. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  113. data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
  114. data/lib/ratatui_ruby/widgets/block.rb +14 -6
  115. data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
  116. data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
  117. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  118. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  119. data/lib/ratatui_ruby/widgets/chart.rb +6 -0
  120. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  121. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  122. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  123. data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
  124. data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
  125. data/lib/ratatui_ruby/widgets/list.rb +87 -3
  126. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  127. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  128. data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
  129. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
  131. data/lib/ratatui_ruby/widgets/row.rb +45 -0
  132. data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
  133. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  134. data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
  135. data/lib/ratatui_ruby/widgets/table.rb +13 -3
  136. data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
  137. data/lib/ratatui_ruby/widgets.rb +1 -0
  138. data/lib/ratatui_ruby.rb +40 -9
  139. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  140. data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
  141. data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
  142. data/sig/examples/app_all_events/view.rbs +1 -1
  143. data/sig/examples/app_stateful_interaction/app.rbs +5 -5
  144. data/sig/examples/widget_block_demo/app.rbs +6 -6
  145. data/sig/manifest.yaml +5 -0
  146. data/sig/patches/data.rbs +26 -0
  147. data/sig/patches/debugger__.rbs +8 -0
  148. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  149. data/sig/ratatui_ruby/buffer.rbs +18 -0
  150. data/sig/ratatui_ruby/cell.rbs +44 -0
  151. data/sig/ratatui_ruby/clear.rbs +18 -0
  152. data/sig/ratatui_ruby/constraint.rbs +26 -0
  153. data/sig/ratatui_ruby/debug.rbs +45 -0
  154. data/sig/ratatui_ruby/draw.rbs +30 -0
  155. data/sig/ratatui_ruby/event.rbs +68 -8
  156. data/sig/ratatui_ruby/frame.rbs +4 -4
  157. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  158. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  159. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  160. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  161. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  162. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  163. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  164. data/sig/ratatui_ruby/ratatui_ruby.rbs +83 -4
  165. data/sig/ratatui_ruby/rect.rbs +17 -0
  166. data/sig/ratatui_ruby/style/color.rbs +22 -0
  167. data/sig/ratatui_ruby/style/style.rbs +29 -0
  168. data/sig/ratatui_ruby/symbols.rbs +141 -0
  169. data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
  170. data/sig/ratatui_ruby/table_state.rbs +6 -0
  171. data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
  172. data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
  173. data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
  174. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
  175. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
  176. data/sig/ratatui_ruby/text/line.rbs +27 -0
  177. data/sig/ratatui_ruby/text/span.rbs +23 -0
  178. data/sig/ratatui_ruby/text.rbs +12 -0
  179. data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
  180. data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
  181. data/sig/ratatui_ruby/tui/core.rbs +2 -2
  182. data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
  183. data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
  184. data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
  185. data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
  186. data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
  187. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  188. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  189. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  190. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  191. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  192. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  193. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  194. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  195. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  196. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  197. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  198. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  199. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  200. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  201. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  202. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  203. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  204. data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
  205. data/tasks/steep.rake +11 -0
  206. metadata +80 -63
  207. data/doc/contributors/v1.0.0_blockers.md +0 -870
  208. data/doc/troubleshooting/debugging.md +0 -101
  209. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
  210. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
  211. data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
  212. data/lib/ratatui_ruby/schema/block.rb +0 -198
  213. data/lib/ratatui_ruby/schema/calendar.rb +0 -84
  214. data/lib/ratatui_ruby/schema/canvas.rb +0 -239
  215. data/lib/ratatui_ruby/schema/center.rb +0 -67
  216. data/lib/ratatui_ruby/schema/chart.rb +0 -159
  217. data/lib/ratatui_ruby/schema/clear.rb +0 -62
  218. data/lib/ratatui_ruby/schema/constraint.rb +0 -151
  219. data/lib/ratatui_ruby/schema/cursor.rb +0 -50
  220. data/lib/ratatui_ruby/schema/gauge.rb +0 -72
  221. data/lib/ratatui_ruby/schema/layout.rb +0 -122
  222. data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
  223. data/lib/ratatui_ruby/schema/list.rb +0 -135
  224. data/lib/ratatui_ruby/schema/list_item.rb +0 -51
  225. data/lib/ratatui_ruby/schema/overlay.rb +0 -51
  226. data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
  227. data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
  228. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
  229. data/lib/ratatui_ruby/schema/rect.rb +0 -174
  230. data/lib/ratatui_ruby/schema/row.rb +0 -76
  231. data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
  232. data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
  233. data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
  234. data/lib/ratatui_ruby/schema/style.rb +0 -97
  235. data/lib/ratatui_ruby/schema/table.rb +0 -141
  236. data/lib/ratatui_ruby/schema/tabs.rb +0 -85
  237. data/lib/ratatui_ruby/schema/text.rb +0 -217
  238. data/sig/examples/app_all_events/model/events.rbs +0 -15
  239. data/sig/examples/app_all_events/view_state.rbs +0 -21
  240. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
  241. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
  242. data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
  243. data/sig/ratatui_ruby/schema/block.rbs +0 -18
  244. data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
  245. data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
  246. data/sig/ratatui_ruby/schema/center.rbs +0 -17
  247. data/sig/ratatui_ruby/schema/chart.rbs +0 -39
  248. data/sig/ratatui_ruby/schema/constraint.rbs +0 -30
  249. data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
  250. data/sig/ratatui_ruby/schema/draw.rbs +0 -33
  251. data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
  252. data/sig/ratatui_ruby/schema/layout.rbs +0 -27
  253. data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
  254. data/sig/ratatui_ruby/schema/list.rbs +0 -28
  255. data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
  256. data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
  257. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
  258. data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
  259. data/sig/ratatui_ruby/schema/rect.rbs +0 -48
  260. data/sig/ratatui_ruby/schema/row.rbs +0 -28
  261. data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
  262. data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
  263. data/sig/ratatui_ruby/schema/style.rbs +0 -19
  264. data/sig/ratatui_ruby/schema/table.rbs +0 -32
  265. data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
  266. data/sig/ratatui_ruby/schema/text.rbs +0 -31
  267. /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
@@ -0,0 +1,435 @@
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
+ # Symbol constants from Ratatui's symbols module.
10
+ #
11
+ # Ratatui provides various character sets for rendering UI elements.
12
+ # Hardcoding these characters is error-prone and makes code less readable.
13
+ #
14
+ # This module exposes those constants for Ruby apps to use directly.
15
+ # Use them for gradients, fills, or custom widget styling.
16
+ module Symbols
17
+ # Shade characters for creating gradient or density effects.
18
+ #
19
+ # Terminal UIs often need to show density levels or create visual gradients.
20
+ # Memorizing Unicode block characters is tedious and error-prone.
21
+ #
22
+ # These constants provide named access to the standard shade characters.
23
+ # Use them to fill areas with varying visual densities.
24
+ #
25
+ # === Examples
26
+ #
27
+ #--
28
+ # SPDX-SnippetBegin
29
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
30
+ # SPDX-License-Identifier: MIT-0
31
+ #++
32
+ # # Create a density gradient
33
+ # gradient = [Shade::EMPTY, Shade::LIGHT, Shade::MEDIUM, Shade::DARK, Shade::FULL]
34
+ #
35
+ # # Use in a progress indicator
36
+ # filled = Shade::FULL * progress
37
+ # empty = Shade::LIGHT * (total - progress)
38
+ #--
39
+ # SPDX-SnippetEnd
40
+ #++
41
+ module Shade
42
+ # Empty space - 0% density.
43
+ EMPTY = " "
44
+
45
+ # Light shading - approximately 25% density.
46
+ LIGHT = "░"
47
+
48
+ # Medium shading - approximately 50% density.
49
+ MEDIUM = "▒"
50
+
51
+ # Dark shading - approximately 75% density.
52
+ DARK = "▓"
53
+
54
+ # Full block - 100% density.
55
+ FULL = "█"
56
+ end
57
+
58
+ # Box-drawing characters for borders and lines.
59
+ #
60
+ # Terminal UIs need consistent box-drawing characters for borders and dividers.
61
+ # Memorizing Unicode box-drawing characters is tedious and error-prone.
62
+ #
63
+ # This module exposes both individual characters and predefined sets.
64
+ # Use the sets with widgets that accept a line_set parameter, or use
65
+ # individual characters for custom drawing.
66
+ #
67
+ # === Examples
68
+ #
69
+ #--
70
+ # SPDX-SnippetBegin
71
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
72
+ # SPDX-License-Identifier: MIT-0
73
+ #++
74
+ # # Use a predefined set for drawing boxes
75
+ # line_set = Symbols::Line::ROUNDED
76
+ #
77
+ # # Draw a simple frame
78
+ # top = "#{line_set[:top_left]}#{line_set[:horizontal] * 10}#{line_set[:top_right]}"
79
+ # mid = "#{line_set[:vertical]}#{' ' * 10}#{line_set[:vertical]}"
80
+ # bot = "#{line_set[:bottom_left]}#{line_set[:horizontal] * 10}#{line_set[:bottom_right]}"
81
+ #
82
+ # # Use individual characters for custom drawing
83
+ # corner = Symbols::Line::ROUNDED_TOP_LEFT # => "╭"
84
+ #--
85
+ # SPDX-SnippetEnd
86
+ #++
87
+ module Line
88
+ # Standard vertical line.
89
+ VERTICAL = "│"
90
+ # Double vertical line.
91
+ DOUBLE_VERTICAL = "║"
92
+ # Thick (heavy) vertical line.
93
+ THICK_VERTICAL = "┃"
94
+
95
+ # Standard horizontal line.
96
+ HORIZONTAL = "─"
97
+ # Double horizontal line.
98
+ DOUBLE_HORIZONTAL = "═"
99
+ # Thick (heavy) horizontal line.
100
+ THICK_HORIZONTAL = "━"
101
+
102
+ # Standard top-right corner.
103
+ TOP_RIGHT = "┐"
104
+ # Rounded top-right corner.
105
+ ROUNDED_TOP_RIGHT = "╮"
106
+ # Double top-right corner.
107
+ DOUBLE_TOP_RIGHT = "╗"
108
+ # Thick top-right corner.
109
+ THICK_TOP_RIGHT = "┓"
110
+
111
+ # Standard top-left corner.
112
+ TOP_LEFT = "┌"
113
+ # Rounded top-left corner.
114
+ ROUNDED_TOP_LEFT = "╭"
115
+ # Double top-left corner.
116
+ DOUBLE_TOP_LEFT = "╔"
117
+ # Thick top-left corner.
118
+ THICK_TOP_LEFT = "┏"
119
+
120
+ # Standard bottom-right corner.
121
+ BOTTOM_RIGHT = "┘"
122
+ # Rounded bottom-right corner.
123
+ ROUNDED_BOTTOM_RIGHT = "╯"
124
+ # Double bottom-right corner.
125
+ DOUBLE_BOTTOM_RIGHT = "╝"
126
+ # Thick bottom-right corner.
127
+ THICK_BOTTOM_RIGHT = "┛"
128
+
129
+ # Standard bottom-left corner.
130
+ BOTTOM_LEFT = "└"
131
+ # Rounded bottom-left corner.
132
+ ROUNDED_BOTTOM_LEFT = "╰"
133
+ # Double bottom-left corner.
134
+ DOUBLE_BOTTOM_LEFT = "╚"
135
+ # Thick bottom-left corner.
136
+ THICK_BOTTOM_LEFT = "┗"
137
+
138
+ # Standard T-junction pointing left.
139
+ VERTICAL_LEFT = "┤"
140
+ # Double T-junction pointing left.
141
+ DOUBLE_VERTICAL_LEFT = "╣"
142
+ # Thick T-junction pointing left.
143
+ THICK_VERTICAL_LEFT = "┫"
144
+
145
+ # Standard T-junction pointing right.
146
+ VERTICAL_RIGHT = "├"
147
+ # Double T-junction pointing right.
148
+ DOUBLE_VERTICAL_RIGHT = "╠"
149
+ # Thick T-junction pointing right.
150
+ THICK_VERTICAL_RIGHT = "┣"
151
+
152
+ # Standard T-junction pointing down.
153
+ HORIZONTAL_DOWN = "┬"
154
+ # Double T-junction pointing down.
155
+ DOUBLE_HORIZONTAL_DOWN = "╦"
156
+ # Thick T-junction pointing down.
157
+ THICK_HORIZONTAL_DOWN = "┳"
158
+
159
+ # Standard T-junction pointing up.
160
+ HORIZONTAL_UP = "┴"
161
+ # Double T-junction pointing up.
162
+ DOUBLE_HORIZONTAL_UP = "╩"
163
+ # Thick T-junction pointing up.
164
+ THICK_HORIZONTAL_UP = "┻"
165
+
166
+ # Standard cross (4-way intersection).
167
+ CROSS = "┼"
168
+ # Double cross (4-way intersection).
169
+ DOUBLE_CROSS = "╬"
170
+ # Thick cross (4-way intersection).
171
+ THICK_CROSS = "╋"
172
+
173
+ # Standard box-drawing set with straight corners.
174
+ NORMAL = {
175
+ vertical: VERTICAL,
176
+ horizontal: HORIZONTAL,
177
+ top_right: TOP_RIGHT,
178
+ top_left: TOP_LEFT,
179
+ bottom_right: BOTTOM_RIGHT,
180
+ bottom_left: BOTTOM_LEFT,
181
+ vertical_left: VERTICAL_LEFT,
182
+ vertical_right: VERTICAL_RIGHT,
183
+ horizontal_down: HORIZONTAL_DOWN,
184
+ horizontal_up: HORIZONTAL_UP,
185
+ cross: CROSS,
186
+ }.freeze
187
+
188
+ # Box-drawing set with rounded corners.
189
+ ROUNDED = {
190
+ vertical: VERTICAL,
191
+ horizontal: HORIZONTAL,
192
+ top_right: ROUNDED_TOP_RIGHT,
193
+ top_left: ROUNDED_TOP_LEFT,
194
+ bottom_right: ROUNDED_BOTTOM_RIGHT,
195
+ bottom_left: ROUNDED_BOTTOM_LEFT,
196
+ vertical_left: VERTICAL_LEFT,
197
+ vertical_right: VERTICAL_RIGHT,
198
+ horizontal_down: HORIZONTAL_DOWN,
199
+ horizontal_up: HORIZONTAL_UP,
200
+ cross: CROSS,
201
+ }.freeze
202
+
203
+ # Double-line box-drawing set.
204
+ DOUBLE = {
205
+ vertical: DOUBLE_VERTICAL,
206
+ horizontal: DOUBLE_HORIZONTAL,
207
+ top_right: DOUBLE_TOP_RIGHT,
208
+ top_left: DOUBLE_TOP_LEFT,
209
+ bottom_right: DOUBLE_BOTTOM_RIGHT,
210
+ bottom_left: DOUBLE_BOTTOM_LEFT,
211
+ vertical_left: DOUBLE_VERTICAL_LEFT,
212
+ vertical_right: DOUBLE_VERTICAL_RIGHT,
213
+ horizontal_down: DOUBLE_HORIZONTAL_DOWN,
214
+ horizontal_up: DOUBLE_HORIZONTAL_UP,
215
+ cross: DOUBLE_CROSS,
216
+ }.freeze
217
+
218
+ # Thick (heavy) box-drawing set.
219
+ THICK = {
220
+ vertical: THICK_VERTICAL,
221
+ horizontal: THICK_HORIZONTAL,
222
+ top_right: THICK_TOP_RIGHT,
223
+ top_left: THICK_TOP_LEFT,
224
+ bottom_right: THICK_BOTTOM_RIGHT,
225
+ bottom_left: THICK_BOTTOM_LEFT,
226
+ vertical_left: THICK_VERTICAL_LEFT,
227
+ vertical_right: THICK_VERTICAL_RIGHT,
228
+ horizontal_down: THICK_HORIZONTAL_DOWN,
229
+ horizontal_up: THICK_HORIZONTAL_UP,
230
+ cross: THICK_CROSS,
231
+ }.freeze
232
+ end
233
+
234
+ # Vertical bar characters for sparklines and bar charts.
235
+ #
236
+ # Sparklines and vertical bar charts need characters that show partial fill.
237
+ # Memorizing Unicode lower block characters is tedious and error-prone.
238
+ #
239
+ # This module exposes both individual characters and predefined sets.
240
+ # Use the sets with Sparkline widget, or use individual characters for
241
+ # custom visualizations.
242
+ #
243
+ # === Examples
244
+ #
245
+ #--
246
+ # SPDX-SnippetBegin
247
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
248
+ # SPDX-License-Identifier: MIT-0
249
+ #++
250
+ # # Use NINE_LEVELS for high-resolution sparklines (default)
251
+ # sparkline = tui.sparkline(data: [1, 2, 3], bar_set: Symbols::Bar::NINE_LEVELS)
252
+ #
253
+ # # Use THREE_LEVELS for simpler rendering
254
+ # sparkline = tui.sparkline(data: [1, 2, 3], bar_set: Symbols::Bar::THREE_LEVELS)
255
+ #--
256
+ # SPDX-SnippetEnd
257
+ #++
258
+ module Bar
259
+ # Full height bar (8/8).
260
+ FULL = "█"
261
+ # 7/8 height bar.
262
+ SEVEN_EIGHTHS = "▇"
263
+ # 3/4 height bar (6/8).
264
+ THREE_QUARTERS = "▆"
265
+ # 5/8 height bar.
266
+ FIVE_EIGHTHS = "▅"
267
+ # Half height bar (4/8).
268
+ HALF = "▄"
269
+ # 3/8 height bar.
270
+ THREE_EIGHTHS = "▃"
271
+ # 1/4 height bar (2/8).
272
+ ONE_QUARTER = "▂"
273
+ # 1/8 height bar.
274
+ ONE_EIGHTH = "▁"
275
+
276
+ # High-resolution bar set with 9 distinct levels.
277
+ NINE_LEVELS = {
278
+ full: FULL,
279
+ seven_eighths: SEVEN_EIGHTHS,
280
+ three_quarters: THREE_QUARTERS,
281
+ five_eighths: FIVE_EIGHTHS,
282
+ half: HALF,
283
+ three_eighths: THREE_EIGHTHS,
284
+ one_quarter: ONE_QUARTER,
285
+ one_eighth: ONE_EIGHTH,
286
+ empty: " ",
287
+ }.freeze
288
+
289
+ # Low-resolution bar set with 3 levels (full, half, empty).
290
+ THREE_LEVELS = {
291
+ full: FULL,
292
+ seven_eighths: FULL, # collapsed to full
293
+ three_quarters: HALF, # collapsed to half
294
+ five_eighths: HALF, # collapsed to half
295
+ half: HALF,
296
+ three_eighths: HALF, # collapsed to half
297
+ one_quarter: HALF, # collapsed to half
298
+ one_eighth: " ", # collapsed to empty
299
+ empty: " ",
300
+ }.freeze
301
+ end
302
+
303
+ # Horizontal block characters for gauges and progress indicators.
304
+ #
305
+ # Progress bars and gauges need characters that show partial fill from left to right.
306
+ # Memorizing Unicode left block characters is tedious and error-prone.
307
+ #
308
+ # This module exposes both individual characters and predefined sets.
309
+ # Use the sets with Gauge widget, or use individual characters for
310
+ # custom progress indicators.
311
+ #
312
+ # Note: Block uses LEFT block characters (fill from left) while Bar uses
313
+ # LOWER block characters (fill from bottom). They look similar but are different.
314
+ #
315
+ # === Examples
316
+ #
317
+ #--
318
+ # SPDX-SnippetBegin
319
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
320
+ # SPDX-License-Identifier: MIT-0
321
+ #++
322
+ # # Use NINE_LEVELS for high-resolution gauges (default)
323
+ # gauge = tui.gauge(percent: 50, block_set: Symbols::Block::NINE_LEVELS)
324
+ #
325
+ # # Use THREE_LEVELS for simpler rendering
326
+ # gauge = tui.gauge(percent: 50, block_set: Symbols::Block::THREE_LEVELS)
327
+ #--
328
+ # SPDX-SnippetEnd
329
+ #++
330
+ module Block
331
+ # Full width block (8/8).
332
+ FULL = "█"
333
+ # 7/8 width block.
334
+ SEVEN_EIGHTHS = "▉"
335
+ # 3/4 width block (6/8).
336
+ THREE_QUARTERS = "▊"
337
+ # 5/8 width block.
338
+ FIVE_EIGHTHS = "▋"
339
+ # Half width block (4/8).
340
+ HALF = "▌"
341
+ # 3/8 width block.
342
+ THREE_EIGHTHS = "▍"
343
+ # 1/4 width block (2/8).
344
+ ONE_QUARTER = "▎"
345
+ # 1/8 width block.
346
+ ONE_EIGHTH = "▏"
347
+
348
+ # High-resolution block set with 9 distinct levels.
349
+ NINE_LEVELS = {
350
+ full: FULL,
351
+ seven_eighths: SEVEN_EIGHTHS,
352
+ three_quarters: THREE_QUARTERS,
353
+ five_eighths: FIVE_EIGHTHS,
354
+ half: HALF,
355
+ three_eighths: THREE_EIGHTHS,
356
+ one_quarter: ONE_QUARTER,
357
+ one_eighth: ONE_EIGHTH,
358
+ empty: " ",
359
+ }.freeze
360
+
361
+ # Low-resolution block set with 3 levels (full, half, empty).
362
+ THREE_LEVELS = {
363
+ full: FULL,
364
+ seven_eighths: FULL, # collapsed to full
365
+ three_quarters: HALF, # collapsed to half
366
+ five_eighths: HALF, # collapsed to half
367
+ half: HALF,
368
+ three_eighths: HALF, # collapsed to half
369
+ one_quarter: HALF, # collapsed to half
370
+ one_eighth: " ", # collapsed to empty
371
+ empty: " ",
372
+ }.freeze
373
+ end
374
+
375
+ # Scrollbar symbol sets for the Scrollbar widget.
376
+ #
377
+ # Scrollbars need consistent visual elements: a track, a thumb, and arrow indicators.
378
+ # Memorizing Unicode scroll characters is tedious and error-prone.
379
+ #
380
+ # This module exposes predefined sets with different visual styles.
381
+ # Use them with the Scrollbar widget to customize its appearance.
382
+ #
383
+ # Note: Uses <tt>begin_char</tt> and <tt>end_char</tt> instead of Rust's
384
+ # <tt>begin</tt>/<tt>end</tt> to avoid Ruby reserved word conflicts.
385
+ #
386
+ # === Examples
387
+ #
388
+ #--
389
+ # SPDX-SnippetBegin
390
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
391
+ # SPDX-License-Identifier: MIT-0
392
+ #++
393
+ # # Use DOUBLE_VERTICAL for bold vertical scrollbars (default)
394
+ # scrollbar = tui.scrollbar(symbols: Symbols::Scrollbar::DOUBLE_VERTICAL)
395
+ #
396
+ # # Use VERTICAL for lighter appearance
397
+ # scrollbar = tui.scrollbar(symbols: Symbols::Scrollbar::VERTICAL)
398
+ #--
399
+ # SPDX-SnippetEnd
400
+ #++
401
+ module Scrollbar
402
+ # Double-line vertical scrollbar with triangle arrows.
403
+ DOUBLE_VERTICAL = {
404
+ track: Line::DOUBLE_VERTICAL,
405
+ thumb: Block::FULL,
406
+ begin_char: "▲",
407
+ end_char: "▼",
408
+ }.freeze
409
+
410
+ # Double-line horizontal scrollbar with triangle arrows.
411
+ DOUBLE_HORIZONTAL = {
412
+ track: Line::DOUBLE_HORIZONTAL,
413
+ thumb: Block::FULL,
414
+ begin_char: "◄",
415
+ end_char: "►",
416
+ }.freeze
417
+
418
+ # Single-line vertical scrollbar with arrow characters.
419
+ VERTICAL = {
420
+ track: Line::VERTICAL,
421
+ thumb: Block::FULL,
422
+ begin_char: "↑",
423
+ end_char: "↓",
424
+ }.freeze
425
+
426
+ # Single-line horizontal scrollbar with arrow characters.
427
+ HORIZONTAL = {
428
+ track: Line::HORIZONTAL,
429
+ thumb: Block::FULL,
430
+ begin_char: "←",
431
+ end_char: "→",
432
+ }.freeze
433
+ end
434
+ end
435
+ end
@@ -46,7 +46,7 @@ module RatatuiRuby
46
46
  # SPDX-SnippetEnd
47
47
  #++
48
48
  module SyntheticEvents
49
- @queue = []
49
+ @queue = [] #: Array[Event]
50
50
  @mutex = Mutex.new
51
51
 
52
52
  class << self
@@ -96,5 +96,56 @@ module RatatuiRuby
96
96
  # Scrolls up by +n+ rows.
97
97
  #
98
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
+ # :singleton-method: with_selected_cell
143
+ # :call-seq: with_selected_cell(cell) -> TableState
144
+ #
145
+ # Creates a new TableState with both row and column selected.
146
+ #
147
+ # [cell] <tt>[row, column]</tt> array, or +nil+.
148
+ #
149
+ # (Native method implemented in Rust)
99
150
  end
100
151
  end
@@ -90,7 +90,7 @@ module RatatuiRuby
90
90
  ##
91
91
  # Restores the terminal to its original state.
92
92
  # Leaves alternate screen and disables raw mode.
93
- # Also flushes any deferred warnings that were queued during the session.
93
+ # Also flushes any deferred warnings and panic info that were queued during the session.
94
94
  #
95
95
  # In headless mode ({headless!}), this method is a silent no-op since
96
96
  # no terminal was ever initialized.
@@ -103,6 +103,7 @@ module RatatuiRuby
103
103
  ensure
104
104
  @tui_session_active = false
105
105
  flush_warnings
106
+ flush_panic_info
106
107
  end
107
108
 
108
109
  ##
@@ -64,7 +64,7 @@ module RatatuiRuby
64
64
  # [event] A <tt>RatatuiRuby::Event</tt> object.
65
65
  def inject_event(event)
66
66
  unless @_ratatui_test_terminal_active
67
- raise "Events must be injected inside a `with_test_terminal` block. " \
67
+ raise RatatuiRuby::Error::Invariant, "Events must be injected inside a `with_test_terminal` block. " \
68
68
  "Calling this method outside the block causes a race condition where the event " \
69
69
  "is flushed before the application starts."
70
70
  end
@@ -209,6 +209,11 @@ module RatatuiRuby
209
209
  # pending async operations to complete before processing the next event.
210
210
  # This enables deterministic testing of async behavior.
211
211
  #
212
+ # *Important*: Sync waits for commands to _complete_. Do not use it
213
+ # with long-running commands that wait indefinitely (e.g., for
214
+ # cancellation). Those commands will block forever, causing a timeout.
215
+ # For cancellation tests, dispatch the cancel command without Sync.
216
+ #
212
217
  # === Example
213
218
  #
214
219
  #--
@@ -92,6 +92,15 @@ module RatatuiRuby
92
92
  # SPDX-SnippetEnd
93
93
  #++
94
94
  module TestHelper
95
+ ##
96
+ # Auto-enables debug mode when TestHelper is included.
97
+ #
98
+ # This ensures Rust backtraces are available in tests.
99
+ # Skips remote debugging since tests don't need it.
100
+ def self.included(base)
101
+ RatatuiRuby::Debug.enable!(source: :test)
102
+ end
103
+
95
104
  include Terminal
96
105
  include Snapshot
97
106
  include EventInjection