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
@@ -35,13 +35,15 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
35
35
  let mut data_storage: Vec<Vec<(f64, f64)>> = Vec::new();
36
36
 
37
37
  for i in 0..datasets_val.len() {
38
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
38
+ let index = isize::try_from(i)
39
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
39
40
  let ds_val: Value = datasets_val.entry(index)?;
40
41
  let data_array: magnus::RArray = ds_val.funcall("data", ())?;
41
42
 
42
43
  let mut points = Vec::new();
43
44
  for j in 0..data_array.len() {
44
- let index = isize::try_from(j).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
45
+ let index = isize::try_from(j)
46
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
45
47
  let point_array_val: Value = data_array.entry(index)?;
46
48
  let point_array = magnus::RArray::from_value(point_array_val).ok_or_else(|| {
47
49
  Error::new(ruby.exception_type_error(), "expected array for point")
@@ -54,7 +56,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
54
56
  }
55
57
 
56
58
  for (i, points) in data_storage.iter().enumerate() {
57
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
59
+ let index = isize::try_from(i)
60
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
58
61
  let ds_val: Value = datasets_val.entry(index)?;
59
62
  let name: String = ds_val.funcall("name", ())?;
60
63
  let marker_sym: Symbol = ds_val.funcall("marker", ())?;
@@ -112,7 +115,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
112
115
  }
113
116
 
114
117
  if !hidden_legend_constraints_val.is_nil() {
115
- let constraints_array: magnus::RArray = hidden_legend_constraints_val.funcall("to_a", ())?;
118
+ let constraints_array: magnus::RArray =
119
+ hidden_legend_constraints_val.funcall("to_a", ())?;
116
120
  if constraints_array.len() == 2 {
117
121
  let width_val: Value = constraints_array.entry(0)?;
118
122
  let height_val: Value = constraints_array.entry(1)?;
@@ -138,7 +142,8 @@ fn parse_axis(axis_val: Value) -> Result<Axis<'static>, Error> {
138
142
 
139
143
  let mut labels = Vec::new();
140
144
  for i in 0..labels_val.len() {
141
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
145
+ let index = isize::try_from(i)
146
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
142
147
  let label: String = labels_val.entry(index)?;
143
148
  labels.push(Span::from(label));
144
149
  }
@@ -175,13 +180,15 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
175
180
  let mut data_storage: Vec<Vec<(f64, f64)>> = Vec::new();
176
181
 
177
182
  for i in 0..datasets_val.len() {
178
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
183
+ let index = isize::try_from(i)
184
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
179
185
  let ds_val: Value = datasets_val.entry(index)?;
180
186
  let data_array: magnus::RArray = ds_val.funcall("data", ())?;
181
187
 
182
188
  let mut points = Vec::new();
183
189
  for j in 0..data_array.len() {
184
- let index = isize::try_from(j).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
190
+ let index = isize::try_from(j)
191
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
185
192
  let point_array_val: Value = data_array.entry(index)?;
186
193
  let point_array = magnus::RArray::from_value(point_array_val).ok_or_else(|| {
187
194
  Error::new(ruby.exception_type_error(), "expected array for point")
@@ -194,7 +201,8 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
194
201
  }
195
202
 
196
203
  for (i, points) in data_storage.iter().enumerate() {
197
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
204
+ let index = isize::try_from(i)
205
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
198
206
  let ds_val: Value = datasets_val.entry(index)?;
199
207
  let name: String = ds_val.funcall("name", ())?;
200
208
 
@@ -214,14 +222,16 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
214
222
 
215
223
  let mut x_labels = Vec::new();
216
224
  for i in 0..x_labels_val.len() {
217
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
225
+ let index = isize::try_from(i)
226
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
218
227
  let label: String = x_labels_val.entry(index)?;
219
228
  x_labels.push(Span::from(label));
220
229
  }
221
230
 
222
231
  let mut y_labels = Vec::new();
223
232
  for i in 0..y_labels_val.len() {
224
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
233
+ let index = isize::try_from(i)
234
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
225
235
  let label: String = y_labels_val.entry(index)?;
226
236
  y_labels.push(Span::from(label));
227
237
  }
@@ -42,7 +42,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
42
42
 
43
43
  if let Some(arr) = constraints_array {
44
44
  for i in 0..arr.len() {
45
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
45
+ let index = isize::try_from(i)
46
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
46
47
  let constraint_obj: Value = arr.entry(index)?;
47
48
  if let Ok(constraint) = parse_constraint(constraint_obj) {
48
49
  ratatui_constraints.push(constraint);
@@ -53,7 +54,9 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
53
54
  // If constraints don't match children, adjust or default
54
55
  if ratatui_constraints.len() != len {
55
56
  ratatui_constraints = (0..len)
56
- .map(|_| Constraint::Percentage(100 / u16::try_from(len).unwrap_or(u16::MAX).max(1)))
57
+ .map(|_| {
58
+ Constraint::Percentage(100 / u16::try_from(len).unwrap_or(u16::MAX).max(1))
59
+ })
57
60
  .collect();
58
61
  }
59
62
 
@@ -64,7 +67,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
64
67
  .split(area);
65
68
 
66
69
  for i in 0..len {
67
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
70
+ let index = isize::try_from(i)
71
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
68
72
  let child: Value = children_array.entry(index)?;
69
73
  if let Err(e) = render_node(frame, chunks[i], child) {
70
74
  eprintln!("Error rendering child {i}: {e:?}");
@@ -168,7 +172,8 @@ pub fn split_layout(
168
172
  // Parse constraints
169
173
  let mut ratatui_constraints = Vec::new();
170
174
  for i in 0..constraints.len() {
171
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
175
+ let index = isize::try_from(i)
176
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
172
177
  let constraint_obj: Value = constraints.entry(index)?;
173
178
  if let Ok(constraint) = parse_constraint(constraint_obj) {
174
179
  ratatui_constraints.push(constraint);
@@ -29,7 +29,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
29
29
 
30
30
  let mut items: Vec<String> = Vec::new();
31
31
  for i in 0..items_array.len() {
32
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
32
+ let index = isize::try_from(i)
33
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
33
34
  let item: String = items_array.entry(index)?;
34
35
  items.push(item);
35
36
  }
@@ -142,21 +143,41 @@ mod tests {
142
143
  fn test_repeat_highlight_symbol() {
143
144
  let items = vec!["Item 1", "Item 2"];
144
145
  let list_without_repeat = List::new(items.clone()).highlight_symbol(Line::from(">> "));
145
- let list_with_repeat = List::new(items).highlight_symbol(Line::from(">> ")).repeat_highlight_symbol(true);
146
-
146
+ let list_with_repeat = List::new(items)
147
+ .highlight_symbol(Line::from(">> "))
148
+ .repeat_highlight_symbol(true);
149
+
147
150
  let mut state = ListState::default();
148
151
  state.select(Some(0));
149
152
 
150
153
  let mut buf1 = Buffer::empty(Rect::new(0, 0, 10, 2));
151
154
  use ratatui::widgets::StatefulWidget;
152
- StatefulWidget::render(list_without_repeat, Rect::new(0, 0, 10, 2), &mut buf1, &mut state);
155
+ StatefulWidget::render(
156
+ list_without_repeat,
157
+ Rect::new(0, 0, 10, 2),
158
+ &mut buf1,
159
+ &mut state,
160
+ );
153
161
 
154
162
  let mut buf2 = Buffer::empty(Rect::new(0, 0, 10, 2));
155
- StatefulWidget::render(list_with_repeat, Rect::new(0, 0, 10, 2), &mut buf2, &mut state);
163
+ StatefulWidget::render(
164
+ list_with_repeat,
165
+ Rect::new(0, 0, 10, 2),
166
+ &mut buf2,
167
+ &mut state,
168
+ );
156
169
 
157
170
  // Both should render, but the behavior might differ based on content width
158
- let content1 = buf1.content().iter().map(|c| c.symbol()).collect::<String>();
159
- let content2 = buf2.content().iter().map(|c| c.symbol()).collect::<String>();
171
+ let content1 = buf1
172
+ .content()
173
+ .iter()
174
+ .map(|c| c.symbol())
175
+ .collect::<String>();
176
+ let content2 = buf2
177
+ .content()
178
+ .iter()
179
+ .map(|c| c.symbol())
180
+ .collect::<String>();
160
181
  assert!(!content1.is_empty());
161
182
  assert!(!content2.is_empty());
162
183
  }
@@ -164,8 +185,10 @@ mod tests {
164
185
  #[test]
165
186
  fn test_scroll_padding() {
166
187
  let items = vec!["Item 1", "Item 2", "Item 3", "Item 4"];
167
- let list = List::new(items).scroll_padding(1).highlight_symbol(Line::from(">> "));
168
-
188
+ let list = List::new(items)
189
+ .scroll_padding(1)
190
+ .highlight_symbol(Line::from(">> "));
191
+
169
192
  let mut state = ListState::default();
170
193
  state.select(Some(1));
171
194
 
@@ -12,7 +12,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
12
12
  .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for layers"))?;
13
13
 
14
14
  for i in 0..layers_array.len() {
15
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
15
+ let index = isize::try_from(i)
16
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
16
17
  let layer: Value = layers_array.entry(index)?;
17
18
  if let Err(e) = render_node(frame, area, layer) {
18
19
  eprintln!("Error rendering overlay layer {i}: {e:?}");
@@ -12,7 +12,7 @@ use ratatui::{
12
12
 
13
13
  use crate::text::parse_text;
14
14
 
15
- fn create_paragraph<'a>(node: Value, bump: &'a Bump) -> Result<Paragraph<'a>, Error> {
15
+ fn create_paragraph(node: Value, bump: &Bump) -> Result<Paragraph<'_>, Error> {
16
16
  let text_val: Value = node.funcall("text", ())?;
17
17
  let style_val: Value = node.funcall("style", ())?;
18
18
  let block_val: Value = node.funcall("block", ())?;
@@ -1,27 +1,38 @@
1
- use magnus::{Error, Value};
2
- use ratatui::{layout::Rect, widgets::{RatatuiLogo, RatatuiLogoSize}, Frame};
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ //
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
4
 
4
- pub fn render(frame: &mut Frame, area: Rect, _node: Value) -> Result<(), Error> {
5
+ use magnus::Value;
6
+ use ratatui::{
7
+ layout::Rect,
8
+ widgets::{RatatuiLogo, RatatuiLogoSize},
9
+ Frame,
10
+ };
11
+
12
+ pub fn render(frame: &mut Frame, area: Rect, _node: Value) {
5
13
  // RatatuiLogo does not support custom styling (it has fixed colors).
6
14
  // It requires a size argument.
7
15
  let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
8
16
  frame.render_widget(widget, area);
9
- Ok(())
10
17
  }
11
18
 
12
19
  #[cfg(test)]
13
20
  mod tests {
14
- use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
15
21
  use super::*;
22
+ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
16
23
 
17
24
  #[test]
18
25
  fn test_render() {
19
26
  let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
20
27
  let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
21
28
  widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
22
-
23
- let content = buffer.content().iter().map(|c| c.symbol()).collect::<String>();
24
-
29
+
30
+ let content = buffer
31
+ .content()
32
+ .iter()
33
+ .map(|c| c.symbol())
34
+ .collect::<String>();
35
+
25
36
  // The logo uses block characters for rendering
26
37
  assert!(content.contains('█'));
27
38
  assert!(!content.trim().is_empty());
@@ -1,11 +1,11 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ //
3
+ // SPDX-License-Identifier: AGPL-3.0-or-later
4
+
1
5
  use crate::style::parse_block;
2
6
  use bumpalo::Bump;
3
7
  use magnus::{prelude::*, Error, Value};
4
- use ratatui::{
5
- layout::Rect,
6
- widgets::RatatuiMascot,
7
- Frame,
8
- };
8
+ use ratatui::{layout::Rect, widgets::RatatuiMascot, Frame};
9
9
 
10
10
  pub fn render_ratatui_mascot(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
11
11
  let block_val: Value = node.funcall("block", ())?;
@@ -26,19 +26,26 @@ pub fn render_ratatui_mascot(frame: &mut Frame, area: Rect, node: Value) -> Resu
26
26
 
27
27
  #[cfg(test)]
28
28
  mod tests {
29
- use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
30
29
  use super::*;
30
+ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
31
31
 
32
32
  #[test]
33
33
  fn test_render() {
34
34
  let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
35
35
  let widget = RatatuiMascot::new();
36
36
  widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
37
-
38
- let content = buffer.content().iter().map(|c| c.symbol()).collect::<String>();
39
-
37
+
38
+ let content = buffer
39
+ .content()
40
+ .iter()
41
+ .map(|c| c.symbol())
42
+ .collect::<String>();
43
+
40
44
  // The mascot uses block drawing characters
41
- assert!(content.contains("█"), "Mascot rendering should contain block characters");
45
+ assert!(
46
+ content.contains("█"),
47
+ "Mascot rendering should contain block characters"
48
+ );
42
49
  assert!(!content.trim().is_empty());
43
50
  }
44
51
  }
@@ -14,7 +14,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
14
14
  let content_length: usize = node.funcall("content_length", ())?;
15
15
  let position: usize = node.funcall("position", ())?;
16
16
  let orientation_sym: Symbol = node.funcall("orientation", ())?;
17
-
17
+
18
18
  let thumb_symbol_val: Value = node.funcall("thumb_symbol", ())?;
19
19
  let thumb_style_val: Value = node.funcall("thumb_style", ())?;
20
20
  let track_symbol_val: Value = node.funcall("track_symbol", ())?;
@@ -32,7 +32,9 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
32
32
 
33
33
  scrollbar = match orientation_sym.to_string().as_str() {
34
34
  "vertical_left" => scrollbar.orientation(ScrollbarOrientation::VerticalLeft),
35
- "horizontal_bottom" | "horizontal" => scrollbar.orientation(ScrollbarOrientation::HorizontalBottom),
35
+ "horizontal_bottom" | "horizontal" => {
36
+ scrollbar.orientation(ScrollbarOrientation::HorizontalBottom)
37
+ }
36
38
  "horizontal_top" => scrollbar.orientation(ScrollbarOrientation::HorizontalTop),
37
39
  _ => scrollbar.orientation(ScrollbarOrientation::VerticalRight),
38
40
  };
@@ -1,10 +1,10 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
2
  // SPDX-License-Identifier: AGPL-3.0-or-later
3
3
 
4
- use crate::style::{parse_block, parse_style, parse_bar_set};
4
+ use crate::style::{parse_bar_set, parse_block, parse_style};
5
5
  use bumpalo::Bump;
6
6
  use magnus::{prelude::*, Error, RString, Value};
7
- use ratatui::{layout::Rect, widgets::Sparkline, widgets::RenderDirection, Frame};
7
+ use ratatui::{layout::Rect, widgets::RenderDirection, widgets::Sparkline, Frame};
8
8
 
9
9
  pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
10
10
  let bump = Bump::new();
@@ -20,7 +20,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
20
20
 
21
21
  let mut data_vec = Vec::new();
22
22
  for i in 0..data_val.len() {
23
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
23
+ let index = isize::try_from(i)
24
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
24
25
  let val: Value = data_val.entry(index)?;
25
26
  if val.is_nil() {
26
27
  data_vec.push(None);
@@ -100,22 +101,24 @@ mod tests {
100
101
  fn test_sparkline_absent_value_symbol() {
101
102
  // Data with absent (None) and present values: [Some(5), None, Some(8), None]
102
103
  let data = vec![Some(5), None, Some(8), None];
103
- let sparkline = Sparkline::default()
104
- .data(&data)
105
- .absent_value_symbol("-");
104
+ let sparkline = Sparkline::default().data(&data).absent_value_symbol("-");
106
105
  let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
107
106
  sparkline.render(Rect::new(0, 0, 4, 1), &mut buf);
108
-
107
+
109
108
  // Collect all rendered symbols
110
109
  let symbols: Vec<&str> = buf.content().iter().map(|c| c.symbol()).collect();
111
-
110
+
112
111
  // Check that we have 4 cells rendered
113
- assert_eq!(symbols.len(), 4, "Should have 4 cells rendered for 4 data points");
114
-
112
+ assert_eq!(
113
+ symbols.len(),
114
+ 4,
115
+ "Should have 4 cells rendered for 4 data points"
116
+ );
117
+
115
118
  // Absent values (None) should render as "-"
116
119
  assert_eq!(symbols[1], "-", "Second value (None) should render as dash");
117
120
  assert_eq!(symbols[3], "-", "Fourth value (None) should render as dash");
118
-
121
+
119
122
  // Present values should not be dashes
120
123
  assert_ne!(symbols[0], "-", "First value (Some(5)) should not be dash");
121
124
  assert_ne!(symbols[2], "-", "Third value (Some(8)) should not be dash");
@@ -33,7 +33,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
33
33
 
34
34
  let mut rows = Vec::new();
35
35
  for i in 0..rows_array.len() {
36
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
36
+ let index = isize::try_from(i)
37
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
37
38
  let row_val: Value = rows_array.entry(index)?;
38
39
  rows.push(parse_row(row_val)?);
39
40
  }
@@ -120,7 +121,8 @@ fn parse_row(row_val: Value) -> Result<Row<'static>, Error> {
120
121
 
121
122
  let mut cells = Vec::new();
122
123
  for i in 0..row_array.len() {
123
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
124
+ let index = isize::try_from(i)
125
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
124
126
  let cell_val: Value = row_array.entry(index)?;
125
127
  cells.push(parse_cell(cell_val)?);
126
128
  }
@@ -159,7 +161,8 @@ fn parse_cell(cell_val: Value) -> Result<Cell<'static>, Error> {
159
161
  if let Some(mods_array) = magnus::RArray::from_value(modifiers_val) {
160
162
  let ruby = magnus::Ruby::get().unwrap();
161
163
  for i in 0..mods_array.len() {
162
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
164
+ let index = isize::try_from(i)
165
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
163
166
  let mod_str: String = mods_array.entry::<String>(index)?;
164
167
  if let Some(modifier) = crate::style::parse_modifier_str(&mod_str) {
165
168
  style = style.add_modifier(modifier);
@@ -177,7 +180,8 @@ fn parse_constraints(widths_array: magnus::RArray) -> Result<Vec<Constraint>, Er
177
180
  let ruby = magnus::Ruby::get().unwrap();
178
181
  let mut constraints = Vec::new();
179
182
  for i in 0..widths_array.len() {
180
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
183
+ let index = isize::try_from(i)
184
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
181
185
  let constraint_obj: Value = widths_array.entry(index)?;
182
186
  let type_sym: Symbol = constraint_obj.funcall("type", ())?;
183
187
  let value_obj: Value = constraint_obj.funcall("value", ())?;
@@ -15,7 +15,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
15
15
 
16
16
  use crate::text::parse_line;
17
17
 
18
- fn create_tabs<'a>(node: Value, bump: &'a Bump) -> Result<Tabs<'a>, Error> {
18
+ fn create_tabs(node: Value, bump: &Bump) -> Result<Tabs<'_>, Error> {
19
19
  let ruby = magnus::Ruby::get().unwrap();
20
20
  let titles_val: Value = node.funcall("titles", ())?;
21
21
  let selected_index: usize = node.funcall("selected_index", ())?;
@@ -30,7 +30,8 @@ fn create_tabs<'a>(node: Value, bump: &'a Bump) -> Result<Tabs<'a>, Error> {
30
30
 
31
31
  let mut titles = Vec::new();
32
32
  for i in 0..titles_array.len() {
33
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
33
+ let index = isize::try_from(i)
34
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
34
35
  let val: Value = titles_array.entry(index)?;
35
36
  if let Ok(line) = parse_line(val) {
36
37
  titles.push(line);
@@ -81,10 +82,11 @@ pub fn width(node: Value) -> Result<usize, Error> {
81
82
  .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for titles"))?;
82
83
 
83
84
  let mut total_width = padding_left + padding_right;
84
-
85
+
85
86
  let mut titles_count = 0;
86
87
  for i in 0..titles_array.len() {
87
- let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
88
+ let index = isize::try_from(i)
89
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
88
90
  let val: Value = titles_array.entry(index)?;
89
91
  let line_width = if let Ok(line) = parse_line(val) {
90
92
  line.width()
@@ -97,11 +99,11 @@ pub fn width(node: Value) -> Result<usize, Error> {
97
99
  }
98
100
 
99
101
  if titles_count > 1 {
100
- let divider_width = if !divider_val.is_nil() {
102
+ let divider_width = if divider_val.is_nil() {
103
+ 1 // Default divider is "|"
104
+ } else {
101
105
  let d: String = divider_val.funcall("to_s", ())?;
102
106
  ratatui::text::Span::raw(d).width()
103
- } else {
104
- 1 // Default divider is "|"
105
107
  };
106
108
  total_width += (titles_count - 1) * divider_width;
107
109
  }
@@ -134,10 +136,8 @@ mod tests {
134
136
  fn test_tabs_highlight_style() {
135
137
  let titles = vec![Line::from("Tab1"), Line::from("Tab2")];
136
138
  let highlight_style = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
137
- let tabs = Tabs::new(titles)
138
- .select(0)
139
- .highlight_style(highlight_style);
140
-
139
+ let tabs = Tabs::new(titles).select(0).highlight_style(highlight_style);
140
+
141
141
  let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
142
142
  tabs.render(Rect::new(0, 0, 15, 1), &mut buf);
143
143
 
@@ -67,7 +67,7 @@ module RatatuiRuby
67
67
  # Cell.symbol("X") # => #<RatatuiRuby::Cell symbol="X">
68
68
  #
69
69
  def self.symbol(symbol)
70
- new(symbol: symbol, fg: nil, bg: nil, modifiers: [])
70
+ new(symbol:, fg: nil, bg: nil, modifiers: [])
71
71
  end
72
72
 
73
73
  # Alias for Rubyists who prefer a shorter name.
@@ -149,7 +149,7 @@ module RatatuiRuby
149
149
  parts << "fg=#{fg.inspect}" if fg
150
150
  parts << "bg=#{bg.inspect}" if bg
151
151
  parts << "modifiers=#{modifiers.inspect}" unless modifiers.empty?
152
- "#<#{self.class} #{parts.join(" ")}>"
152
+ "#<#{self.class} #{parts.join(' ')}>"
153
153
  end
154
154
 
155
155
  # Returns the cell's character.
@@ -160,7 +160,7 @@ module RatatuiRuby
160
160
  # Support for pattern matching.
161
161
  # Supports both <tt>:symbol</tt> and <tt>:char</tt> keys.
162
162
  def deconstruct_keys(keys)
163
- { symbol: symbol, char: symbol, fg: fg, bg: bg, modifiers: modifiers }
163
+ { symbol:, char: symbol, fg:, bg:, modifiers: }
164
164
  end
165
165
  end
166
166
  end
@@ -108,7 +108,7 @@ module RatatuiRuby
108
108
  if mods.empty?
109
109
  @code.to_sym
110
110
  else
111
- "#{mods}_#{@code}".to_sym
111
+ :"#{mods}_#{@code}"
112
112
  end
113
113
  end
114
114
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ class Event
8
+ # {Null object}[https://en.wikipedia.org/wiki/Null_object_pattern] for absent events.
9
+ #
10
+ # Event loops poll for input 60 times per second. Usually nothing is happening.
11
+ # If <tt>RatatuiRuby.poll_event</tt> returned <tt>nil</tt>, you would need
12
+ # nil-checks: <tt>event&.key?</tt>, <tt>next unless event</tt>.
13
+ #
14
+ # This class eliminates that friction. It responds to every predicate with
15
+ # <tt>false</tt>. Call <tt>none?</tt> to detect it explicitly. Pattern-match on
16
+ # <tt>type: :none</tt> for exhaustive dispatch.
17
+ #
18
+ # Use it to simplify your event loop. No guards. Optional `else` clauses.
19
+ #
20
+ # See Martin Fowler's {Special Case}[https://martinfowler.com/eaaCatalog/specialCase.html] pattern.
21
+ #
22
+ # === Predicate Example
23
+ #
24
+ # event = RatatuiRuby.poll_event
25
+ # break if event.ctrl_c?
26
+ # redraw if event.none?
27
+ #
28
+ # === Pattern Matching Example
29
+ #
30
+ # redraw if RatatuiRuby.poll_event in type: :none
31
+ class None < Event
32
+ # Returns true for None events.
33
+ def none?
34
+ true
35
+ end
36
+
37
+ # Deconstructs the event for pattern matching.
38
+ def deconstruct_keys(keys)
39
+ { type: :none }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -6,11 +6,63 @@
6
6
  module RatatuiRuby
7
7
  # Base class for all RatatuiRuby events.
8
8
  #
9
- # Events are returned by RatatuiRuby.poll_event.
10
- # All events support Ruby 3.0+ pattern matching via #deconstruct_keys.
9
+ # Events represent terminal input: keyboard, mouse, resize, paste, focus changes.
10
+ # Returned by RatatuiRuby.poll_event. All events support Ruby 3.0+ pattern matching.
11
11
  #
12
- # See RatatuiRuby.poll_event
12
+ # == Event Types
13
+ #
14
+ # * <tt>Key</tt> — keyboard input
15
+ # * <tt>Mouse</tt> — mouse clicks, movement, wheel
16
+ # * <tt>Resize</tt> — terminal resized
17
+ # * <tt>Paste</tt> — clipboard paste
18
+ # * <tt>FocusGained</tt> — terminal gained focus
19
+ # * <tt>FocusLost</tt> — terminal lost focus
20
+ # * <tt>None</tt> — no event available (Null Object)
21
+ #
22
+ # == Pattern Matching (Exhaustive)
23
+ #
24
+ # Use <tt>case...in</tt> to dispatch on every possible event type. This ensures
25
+ # you handle every case without needing an +else+ clause:
26
+ #
27
+ # case RatatuiRuby.poll_event
28
+ # in { type: :key, code: "q" }
29
+ # break
30
+ # in { type: :key, code: code, modifiers: }
31
+ # handle_key(code, modifiers)
32
+ # in { type: :mouse, kind: "down", x:, y: }
33
+ # handle_click(x, y)
34
+ # in { type: :mouse, kind:, x:, y: }
35
+ # # handle other mouse activities
36
+ # in { type: :resize, width:, height: }
37
+ # handle_resize(width, height)
38
+ # in { type: :paste, content: }
39
+ # handle_paste(content)
40
+ # in { type: :focus_gained }
41
+ # handle_focus_gain
42
+ # in { type: :focus_lost }
43
+ # handle_focus_loss
44
+ # in { type: :none }
45
+ # # Idle
46
+ # end
47
+ #
48
+ # == Predicates
49
+ #
50
+ # Check event types with predicates without pattern matching:
51
+ #
52
+ # event = RatatuiRuby.poll_event
53
+ # if event.key?
54
+ # puts "Key pressed"
55
+ # elsif event.none?
56
+ # # Idle
57
+ # elsif event.mouse?
58
+ # puts "Mouse event"
59
+ # end
13
60
  class Event
61
+ # Returns true if this is a None event.
62
+ def none?
63
+ false
64
+ end
65
+
14
66
  # Returns true if this is a Key event.
15
67
  def key?
16
68
  false
@@ -67,10 +119,10 @@ module RatatuiRuby
67
119
  def deconstruct_keys(keys)
68
120
  {}
69
121
  end
70
-
71
122
  end
72
123
  end
73
124
 
125
+ require_relative "event/none"
74
126
  require_relative "event/key"
75
127
  require_relative "event/mouse"
76
128
  require_relative "event/resize"