ratatui_ruby 0.4.0 → 0.5.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 (351) 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 +87 -171
  7. data/CHANGELOG.md +38 -1
  8. data/README.md +8 -3
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +105 -45
  11. data/doc/application_testing.md +5 -3
  12. data/doc/contributors/design/ruby_frontend.md +9 -5
  13. data/doc/contributors/developing_examples.md +76 -18
  14. data/doc/contributors/documentation_style.md +7 -0
  15. data/doc/contributors/index.md +2 -0
  16. data/doc/event_handling.md +10 -4
  17. data/doc/images/app_all_events.png +0 -0
  18. data/doc/images/app_color_picker.png +0 -0
  19. data/doc/images/verify_readme_usage.png +0 -0
  20. data/doc/images/widget_barchart_demo.png +0 -0
  21. data/doc/images/widget_block_padding.png +0 -0
  22. data/doc/images/widget_block_titles.png +0 -0
  23. data/doc/images/widget_box_demo.png +0 -0
  24. data/doc/images/widget_calendar_demo.png +0 -0
  25. data/doc/images/widget_cell_demo.png +0 -0
  26. data/doc/images/widget_chart_demo.png +0 -0
  27. data/doc/images/widget_gauge_demo.png +0 -0
  28. data/doc/images/widget_layout_split.png +0 -0
  29. data/doc/images/widget_line_gauge_demo.png +0 -0
  30. data/doc/images/widget_list_demo.png +0 -0
  31. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  32. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  33. data/doc/images/widget_render.png +0 -0
  34. data/doc/images/widget_scrollbar_demo.png +0 -0
  35. data/doc/images/widget_sparkline_demo.png +0 -0
  36. data/doc/images/widget_style_colors.png +0 -0
  37. data/doc/images/widget_table_flex.png +0 -0
  38. data/doc/images/widget_tabs_demo.png +0 -0
  39. data/doc/interactive_design.md +25 -30
  40. data/doc/quickstart.md +147 -120
  41. data/examples/app_all_events/README.md +81 -0
  42. data/examples/app_all_events/app.rb +93 -0
  43. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  44. data/examples/app_all_events/model/event_entry.rb +75 -0
  45. data/examples/app_all_events/model/events.rb +180 -0
  46. data/examples/app_all_events/model/highlight.rb +57 -0
  47. data/examples/app_all_events/model/timestamp.rb +54 -0
  48. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  49. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  50. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  51. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  52. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  53. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  54. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  55. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  56. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  57. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  58. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  59. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  60. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  61. data/examples/app_all_events/view/app_view.rb +78 -0
  62. data/examples/app_all_events/view/controls_view.rb +50 -0
  63. data/examples/app_all_events/view/counts_view.rb +55 -0
  64. data/examples/app_all_events/view/live_view.rb +69 -0
  65. data/examples/app_all_events/view/log_view.rb +60 -0
  66. data/examples/app_all_events/view.rb +7 -0
  67. data/examples/app_all_events/view_state.rb +42 -0
  68. data/examples/app_color_picker/README.md +94 -0
  69. data/examples/app_color_picker/app.rb +112 -0
  70. data/examples/app_color_picker/clipboard.rb +84 -0
  71. data/examples/app_color_picker/color.rb +191 -0
  72. data/examples/app_color_picker/copy_dialog.rb +170 -0
  73. data/examples/app_color_picker/harmony.rb +56 -0
  74. data/examples/app_color_picker/input.rb +142 -0
  75. data/examples/app_color_picker/palette.rb +80 -0
  76. data/examples/app_color_picker/scene.rb +201 -0
  77. data/examples/{login_form → app_login_form}/app.rb +39 -42
  78. data/examples/{map_demo → app_map_demo}/app.rb +24 -21
  79. data/examples/{table_select → app_table_select}/app.rb +68 -65
  80. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +15 -6
  81. data/examples/verify_quickstart_layout/app.rb +69 -0
  82. data/examples/{quickstart_lifecycle → verify_quickstart_lifecycle}/app.rb +19 -10
  83. data/examples/verify_readme_usage/app.rb +34 -0
  84. data/examples/widget_barchart_demo/app.rb +238 -0
  85. data/examples/{block_padding → widget_block_padding}/app.rb +17 -13
  86. data/examples/{block_titles → widget_block_titles}/app.rb +25 -17
  87. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  88. data/examples/widget_calendar_demo/app.rb +109 -0
  89. data/examples/widget_cell_demo/app.rb +104 -0
  90. data/examples/widget_chart_demo/app.rb +213 -0
  91. data/examples/widget_gauge_demo/app.rb +212 -0
  92. data/examples/widget_layout_split/app.rb +246 -0
  93. data/examples/widget_line_gauge_demo/app.rb +217 -0
  94. data/examples/widget_list_demo/app.rb +382 -0
  95. data/examples/widget_list_styles/app.rb +141 -0
  96. data/examples/widget_popup_demo/app.rb +104 -0
  97. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  98. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  99. data/examples/widget_rect/app.rb +205 -0
  100. data/examples/widget_render/app.rb +184 -0
  101. data/examples/widget_rich_text/app.rb +137 -0
  102. data/examples/widget_scroll_text/app.rb +108 -0
  103. data/examples/widget_scrollbar_demo/app.rb +153 -0
  104. data/examples/widget_sparkline_demo/app.rb +274 -0
  105. data/examples/widget_style_colors/app.rb +19 -21
  106. data/examples/widget_table_flex/app.rb +95 -0
  107. data/examples/widget_tabs_demo/app.rb +167 -0
  108. data/ext/ratatui_ruby/Cargo.lock +1 -1
  109. data/ext/ratatui_ruby/Cargo.toml +1 -1
  110. data/ext/ratatui_ruby/src/events.rs +121 -36
  111. data/ext/ratatui_ruby/src/frame.rs +115 -0
  112. data/ext/ratatui_ruby/src/lib.rs +79 -26
  113. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  114. data/ext/ratatui_ruby/src/style.rs +138 -57
  115. data/ext/ratatui_ruby/src/terminal.rs +5 -9
  116. data/ext/ratatui_ruby/src/text.rs +13 -6
  117. data/ext/ratatui_ruby/src/widgets/barchart.rs +56 -54
  118. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  119. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  120. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  121. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  122. data/ext/ratatui_ruby/src/widgets/list.rs +32 -9
  123. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  124. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  125. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  126. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  127. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +4 -2
  128. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  129. data/ext/ratatui_ruby/src/widgets/table.rs +8 -4
  130. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  131. data/lib/ratatui_ruby/cell.rb +3 -3
  132. data/lib/ratatui_ruby/event/key.rb +1 -1
  133. data/lib/ratatui_ruby/event/none.rb +43 -0
  134. data/lib/ratatui_ruby/event.rb +56 -4
  135. data/lib/ratatui_ruby/frame.rb +87 -0
  136. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +11 -11
  137. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  138. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  139. data/lib/ratatui_ruby/schema/block.rb +163 -168
  140. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  141. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  142. data/lib/ratatui_ruby/schema/center.rb +46 -46
  143. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  144. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  145. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  146. data/lib/ratatui_ruby/schema/cursor.rb +25 -25
  147. data/lib/ratatui_ruby/schema/gauge.rb +53 -53
  148. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  149. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  150. data/lib/ratatui_ruby/schema/list.rb +86 -84
  151. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  152. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  153. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  154. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  155. data/lib/ratatui_ruby/schema/rect.rb +60 -60
  156. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  157. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  158. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  159. data/lib/ratatui_ruby/schema/style.rb +46 -46
  160. data/lib/ratatui_ruby/schema/table.rb +112 -119
  161. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  162. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  163. data/lib/ratatui_ruby/session.rb +40 -23
  164. data/lib/ratatui_ruby/test_helper.rb +185 -19
  165. data/lib/ratatui_ruby/version.rb +1 -1
  166. data/lib/ratatui_ruby.rb +65 -39
  167. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  168. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  169. data/sig/examples/app_all_events/model/events.rbs +15 -0
  170. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  171. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  172. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  173. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  174. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  175. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  176. data/sig/examples/app_all_events/view.rbs +8 -0
  177. data/sig/examples/app_all_events/view_state.rbs +15 -0
  178. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  179. data/sig/examples/app_login_form/app.rbs +11 -0
  180. data/sig/examples/app_map_demo/app.rbs +11 -0
  181. data/sig/examples/app_table_select/app.rbs +11 -0
  182. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  183. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  184. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  185. data/sig/examples/widget_block_padding/app.rbs +11 -0
  186. data/sig/examples/widget_block_titles/app.rbs +11 -0
  187. data/sig/examples/widget_box_demo/app.rbs +11 -0
  188. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  189. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  190. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  191. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  192. data/sig/examples/widget_layout_split/app.rbs +10 -0
  193. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  194. data/sig/examples/widget_list_demo/app.rbs +12 -0
  195. data/sig/examples/widget_list_styles/app.rbs +11 -0
  196. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  197. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  198. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  199. data/sig/examples/widget_rect/app.rbs +12 -0
  200. data/sig/examples/widget_render/app.rbs +10 -0
  201. data/sig/examples/widget_rich_text/app.rbs +11 -0
  202. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  203. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  204. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  205. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  206. data/sig/examples/widget_table_flex/app.rbs +11 -0
  207. data/sig/ratatui_ruby/frame.rbs +9 -0
  208. data/sig/ratatui_ruby/ratatui_ruby.rbs +3 -2
  209. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  210. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  211. data/sig/ratatui_ruby/session.rbs +94 -0
  212. data/tasks/autodoc/inventory.rb +61 -0
  213. data/tasks/autodoc/member.rb +56 -0
  214. data/tasks/autodoc/name.rb +19 -0
  215. data/tasks/autodoc/notice.rb +26 -0
  216. data/tasks/autodoc/rbs.rb +38 -0
  217. data/tasks/autodoc/rdoc.rb +45 -0
  218. data/tasks/autodoc.rake +47 -0
  219. data/tasks/bump/history.rb +2 -2
  220. data/tasks/doc.rake +600 -6
  221. data/tasks/example_viewer.html.erb +172 -0
  222. data/tasks/lint.rake +8 -4
  223. data/tasks/resources/index.html.erb +6 -0
  224. data/tasks/sourcehut.rake +4 -4
  225. data/tasks/terminal_preview/app_screenshot.rb +1 -3
  226. data/tasks/terminal_preview/crash_report.rb +7 -9
  227. data/tasks/terminal_preview/launcher_script.rb +4 -6
  228. data/tasks/terminal_preview/preview_collection.rb +4 -6
  229. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  230. data/tasks/terminal_preview/saved_screenshot.rb +7 -9
  231. data/tasks/terminal_preview/terminal_window.rb +7 -9
  232. data/tasks/test.rake +1 -1
  233. data/tasks/website/index_page.rb +3 -3
  234. data/tasks/website/version.rb +10 -10
  235. data/tasks/website/version_menu.rb +10 -12
  236. data/tasks/website/versioned_documentation.rb +49 -17
  237. data/tasks/website/website.rb +6 -8
  238. data/tasks/website.rake +4 -4
  239. metadata +156 -125
  240. data/LICENSES/BSD-2-Clause.txt +0 -9
  241. data/doc/contributors/better_dx.md +0 -543
  242. data/doc/contributors/example_analysis.md +0 -82
  243. data/doc/images/all_events.png +0 -0
  244. data/doc/images/block_padding.png +0 -0
  245. data/doc/images/block_titles.png +0 -0
  246. data/doc/images/box_demo.png +0 -0
  247. data/doc/images/calendar_demo.png +0 -0
  248. data/doc/images/cell_demo.png +0 -0
  249. data/doc/images/chart_demo.png +0 -0
  250. data/doc/images/flex_layout.png +0 -0
  251. data/doc/images/gauge_demo.png +0 -0
  252. data/doc/images/line_gauge_demo.png +0 -0
  253. data/doc/images/list_demo.png +0 -0
  254. data/doc/images/readme_usage.png +0 -0
  255. data/doc/images/scrollbar_demo.png +0 -0
  256. data/doc/images/sparkline_demo.png +0 -0
  257. data/doc/images/table_flex.png +0 -0
  258. data/examples/all_events/app.rb +0 -169
  259. data/examples/all_events/app.rbs +0 -7
  260. data/examples/all_events/test_app.rb +0 -139
  261. data/examples/analytics/app.rb +0 -258
  262. data/examples/analytics/app.rbs +0 -7
  263. data/examples/analytics/test_app.rb +0 -132
  264. data/examples/block_padding/app.rbs +0 -7
  265. data/examples/block_padding/test_app.rb +0 -31
  266. data/examples/block_titles/app.rbs +0 -7
  267. data/examples/block_titles/test_app.rb +0 -34
  268. data/examples/box_demo/app.rbs +0 -7
  269. data/examples/box_demo/test_app.rb +0 -88
  270. data/examples/calendar_demo/app.rb +0 -101
  271. data/examples/calendar_demo/app.rbs +0 -7
  272. data/examples/calendar_demo/test_app.rb +0 -108
  273. data/examples/cell_demo/app.rb +0 -108
  274. data/examples/cell_demo/app.rbs +0 -7
  275. data/examples/cell_demo/test_app.rb +0 -36
  276. data/examples/chart_demo/app.rb +0 -203
  277. data/examples/chart_demo/app.rbs +0 -7
  278. data/examples/chart_demo/test_app.rb +0 -102
  279. data/examples/custom_widget/app.rb +0 -51
  280. data/examples/custom_widget/app.rbs +0 -7
  281. data/examples/custom_widget/test_app.rb +0 -30
  282. data/examples/flex_layout/app.rb +0 -156
  283. data/examples/flex_layout/app.rbs +0 -7
  284. data/examples/flex_layout/test_app.rb +0 -65
  285. data/examples/gauge_demo/app.rb +0 -182
  286. data/examples/gauge_demo/test_app.rb +0 -120
  287. data/examples/hit_test/app.rb +0 -175
  288. data/examples/hit_test/app.rbs +0 -7
  289. data/examples/hit_test/test_app.rb +0 -102
  290. data/examples/line_gauge_demo/app.rb +0 -190
  291. data/examples/line_gauge_demo/app.rbs +0 -7
  292. data/examples/line_gauge_demo/test_app.rb +0 -129
  293. data/examples/list_demo/app.rb +0 -253
  294. data/examples/list_demo/test_app.rb +0 -237
  295. data/examples/list_styles/app.rb +0 -140
  296. data/examples/list_styles/app.rbs +0 -7
  297. data/examples/list_styles/test_app.rb +0 -157
  298. data/examples/login_form/app.rbs +0 -7
  299. data/examples/login_form/test_app.rb +0 -51
  300. data/examples/map_demo/app.rbs +0 -7
  301. data/examples/map_demo/test_app.rb +0 -149
  302. data/examples/mouse_events/app.rb +0 -97
  303. data/examples/mouse_events/app.rbs +0 -7
  304. data/examples/mouse_events/test_app.rb +0 -53
  305. data/examples/popup_demo/app.rb +0 -103
  306. data/examples/popup_demo/app.rbs +0 -7
  307. data/examples/popup_demo/test_app.rb +0 -54
  308. data/examples/quickstart_dsl/app.rbs +0 -7
  309. data/examples/quickstart_dsl/test_app.rb +0 -29
  310. data/examples/quickstart_lifecycle/app.rbs +0 -7
  311. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  312. data/examples/ratatui_logo_demo/app.rb +0 -79
  313. data/examples/ratatui_logo_demo/app.rbs +0 -7
  314. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  315. data/examples/ratatui_mascot_demo/app.rb +0 -84
  316. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  317. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  318. data/examples/readme_usage/app.rb +0 -29
  319. data/examples/readme_usage/app.rbs +0 -7
  320. data/examples/readme_usage/test_app.rb +0 -29
  321. data/examples/rich_text/app.rb +0 -141
  322. data/examples/rich_text/app.rbs +0 -7
  323. data/examples/rich_text/test_app.rb +0 -166
  324. data/examples/scroll_text/app.rb +0 -103
  325. data/examples/scroll_text/app.rbs +0 -7
  326. data/examples/scroll_text/test_app.rb +0 -110
  327. data/examples/scrollbar_demo/app.rb +0 -143
  328. data/examples/scrollbar_demo/app.rbs +0 -7
  329. data/examples/scrollbar_demo/test_app.rb +0 -77
  330. data/examples/sparkline_demo/app.rb +0 -240
  331. data/examples/sparkline_demo/test_app.rb +0 -107
  332. data/examples/table_flex/app.rb +0 -65
  333. data/examples/table_flex/app.rbs +0 -7
  334. data/examples/table_flex/test_app.rb +0 -36
  335. data/examples/table_select/app.rbs +0 -7
  336. data/examples/table_select/test_app.rb +0 -180
  337. data/examples/widget_style_colors/test_app.rb +0 -48
  338. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  339. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  340. /data/doc/images/{login_form.png → app_login_form.png} +0 -0
  341. /data/doc/images/{map_demo.png → app_map_demo.png} +0 -0
  342. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  343. /data/doc/images/{table_select.png → app_table_select.png} +0 -0
  344. /data/doc/images/{quickstart_dsl.png → verify_quickstart_dsl.png} +0 -0
  345. /data/doc/images/{ratatui_logo_demo.png → verify_quickstart_layout.png} +0 -0
  346. /data/doc/images/{quickstart_lifecycle.png → verify_quickstart_lifecycle.png} +0 -0
  347. /data/doc/images/{list_styles.png → widget_list_styles.png} +0 -0
  348. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  349. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  350. /data/doc/images/{rich_text.png → widget_rich_text.png} +0 -0
  351. /data/doc/images/{scroll_text.png → widget_scroll_text.png} +0 -0
