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,158 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ use crate::style::{parse_color_value, parse_modifier_str, parse_style};
5
+ use crate::widgets;
6
+ use magnus::{prelude::*, Error, RArray, Value};
7
+ use ratatui::{buffer::Buffer, layout::Rect, style::Style, widgets::Widget};
8
+
9
+ pub fn render_node(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
10
+ if node.respond_to("render", true)? {
11
+ let ruby = magnus::Ruby::get().unwrap();
12
+ let ruby_area = {
13
+ let module = ruby.define_module("RatatuiRuby")?;
14
+ let layout_mod = module.const_get::<_, magnus::RModule>("Layout")?;
15
+ let class = layout_mod.const_get::<_, magnus::RClass>("Rect")?;
16
+ class.funcall::<_, _, Value>("new", (area.x, area.y, area.width, area.height))?
17
+ };
18
+
19
+ // Call render with just the area (no buffer!)
20
+ let commands: Value = node.funcall("render", (ruby_area,))?;
21
+
22
+ // Process returned draw commands
23
+ if let Some(arr) = RArray::from_value(commands) {
24
+ for i in 0..arr.len() {
25
+ let ruby = magnus::Ruby::get().unwrap();
26
+ let index = isize::try_from(i)
27
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
28
+ let cmd: Value = arr.entry(index)?;
29
+ process_draw_command(buffer, cmd)?;
30
+ }
31
+ }
32
+ return Ok(());
33
+ }
34
+
35
+ // SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
36
+ let class_name = unsafe { node.class().name() }.into_owned();
37
+
38
+ // Special case: Cursor widget requires Frame, not just Buffer
39
+ // For now, we skip it in buffer-only contexts (like insert_before)
40
+ // Frame-based callers should check for Cursor and handle it specially
41
+ if class_name.as_str() == "RatatuiRuby::Widgets::Cursor" {
42
+ // No-op for buffer-only rendering
43
+ // Frame-based rendering should use Frame.set_cursor_position directly
44
+ return Ok(());
45
+ }
46
+
47
+ match class_name.as_str() {
48
+ "RatatuiRuby::Widgets::Paragraph" => widgets::paragraph::render(buffer, area, node)?,
49
+ "RatatuiRuby::Widgets::Clear" => widgets::clear::render(buffer, area, node)?,
50
+ "RatatuiRuby::Widgets::Overlay" => widgets::overlay::render(buffer, area, node)?,
51
+ "RatatuiRuby::Widgets::Center" => widgets::center::render(buffer, area, node)?,
52
+ "RatatuiRuby::Layout::Layout" => widgets::layout::render(buffer, area, node)?,
53
+ "RatatuiRuby::Widgets::List" => widgets::list::render(buffer, area, node)?,
54
+ "RatatuiRuby::Widgets::Gauge" => widgets::gauge::render(buffer, area, node)?,
55
+ "RatatuiRuby::Widgets::LineGauge" => widgets::line_gauge::render(buffer, area, node)?,
56
+ "RatatuiRuby::Widgets::Table" => widgets::table::render(buffer, area, node)?,
57
+ "RatatuiRuby::Widgets::Block" => widgets::block::render(buffer, area, node)?,
58
+ "RatatuiRuby::Widgets::Tabs" => widgets::tabs::render(buffer, area, node)?,
59
+ "RatatuiRuby::Widgets::Scrollbar" => widgets::scrollbar::render(buffer, area, node)?,
60
+ "RatatuiRuby::Widgets::BarChart" => widgets::barchart::render(buffer, area, node)?,
61
+ "RatatuiRuby::Widgets::Canvas" => widgets::canvas::render(buffer, area, node)?,
62
+ "RatatuiRuby::Widgets::Calendar" => widgets::calendar::render(buffer, area, node)?,
63
+ "RatatuiRuby::Widgets::Sparkline" => widgets::sparkline::render(buffer, area, node)?,
64
+ "RatatuiRuby::Widgets::Chart" => {
65
+ widgets::chart::render(buffer, area, node)?;
66
+ }
67
+ "RatatuiRuby::Widgets::RatatuiLogo" => widgets::ratatui_logo::render(buffer, area, node),
68
+ "RatatuiRuby::Widgets::RatatuiMascot" => {
69
+ widgets::ratatui_mascot::render_ratatui_mascot(buffer, area, node)?;
70
+ }
71
+ // Text primitives can also be rendered directly as widgets
72
+ "RatatuiRuby::Text::Line" => {
73
+ let line = crate::text::parse_line(node)?;
74
+ line.render(area, buffer);
75
+ }
76
+ "RatatuiRuby::Text::Span" => {
77
+ let span = crate::text::parse_span(node)?;
78
+ span.render(area, buffer);
79
+ }
80
+ _ => {}
81
+ }
82
+ Ok(())
83
+ }
84
+
85
+ pub fn render_widget_to_buffer(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
86
+ // Just delegate to render_node since it now works with Buffer
87
+ render_node(buffer, area, node)
88
+ }
89
+
90
+ fn process_draw_command(buffer: &mut Buffer, cmd: Value) -> Result<(), Error> {
91
+ let ruby = magnus::Ruby::get().unwrap();
92
+ // SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
93
+ let class_name = unsafe { cmd.class().name() }.into_owned();
94
+
95
+ match class_name.as_str() {
96
+ "RatatuiRuby::Draw::StringCmd" => {
97
+ let x: u16 = cmd.funcall("x", ())?;
98
+ let y: u16 = cmd.funcall("y", ())?;
99
+ let string: String = cmd.funcall("string", ())?;
100
+ let style_val: Value = cmd.funcall("style", ())?;
101
+ let style = parse_style(style_val)?;
102
+ buffer.set_string(x, y, string, style);
103
+ }
104
+ "RatatuiRuby::Draw::CellCmd" => {
105
+ let x: u16 = cmd.funcall("x", ())?;
106
+ let y: u16 = cmd.funcall("y", ())?;
107
+ let cell_val: Value = cmd.funcall("cell", ())?;
108
+
109
+ let area = buffer.area;
110
+ if x >= area.x + area.width || y >= area.y + area.height {
111
+ return Ok(());
112
+ }
113
+
114
+ let symbol: String = cell_val.funcall("char", ())?;
115
+ let fg_val: Value = cell_val.funcall("fg", ())?;
116
+ let bg_val: Value = cell_val.funcall("bg", ())?;
117
+ let modifiers_val: Value = cell_val.funcall("modifiers", ())?;
118
+
119
+ let mut style = Style::default();
120
+
121
+ if !fg_val.is_nil() {
122
+ if let Some(color) = parse_color_value(fg_val)? {
123
+ style = style.fg(color);
124
+ }
125
+ }
126
+ if !bg_val.is_nil() {
127
+ if let Some(color) = parse_color_value(bg_val)? {
128
+ style = style.bg(color);
129
+ }
130
+ }
131
+
132
+ if let Some(mods_array) = RArray::from_value(modifiers_val) {
133
+ for i in 0..mods_array.len() {
134
+ let index = isize::try_from(i)
135
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
136
+ let mod_val: Value = mods_array.entry(index)?;
137
+ // Accept both symbols and strings (DWIM)
138
+ let mod_str: String = mod_val.funcall("to_s", ())?;
139
+ if let Some(modifier) = parse_modifier_str(&mod_str) {
140
+ style = style.add_modifier(modifier);
141
+ }
142
+ }
143
+ }
144
+
145
+ if let Some(cell) = buffer.cell_mut((x, y)) {
146
+ cell.set_symbol(&symbol).set_style(style);
147
+ }
148
+ }
149
+ _ => {
150
+ return Err(Error::new(
151
+ ruby.exception_type_error(),
152
+ format!("Unknown draw command: {class_name}"),
153
+ ));
154
+ }
155
+ }
156
+
157
+ Ok(())
158
+ }
@@ -0,0 +1,101 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ use magnus::{prelude::*, Error, Value};
5
+
6
+ /// Calculate the display width of a string in terminal cells.
7
+ ///
8
+ /// Handles unicode correctly, including:
9
+ /// - Regular ASCII characters: 1 cell each
10
+ /// - CJK characters: 2 cells each (full-width)
11
+ /// - Emoji: typically 2 cells each (varies by terminal)
12
+ /// - Combining marks and zero-width characters: 0 cells
13
+ ///
14
+ /// This uses the same `unicode-width` crate that Ratatui uses internally.
15
+ ///
16
+ /// Returns the total display width in cells (not bytes or characters).
17
+ pub fn text_width(string: Value) -> Result<usize, Error> {
18
+ let ruby = magnus::Ruby::get().unwrap();
19
+
20
+ let s: String = String::try_convert(string).map_err(|_| {
21
+ Error::new(
22
+ ruby.exception_type_error(),
23
+ "expected a String or object that converts to String",
24
+ )
25
+ })?;
26
+
27
+ // Use unicode_width's width calculation.
28
+ // This is the same mechanism Ratatui uses internally for Paragraph.line_width().
29
+ let width = s
30
+ .chars()
31
+ .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
32
+ .sum();
33
+
34
+ Ok(width)
35
+ }
36
+
37
+ #[cfg(test)]
38
+ mod tests {
39
+ use unicode_width::UnicodeWidthChar;
40
+
41
+ fn measure_width(s: &str) -> usize {
42
+ s.chars()
43
+ .map(|c| UnicodeWidthChar::width(c).unwrap_or(0))
44
+ .sum()
45
+ }
46
+
47
+ #[test]
48
+ fn test_ascii_width() {
49
+ // ASCII is 1 cell per character
50
+ assert_eq!(measure_width("hello"), 5);
51
+ assert_eq!(measure_width("Hello, World!"), 13);
52
+ }
53
+
54
+ #[test]
55
+ fn test_emoji_width() {
56
+ // Emoji typically take 2 cells
57
+ // 👍 is U+1F44D THUMBS UP SIGN, width 2
58
+ assert_eq!(measure_width("👍"), 2);
59
+ // 🌍 is U+1F30D EARTH GLOBE EUROPE-AFRICA, width 2
60
+ assert_eq!(measure_width("🌍"), 2);
61
+ // "Hello 👍" = 5 + 1 + 2 = 8
62
+ assert_eq!(measure_width("Hello 👍"), 8);
63
+ }
64
+
65
+ #[test]
66
+ fn test_cjk_width() {
67
+ // CJK characters are full-width, 2 cells each
68
+ // 你 (U+4F60) is width 2
69
+ assert_eq!(measure_width("你"), 2);
70
+ // 好 (U+597D) is width 2
71
+ assert_eq!(measure_width("好"), 2);
72
+ // "你好" should be 4
73
+ assert_eq!(measure_width("你好"), 4);
74
+ }
75
+
76
+ #[test]
77
+ fn test_mixed_width() {
78
+ // "a你b好" = 1 + 2 + 1 + 2 = 6
79
+ assert_eq!(measure_width("a你b好"), 6);
80
+ }
81
+
82
+ #[test]
83
+ fn test_empty_string() {
84
+ assert_eq!(measure_width(""), 0);
85
+ }
86
+
87
+ #[test]
88
+ fn test_spaces_and_punctuation() {
89
+ // Regular ASCII space and punctuation are 1 cell each
90
+ assert_eq!(measure_width("a b c"), 5);
91
+ assert_eq!(measure_width("!!!"), 3);
92
+ }
93
+
94
+ #[test]
95
+ fn test_combining_marks() {
96
+ // Zero-width marks don't add to width
97
+ // "a" + combining acute accent (U+0301)
98
+ let combining = "a\u{0301}";
99
+ assert_eq!(measure_width(combining), 1);
100
+ }
101
+ }