@@ -28,10 +28,13 @@ pub fn inject_test_event(event_type: String, data: magnus::RHash) -> Result<(),
28
28
  Ok(())
29
29
  }
30
30
 
31
- fn parse_key_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
32
- let code_val: Value = data.get(ruby.to_symbol("code")).ok_or_else(|| {
33
- Error::new(ruby.exception_arg_error(), "Missing 'code' in key event")
34
- })?;
31
+ fn parse_key_event(
32
+ data: magnus::RHash,
33
+ ruby: &magnus::Ruby,
34
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
35
+ let code_val: Value = data
36
+ .get(ruby.to_symbol("code"))
37
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'code' in key event"))?;
35
38
  let code_str: String = String::try_convert(code_val)?;
36
39
  let code = match code_str.as_str() {
37
40
  "up" => ratatui::crossterm::event::KeyCode::Up,
@@ -59,13 +62,18 @@ fn parse_key_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::
59
62
  }
60
63
  }
61
64
 
62
- Ok(ratatui::crossterm::event::Event::Key(ratatui::crossterm::event::KeyEvent::new(code, modifiers)))
65
+ Ok(ratatui::crossterm::event::Event::Key(
66
+ ratatui::crossterm::event::KeyEvent::new(code, modifiers),
67
+ ))
63
68
  }
64
69
 
65
- fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
66
- let kind_val: Value = data.get(ruby.to_symbol("kind")).ok_or_else(|| {
67
- Error::new(ruby.exception_arg_error(), "Missing 'kind' in mouse event")
68
- })?;
70
+ fn parse_mouse_event(
71
+ data: magnus::RHash,
72
+ ruby: &magnus::Ruby,
73
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
74
+ let kind_val: Value = data
75
+ .get(ruby.to_symbol("kind"))
76
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'kind' in mouse event"))?;
69
77
  let kind_str: String = String::try_convert(kind_val)?;
70
78
 
71
79
  let button = if let Some(btn_val) = data.get(ruby.to_symbol("button")) {
@@ -79,14 +87,14 @@ fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui
79
87
  ratatui::crossterm::event::MouseButton::Left
80
88
  };
81
89
 
82
- let x_val: Value = data.get(ruby.to_symbol("x")).ok_or_else(|| {
83
- Error::new(ruby.exception_arg_error(), "Missing 'x' in mouse event")
84
- })?;
90
+ let x_val: Value = data
91
+ .get(ruby.to_symbol("x"))
92
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'x' in mouse event"))?;
85
93
  let x: u16 = u16::try_convert(x_val)?;
86
94
 
87
- let y_val: Value = data.get(ruby.to_symbol("y")).ok_or_else(|| {
88
- Error::new(ruby.exception_arg_error(), "Missing 'y' in mouse event")
89
- })?;
95
+ let y_val: Value = data
96
+ .get(ruby.to_symbol("y"))
97
+ .ok_or_else(|| Error::new(ruby.exception_arg_error(), "Missing 'y' in mouse event"))?;
90
98
  let y: u16 = u16::try_convert(y_val)?;
91
99
 
92
100
  let kind = match kind_str.as_str() {
@@ -98,7 +106,12 @@ fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui
98
106
  "scroll_up" => ratatui::crossterm::event::MouseEventKind::ScrollUp,
99
107
  "scroll_left" => ratatui::crossterm::event::MouseEventKind::ScrollLeft,
100
108
  "scroll_right" => ratatui::crossterm::event::MouseEventKind::ScrollRight,
101
- _ => return Err(Error::new(ruby.exception_arg_error(), format!("Unknown mouse kind: {kind_str}"))),
109
+ _ => {
110
+ return Err(Error::new(
111
+ ruby.exception_arg_error(),
112
+ format!("Unknown mouse kind: {kind_str}"),
113
+ ))
114
+ }
102
115
  };
103
116
 
104
117
  let mut modifiers = ratatui::crossterm::event::KeyModifiers::empty();
@@ -114,26 +127,48 @@ fn parse_mouse_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui
114
127
  }
115
128
  }
116
129
 
117
- Ok(ratatui::crossterm::event::Event::Mouse(ratatui::crossterm::event::MouseEvent { kind, column: x, row: y, modifiers }))
130
+ Ok(ratatui::crossterm::event::Event::Mouse(
131
+ ratatui::crossterm::event::MouseEvent {
132
+ kind,
133
+ column: x,
134
+ row: y,
135
+ modifiers,
136
+ },
137
+ ))
118
138
  }
119
139
 
120
- fn parse_resize_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
140
+ fn parse_resize_event(
141
+ data: magnus::RHash,
142
+ ruby: &magnus::Ruby,
143
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
121
144
  let width_val: Value = data.get(ruby.to_symbol("width")).ok_or_else(|| {
122
- Error::new(ruby.exception_arg_error(), "Missing 'width' in resize event")
145
+ Error::new(
146
+ ruby.exception_arg_error(),
147
+ "Missing 'width' in resize event",
148
+ )
123
149
  })?;
124
150
  let width: u16 = u16::try_convert(width_val)?;
125
151
 
126
152
  let height_val: Value = data.get(ruby.to_symbol("height")).ok_or_else(|| {
127
- Error::new(ruby.exception_arg_error(), "Missing 'height' in resize event")
153
+ Error::new(
154
+ ruby.exception_arg_error(),
155
+ "Missing 'height' in resize event",
156
+ )
128
157
  })?;
129
158
  let height: u16 = u16::try_convert(height_val)?;
130
159
 
131
160
  Ok(ratatui::crossterm::event::Event::Resize(width, height))
132
161
  }
133
162
 
134
- fn parse_paste_event(data: magnus::RHash, ruby: &magnus::Ruby) -> Result<ratatui::crossterm::event::Event, Error> {
163
+ fn parse_paste_event(
164
+ data: magnus::RHash,
165
+ ruby: &magnus::Ruby,
166
+ ) -> Result<ratatui::crossterm::event::Event, Error> {
135
167
  let content_val: Value = data.get(ruby.to_symbol("content")).ok_or_else(|| {
136
- Error::new(ruby.exception_arg_error(), "Missing 'content' in paste event")
168
+ Error::new(
169
+ ruby.exception_arg_error(),
170
+ "Missing 'content' in paste event",
171
+ )
137
172
  })?;
138
173
  let content: String = String::try_convert(content_val)?;
139
174
  Ok(ratatui::crossterm::event::Event::Paste(content))
@@ -159,7 +194,10 @@ pub fn poll_event(ruby: &magnus::Ruby) -> Result<Value, Error> {
159
194
 
160
195
  let is_test_mode = {
161
196
  let term_lock = crate::terminal::TERMINAL.lock().unwrap();
162
- matches!(term_lock.as_ref(), Some(crate::terminal::TerminalWrapper::Test(_)))
197
+ matches!(
198
+ term_lock.as_ref(),
199
+ Some(crate::terminal::TerminalWrapper::Test(_))
200
+ )
163
201
  };
164
202
 
165
203
  if is_test_mode {
@@ -209,10 +247,27 @@ fn handle_key_event(key: ratatui::crossterm::event::KeyEvent) -> Result<Value, E
209
247
  };
210
248
  hash.aset(ruby.to_symbol("code"), code)?;
211
249
  let mut modifiers = Vec::new();
212
- if key.modifiers.contains(ratatui::crossterm::event::KeyModifiers::CONTROL) { modifiers.push("ctrl"); }
213
- if key.modifiers.contains(ratatui::crossterm::event::KeyModifiers::ALT) { modifiers.push("alt"); }
214
- if key.modifiers.contains(ratatui::crossterm::event::KeyModifiers::SHIFT) { modifiers.push("shift"); }
215
- if !modifiers.is_empty() { hash.aset(ruby.to_symbol("modifiers"), modifiers)?; }
250
+ if key
251
+ .modifiers
252
+ .contains(ratatui::crossterm::event::KeyModifiers::CONTROL)
253
+ {
254
+ modifiers.push("ctrl");
255
+ }
256
+ if key
257
+ .modifiers
258
+ .contains(ratatui::crossterm::event::KeyModifiers::ALT)
259
+ {
260
+ modifiers.push("alt");
261
+ }
262
+ if key
263
+ .modifiers
264
+ .contains(ratatui::crossterm::event::KeyModifiers::SHIFT)
265
+ {
266
+ modifiers.push("shift");
267
+ }
268
+ if !modifiers.is_empty() {
269
+ hash.aset(ruby.to_symbol("modifiers"), modifiers)?;
270
+ }
216
271
  Ok(hash.into_value_with(&ruby))
217
272
  }
218
273
 
@@ -224,14 +279,29 @@ fn handle_mouse_event(event: ratatui::crossterm::event::MouseEvent) -> Result<Va
224
279
  ratatui::crossterm::event::MouseEventKind::Down(btn) => ("down", btn),
225
280
  ratatui::crossterm::event::MouseEventKind::Up(btn) => ("up", btn),
226
281
  ratatui::crossterm::event::MouseEventKind::Drag(btn) => ("drag", btn),
227
- ratatui::crossterm::event::MouseEventKind::Moved => ("moved", ratatui::crossterm::event::MouseButton::Left),
228
- ratatui::crossterm::event::MouseEventKind::ScrollDown => ("scroll_down", ratatui::crossterm::event::MouseButton::Left),
229
- ratatui::crossterm::event::MouseEventKind::ScrollUp => ("scroll_up", ratatui::crossterm::event::MouseButton::Left),
230
- ratatui::crossterm::event::MouseEventKind::ScrollLeft => ("scroll_left", ratatui::crossterm::event::MouseButton::Left),
231
- ratatui::crossterm::event::MouseEventKind::ScrollRight => ("scroll_right", ratatui::crossterm::event::MouseButton::Left),
282
+ ratatui::crossterm::event::MouseEventKind::Moved => {
283
+ ("moved", ratatui::crossterm::event::MouseButton::Left)
284
+ }
285
+ ratatui::crossterm::event::MouseEventKind::ScrollDown => {
286
+ ("scroll_down", ratatui::crossterm::event::MouseButton::Left)
287
+ }
288
+ ratatui::crossterm::event::MouseEventKind::ScrollUp => {
289
+ ("scroll_up", ratatui::crossterm::event::MouseButton::Left)
290
+ }
291
+ ratatui::crossterm::event::MouseEventKind::ScrollLeft => {
292
+ ("scroll_left", ratatui::crossterm::event::MouseButton::Left)
293
+ }
294
+ ratatui::crossterm::event::MouseEventKind::ScrollRight => {
295
+ ("scroll_right", ratatui::crossterm::event::MouseButton::Left)
296
+ }
232
297
  };
233
298
  hash.aset(ruby.to_symbol("kind"), ruby.to_symbol(kind))?;
234
- if matches!(event.kind, ratatui::crossterm::event::MouseEventKind::Down(_) | ratatui::crossterm::event::MouseEventKind::Up(_) | ratatui::crossterm::event::MouseEventKind::Drag(_)) {
299
+ if matches!(
300
+ event.kind,
301
+ ratatui::crossterm::event::MouseEventKind::Down(_)
302
+ | ratatui::crossterm::event::MouseEventKind::Up(_)
303
+ | ratatui::crossterm::event::MouseEventKind::Drag(_)
304
+ ) {
235
305
  let btn_sym = match button {
236
306
  ratatui::crossterm::event::MouseButton::Left => "left",
237
307
  ratatui::crossterm::event::MouseButton::Right => "right",
@@ -244,9 +314,24 @@ fn handle_mouse_event(event: ratatui::crossterm::event::MouseEvent) -> Result<Va
244
314
  hash.aset(ruby.to_symbol("x"), event.column)?;
245
315
  hash.aset(ruby.to_symbol("y"), event.row)?;
246
316
  let mut modifiers = Vec::new();
247
- if event.modifiers.contains(ratatui::crossterm::event::KeyModifiers::CONTROL) { modifiers.push("ctrl"); }
248
- if event.modifiers.contains(ratatui::crossterm::event::KeyModifiers::ALT) { modifiers.push("alt"); }
249
- if event.modifiers.contains(ratatui::crossterm::event::KeyModifiers::SHIFT) { modifiers.push("shift"); }
317
+ if event
318
+ .modifiers
319
+ .contains(ratatui::crossterm::event::KeyModifiers::CONTROL)
320
+ {
321
+ modifiers.push("ctrl");
322
+ }
323
+ if event
324
+ .modifiers
325
+ .contains(ratatui::crossterm::event::KeyModifiers::ALT)
326
+ {
327
+ modifiers.push("alt");
328
+ }
329
+ if event
330
+ .modifiers
331
+ .contains(ratatui::crossterm::event::KeyModifiers::SHIFT)
332
+ {
333
+ modifiers.push("shift");
334
+ }
250
335
  hash.aset(ruby.to_symbol("modifiers"), modifiers)?;
251
336
  Ok(hash.into_value_with(&ruby))
252
337
  }
@@ -0,0 +1,115 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ //! Frame wrapper for exposing Ratatui's Frame to Ruby.
5
+ //!
6
+ //! This module provides `RubyFrame`, a struct that wraps `ratatui::Frame` and exposes
7
+ //! it to Ruby via Magnus. It enables explicit widget placement through `render_widget`,
8
+ //! aligning `RatatuiRuby` with native Rust Ratatui patterns.
9
+ //!
10
+ //! # Safety
11
+ //!
12
+ //! `RubyFrame` uses raw pointer casting to store a `Frame` reference with an erased
13
+ //! lifetime. This is safe because:
14
+ //! 1. `RubyFrame` is only created within `Terminal::draw()` callbacks
15
+ //! 2. `RubyFrame` is never returned from or stored beyond the callback scope
16
+ //! 3. The Ruby block receiving `RubyFrame` completes before the callback returns
17
+ //!
18
+ //! The `'static` lifetime is a lie, but a safe one within these constraints.
19
+
20
+ use crate::rendering;
21
+ use magnus::{prelude::*, Error, Value};
22
+ use ratatui::layout::Rect;
23
+ use ratatui::Frame;
24
+ use std::cell::UnsafeCell;
25
+ use std::ptr::NonNull;
26
+
27
+ /// A wrapper around Ratatui's `Frame` that can be exposed to Ruby.
28
+ ///
29
+ /// This struct uses raw pointers to hold a mutable reference to the frame,
30
+ /// which is valid only for the duration of the draw callback.
31
+ ///
32
+ /// # Safety
33
+ ///
34
+ /// We implement `Send` manually because:
35
+ /// 1. `RubyFrame` is only created and used within a single `Terminal::draw()` callback
36
+ /// 2. The Ruby VM is single-threaded (GVL), so the frame pointer is never accessed
37
+ /// from multiple threads simultaneously
38
+ /// 3. `RubyFrame` never escapes the draw callback scope
39
+ #[magnus::wrap(class = "RatatuiRuby::Frame")]
40
+ pub struct RubyFrame {
41
+ /// Pointer to the underlying frame. Valid only during the draw callback.
42
+ inner: UnsafeCell<NonNull<Frame<'static>>>,
43
+ }
44
+
45
+ // SAFETY: RubyFrame is only used within Terminal::draw() callbacks, which are
46
+ // single-threaded. The Ruby VM's GVL ensures no concurrent access.
47
+ unsafe impl Send for RubyFrame {}
48
+
49
+ impl RubyFrame {
50
+ /// Creates a new `RubyFrame` wrapping the given frame reference.
51
+ ///
52
+ /// # Safety
53
+ ///
54
+ /// The caller must ensure that:
55
+ /// 1. The `RubyFrame` does not outlive the frame reference
56
+ /// 2. No other mutable references to the frame exist while `RubyFrame` is in use
57
+ pub fn new(frame: &mut Frame<'_>) -> Self {
58
+ // SAFETY: We cast the frame pointer to 'static lifetime. This is safe because:
59
+ // - RubyFrame is only used within Terminal::draw() callbacks
60
+ // - The Ruby block completes before the callback returns
61
+ // - No reference to RubyFrame escapes the callback scope
62
+ let ptr = NonNull::from(frame);
63
+ let static_ptr: NonNull<Frame<'static>> =
64
+ // SAFETY: Lifetime erasure is safe within the draw callback scope.
65
+ // The frame pointer remains valid for the entire callback duration.
66
+ unsafe { std::mem::transmute(ptr) };
67
+
68
+ Self {
69
+ inner: UnsafeCell::new(static_ptr),
70
+ }
71
+ }
72
+
73
+ /// Returns the terminal area as a Ruby `RatatuiRuby::Rect`.
74
+ ///
75
+ /// This mirrors `frame.area()` in Rust Ratatui.
76
+ pub fn area(&self) -> Result<Value, Error> {
77
+ let ruby = magnus::Ruby::get().unwrap();
78
+
79
+ // SAFETY: The frame pointer is valid for the duration of the draw callback.
80
+ // We only read from the frame, which is safe with an immutable reference.
81
+ let area = unsafe { (*self.inner.get()).as_ref().area() };
82
+
83
+ // Create a Ruby Rect object
84
+ let module = ruby.define_module("RatatuiRuby")?;
85
+ let class = module.const_get::<_, magnus::RClass>("Rect")?;
86
+ class.funcall("new", (area.x, area.y, area.width, area.height))
87
+ }
88
+
89
+ /// Renders a widget at the specified area.
90
+ ///
91
+ /// This mirrors `frame.render_widget(widget, area)` in Rust Ratatui.
92
+ ///
93
+ /// # Arguments
94
+ ///
95
+ /// * `widget` - A Ruby widget object (e.g., `RatatuiRuby::Paragraph`)
96
+ /// * `area` - A Ruby `Rect` or hash-like object with `x`, `y`, `width`, `height`
97
+ pub fn render_widget(&self, widget: Value, area: Value) -> Result<(), Error> {
98
+ // Parse the Ruby area into a Rust Rect
99
+ let x: u16 = area.funcall("x", ())?;
100
+ let y: u16 = area.funcall("y", ())?;
101
+ let width: u16 = area.funcall("width", ())?;
102
+ let height: u16 = area.funcall("height", ())?;
103
+ let rect = Rect::new(x, y, width, height);
104
+
105
+ // SAFETY: The frame pointer is valid for the duration of the draw callback.
106
+ // We take a mutable reference which is safe because:
107
+ // 1. RubyFrame is only used within Terminal::draw() callbacks
108
+ // 2. Ruby's GVL ensures single-threaded access
109
+ // 3. No other code holds a reference to the frame during this call
110
+ let frame = unsafe { (*self.inner.get()).as_mut() };
111
+
112
+ // Delegate to the existing render_node function
113
+ rendering::render_node(frame, rect, widget)
114
+ }
115
+ }
@@ -11,36 +11,83 @@
11
11
  #![allow(clippy::module_name_repetitions)]
12
12
 
13
13
  mod events;
14
+ mod frame;
14
15
  mod rendering;
15
16
  mod style;
16
17
  mod terminal;
17
18
  mod text;
18
19
  mod widgets;
19
20
 
20
- use magnus::{function, Error, Module, Object, Value};
21
+ use frame::RubyFrame;
22
+ use magnus::{function, method, Error, Module, Object, Ruby, Value};
21
23
  use terminal::{init_terminal, restore_terminal, TERMINAL};
22
24
 
23
- fn draw(tree: Value) -> Result<(), Error> {
24
- let ruby = magnus::Ruby::get().unwrap();
25
+ /// Draw to the terminal.
26
+ ///
27
+ /// Supports two calling conventions:
28
+ /// - Legacy: `RatatuiRuby.draw(tree)` - Renders a widget tree to the full terminal area
29
+ /// - New: `RatatuiRuby.draw { |frame| ... }` - Yields a Frame for explicit widget placement
30
+ fn draw(args: &[Value]) -> Result<(), Error> {
31
+ let ruby = Ruby::get().unwrap();
32
+
33
+ // Parse arguments: check for optional tree argument
34
+ let tree: Option<Value> = if args.is_empty() {
35
+ None
36
+ } else if args.len() == 1 {
37
+ Some(args[0])
38
+ } else {
39
+ return Err(Error::new(
40
+ ruby.exception_arg_error(),
41
+ format!(
42
+ "wrong number of arguments (given {}, expected 0..1)",
43
+ args.len()
44
+ ),
45
+ ));
46
+ };
47
+ let block_given = ruby.block_given();
48
+
49
+ // Validate: must have either tree or block, but not both
50
+ if tree.is_some() && block_given {
51
+ return Err(Error::new(
52
+ ruby.exception_arg_error(),
53
+ "Cannot provide both a tree and a block to draw",
54
+ ));
55
+ }
56
+ if tree.is_none() && !block_given {
57
+ return Err(Error::new(
58
+ ruby.exception_arg_error(),
59
+ "Must provide either a tree or a block to draw",
60
+ ));
61
+ }
62
+
25
63
  let mut term_lock = TERMINAL.lock().unwrap();
26
- let mut render_error = None;
64
+ let mut render_error: Option<Error> = None;
65
+
66
+ // Helper closure to execute the draw callback logic for either terminal type
67
+ let mut draw_callback = |f: &mut ratatui::Frame<'_>| {
68
+ if block_given {
69
+ // New API: yield RubyFrame to block
70
+ let ruby_frame = RubyFrame::new(f);
71
+ if let Err(e) = ruby.yield_value::<_, Value>(ruby_frame) {
72
+ render_error = Some(e);
73
+ }
74
+ } else if let Some(tree_value) = tree {
75
+ // Legacy API: render tree to full area
76
+ if let Err(e) = rendering::render_node(f, f.area(), tree_value) {
77
+ render_error = Some(e);
78
+ }
79
+ }
80
+ };
81
+
27
82
  if let Some(wrapper) = term_lock.as_mut() {
28
83
  match wrapper {
29
84
  terminal::TerminalWrapper::Crossterm(term) => {
30
- term.draw(|f| {
31
- if let Err(e) = rendering::render_node(f, f.area(), tree) {
32
- render_error = Some(e);
33
- }
34
- })
35
- .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
85
+ term.draw(&mut draw_callback)
86
+ .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
36
87
  }
37
88
  terminal::TerminalWrapper::Test(term) => {
38
- term.draw(|f| {
39
- if let Err(e) = rendering::render_node(f, f.area(), tree) {
40
- render_error = Some(e);
41
- }
42
- })
43
- .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
89
+ term.draw(&mut draw_callback)
90
+ .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
44
91
  }
45
92
  }
46
93
  } else {
@@ -59,11 +106,14 @@ fn init() -> Result<(), Error> {
59
106
  let ruby = magnus::Ruby::get().unwrap();
60
107
  let m = ruby.define_module("RatatuiRuby")?;
61
108
 
62
-
63
-
64
109
  m.define_module_function("_init_terminal", function!(init_terminal, 2))?;
65
110
  m.define_module_function("restore_terminal", function!(restore_terminal, 0))?;
66
- m.define_module_function("draw", function!(draw, 1))?;
111
+ m.define_module_function("_draw", function!(draw, -1))?;
112
+
113
+ // Register Frame class
114
+ let frame_class = m.define_class("Frame", ruby.class_object())?;
115
+ frame_class.define_method("area", method!(RubyFrame::area, 0))?;
116
+ frame_class.define_method("render_widget", method!(RubyFrame::render_widget, 2))?;
67
117
  m.define_module_function("_poll_event", function!(events::poll_event, 0))?;
68
118
  m.define_module_function("inject_test_event", function!(events::inject_test_event, 2))?;
69
119
  m.define_module_function("clear_events", function!(events::clear_events, 0))?;
@@ -81,19 +131,22 @@ fn init() -> Result<(), Error> {
81
131
  "get_cursor_position",
82
132
  function!(terminal::get_cursor_position, 0),
83
133
  )?;
84
- m.define_module_function(
85
- "_get_cell_at",
86
- function!(terminal::get_cell_at, 2),
87
- )?;
134
+ m.define_module_function("_get_cell_at", function!(terminal::get_cell_at, 2))?;
88
135
  m.define_module_function("resize_terminal", function!(terminal::resize_terminal, 2))?;
89
136
 
90
137
  // Register Layout.split on the Layout class
91
138
  let layout_class = m.const_get::<_, magnus::RClass>("Layout")?;
92
139
  layout_class.define_singleton_method("_split", function!(widgets::layout::split_layout, 4))?;
93
-
140
+
94
141
  // Paragraph metrics
95
- m.define_module_function("_paragraph_line_count", function!(widgets::paragraph::line_count, 2))?;
96
- m.define_module_function("_paragraph_line_width", function!(widgets::paragraph::line_width, 1))?;
142
+ m.define_module_function(
143
+ "_paragraph_line_count",
144
+ function!(widgets::paragraph::line_count, 2),
145
+ )?;
146
+ m.define_module_function(
147
+ "_paragraph_line_width",
148
+ function!(widgets::paragraph::line_width, 1),
149
+ )?;
97
150
 
98
151
  // Tabs metrics
99
152
  m.define_module_function("_tabs_width", function!(widgets::tabs::width, 1))?;
@@ -22,7 +22,8 @@ pub fn render_node(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Err
22
22
  if let Some(arr) = RArray::from_value(commands) {
23
23
  for i in 0..arr.len() {
24
24
  let ruby = magnus::Ruby::get().unwrap();
25
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
25
+ let index = isize::try_from(i)
26
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
26
27
  let cmd: Value = arr.entry(index)?;
27
28
  process_draw_command(frame.buffer_mut(), cmd)?;
28
29
  }
@@ -54,8 +55,10 @@ pub fn render_node(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Err
54
55
  "RatatuiRuby::Chart" | "RatatuiRuby::LineChart" => {
55
56
  widgets::chart::render(frame, area, node)?;
56
57
  }
57
- "RatatuiRuby::RatatuiLogo" => widgets::ratatui_logo::render(frame, area, node)?,
58
- "RatatuiRuby::RatatuiMascot" => widgets::ratatui_mascot::render_ratatui_mascot(frame, area, node)?,
58
+ "RatatuiRuby::RatatuiLogo" => widgets::ratatui_logo::render(frame, area, node),
59
+ "RatatuiRuby::RatatuiMascot" => {
60
+ widgets::ratatui_mascot::render_ratatui_mascot(frame, area, node)?;
61
+ }
59
62
  _ => {}
60
63
  }
61
64
  Ok(())
@@ -105,7 +108,8 @@ fn process_draw_command(buffer: &mut Buffer, cmd: Value) -> Result<(), Error> {
105
108
 
106
109
  if let Some(mods_array) = RArray::from_value(modifiers_val) {
107
110
  for i in 0..mods_array.len() {
108
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
111
+ let index = isize::try_from(i)
112
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
109
113
  let mod_str: String = mods_array.entry::<String>(index)?;
110
114
  if let Some(modifier) = parse_modifier_str(&mod_str) {
111
115
  style = style.add_modifier(modifier);