ratatui_ruby 0.3.1 → 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 (350) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +14 -12
  3. data/.builds/ruby-3.3.yml +14 -12
  4. data/.builds/ruby-3.4.yml +14 -12
  5. data/.builds/ruby-4.0.0.yml +14 -12
  6. data/AGENTS.md +89 -132
  7. data/CHANGELOG.md +223 -1
  8. data/README.md +23 -16
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +176 -0
  11. data/doc/application_testing.md +17 -10
  12. data/doc/contributors/design/ruby_frontend.md +11 -7
  13. data/doc/contributors/developing_examples.md +261 -0
  14. data/doc/contributors/documentation_style.md +104 -0
  15. data/doc/contributors/dwim_dx.md +366 -0
  16. data/doc/contributors/index.md +2 -0
  17. data/doc/custom.css +14 -0
  18. data/doc/event_handling.md +125 -0
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_analytics.png +0 -0
  21. data/doc/images/app_color_picker.png +0 -0
  22. data/doc/images/app_custom_widget.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_map_demo.png +0 -0
  25. data/doc/images/app_mouse_events.png +0 -0
  26. data/doc/images/app_table_select.png +0 -0
  27. data/doc/images/verify_quickstart_dsl.png +0 -0
  28. data/doc/images/verify_quickstart_layout.png +0 -0
  29. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  30. data/doc/images/verify_readme_usage.png +0 -0
  31. data/doc/images/widget_barchart_demo.png +0 -0
  32. data/doc/images/widget_block_padding.png +0 -0
  33. data/doc/images/widget_block_titles.png +0 -0
  34. data/doc/images/widget_box_demo.png +0 -0
  35. data/doc/images/widget_calendar_demo.png +0 -0
  36. data/doc/images/widget_cell_demo.png +0 -0
  37. data/doc/images/widget_chart_demo.png +0 -0
  38. data/doc/images/widget_gauge_demo.png +0 -0
  39. data/doc/images/widget_layout_split.png +0 -0
  40. data/doc/images/widget_line_gauge_demo.png +0 -0
  41. data/doc/images/widget_list_demo.png +0 -0
  42. data/doc/images/widget_list_styles.png +0 -0
  43. data/doc/images/widget_popup_demo.png +0 -0
  44. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  45. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  46. data/doc/images/widget_rect.png +0 -0
  47. data/doc/images/widget_render.png +0 -0
  48. data/doc/images/widget_rich_text.png +0 -0
  49. data/doc/images/widget_scroll_text.png +0 -0
  50. data/doc/images/widget_scrollbar_demo.png +0 -0
  51. data/doc/images/widget_sparkline_demo.png +0 -0
  52. data/doc/images/widget_style_colors.png +0 -0
  53. data/doc/images/widget_table_flex.png +0 -0
  54. data/doc/images/widget_tabs_demo.png +0 -0
  55. data/doc/index.md +1 -0
  56. data/doc/interactive_design.md +116 -0
  57. data/doc/quickstart.md +186 -84
  58. data/examples/app_all_events/README.md +81 -0
  59. data/examples/app_all_events/app.rb +93 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +75 -0
  62. data/examples/app_all_events/model/events.rb +180 -0
  63. data/examples/app_all_events/model/highlight.rb +57 -0
  64. data/examples/app_all_events/model/timestamp.rb +54 -0
  65. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  66. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  67. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  68. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  69. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  70. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  71. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  72. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  73. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  74. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  75. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  76. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  77. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  78. data/examples/app_all_events/view/app_view.rb +78 -0
  79. data/examples/app_all_events/view/controls_view.rb +50 -0
  80. data/examples/app_all_events/view/counts_view.rb +55 -0
  81. data/examples/app_all_events/view/live_view.rb +69 -0
  82. data/examples/app_all_events/view/log_view.rb +60 -0
  83. data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
  84. data/examples/app_all_events/view_state.rb +42 -0
  85. data/examples/app_color_picker/README.md +94 -0
  86. data/examples/app_color_picker/app.rb +112 -0
  87. data/examples/app_color_picker/clipboard.rb +84 -0
  88. data/examples/app_color_picker/color.rb +191 -0
  89. data/examples/app_color_picker/copy_dialog.rb +170 -0
  90. data/examples/app_color_picker/harmony.rb +56 -0
  91. data/examples/app_color_picker/input.rb +142 -0
  92. data/examples/app_color_picker/palette.rb +80 -0
  93. data/examples/app_color_picker/scene.rb +201 -0
  94. data/examples/app_login_form/app.rb +108 -0
  95. data/examples/app_map_demo/app.rb +93 -0
  96. data/examples/app_table_select/app.rb +201 -0
  97. data/examples/verify_quickstart_dsl/app.rb +45 -0
  98. data/examples/verify_quickstart_layout/app.rb +69 -0
  99. data/examples/verify_quickstart_lifecycle/app.rb +48 -0
  100. data/examples/verify_readme_usage/app.rb +34 -0
  101. data/examples/widget_barchart_demo/app.rb +238 -0
  102. data/examples/widget_block_padding/app.rb +67 -0
  103. data/examples/widget_block_titles/app.rb +69 -0
  104. data/examples/widget_box_demo/app.rb +250 -0
  105. data/examples/widget_calendar_demo/app.rb +109 -0
  106. data/examples/widget_cell_demo/app.rb +104 -0
  107. data/examples/widget_chart_demo/app.rb +213 -0
  108. data/examples/widget_gauge_demo/app.rb +212 -0
  109. data/examples/widget_layout_split/app.rb +246 -0
  110. data/examples/widget_line_gauge_demo/app.rb +217 -0
  111. data/examples/widget_list_demo/app.rb +382 -0
  112. data/examples/widget_list_styles/app.rb +141 -0
  113. data/examples/widget_popup_demo/app.rb +104 -0
  114. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  115. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  116. data/examples/widget_rect/app.rb +205 -0
  117. data/examples/widget_render/app.rb +184 -0
  118. data/examples/widget_rich_text/app.rb +137 -0
  119. data/examples/widget_scroll_text/app.rb +108 -0
  120. data/examples/widget_scrollbar_demo/app.rb +153 -0
  121. data/examples/widget_sparkline_demo/app.rb +274 -0
  122. data/examples/widget_style_colors/app.rb +102 -0
  123. data/examples/widget_table_flex/app.rb +95 -0
  124. data/examples/widget_tabs_demo/app.rb +167 -0
  125. data/ext/ratatui_ruby/Cargo.lock +889 -115
  126. data/ext/ratatui_ruby/Cargo.toml +4 -3
  127. data/ext/ratatui_ruby/clippy.toml +7 -0
  128. data/ext/ratatui_ruby/extconf.rb +7 -0
  129. data/ext/ratatui_ruby/src/events.rs +293 -219
  130. data/ext/ratatui_ruby/src/frame.rs +115 -0
  131. data/ext/ratatui_ruby/src/lib.rs +105 -24
  132. data/ext/ratatui_ruby/src/rendering.rs +94 -10
  133. data/ext/ratatui_ruby/src/style.rs +357 -93
  134. data/ext/ratatui_ruby/src/terminal.rs +121 -31
  135. data/ext/ratatui_ruby/src/text.rs +178 -0
  136. data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
  137. data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
  138. data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
  139. data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
  140. data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
  141. data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
  142. data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
  143. data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
  144. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
  145. data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
  146. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  147. data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
  148. data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
  149. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  150. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
  151. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
  152. data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
  153. data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
  154. data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
  155. data/lib/ratatui_ruby/cell.rb +166 -0
  156. data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
  157. data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
  158. data/lib/ratatui_ruby/event/key.rb +211 -0
  159. data/lib/ratatui_ruby/event/mouse.rb +124 -0
  160. data/lib/ratatui_ruby/event/none.rb +43 -0
  161. data/lib/ratatui_ruby/event/paste.rb +71 -0
  162. data/lib/ratatui_ruby/event/resize.rb +80 -0
  163. data/lib/ratatui_ruby/event.rb +131 -0
  164. data/lib/ratatui_ruby/frame.rb +87 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
  166. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
  167. data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
  168. data/lib/ratatui_ruby/schema/block.rb +178 -11
  169. data/lib/ratatui_ruby/schema/calendar.rb +70 -14
  170. data/lib/ratatui_ruby/schema/canvas.rb +213 -46
  171. data/lib/ratatui_ruby/schema/center.rb +46 -8
  172. data/lib/ratatui_ruby/schema/chart.rb +134 -32
  173. data/lib/ratatui_ruby/schema/clear.rb +22 -53
  174. data/lib/ratatui_ruby/schema/constraint.rb +72 -12
  175. data/lib/ratatui_ruby/schema/cursor.rb +23 -5
  176. data/lib/ratatui_ruby/schema/draw.rb +53 -0
  177. data/lib/ratatui_ruby/schema/gauge.rb +56 -12
  178. data/lib/ratatui_ruby/schema/layout.rb +91 -9
  179. data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
  180. data/lib/ratatui_ruby/schema/list.rb +92 -16
  181. data/lib/ratatui_ruby/schema/overlay.rb +29 -3
  182. data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
  183. data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
  184. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
  185. data/lib/ratatui_ruby/schema/rect.rb +59 -10
  186. data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
  187. data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
  188. data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
  189. data/lib/ratatui_ruby/schema/style.rb +39 -11
  190. data/lib/ratatui_ruby/schema/table.rb +109 -18
  191. data/lib/ratatui_ruby/schema/tabs.rb +71 -10
  192. data/lib/ratatui_ruby/schema/text.rb +90 -0
  193. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  194. data/lib/ratatui_ruby/session.rb +163 -0
  195. data/lib/ratatui_ruby/test_helper.rb +322 -13
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby.rb +184 -38
  198. data/sig/examples/app_all_events/app.rbs +11 -0
  199. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  200. data/sig/examples/app_all_events/model/events.rbs +15 -0
  201. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  202. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  203. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  204. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  205. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  206. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  207. data/sig/examples/app_all_events/view.rbs +8 -0
  208. data/sig/examples/app_all_events/view_state.rbs +15 -0
  209. data/sig/examples/app_color_picker/app.rbs +12 -0
  210. data/sig/examples/app_login_form/app.rbs +11 -0
  211. data/sig/examples/app_map_demo/app.rbs +11 -0
  212. data/sig/examples/app_table_select/app.rbs +11 -0
  213. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  214. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  215. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  216. data/sig/examples/widget_block_padding/app.rbs +11 -0
  217. data/sig/examples/widget_block_titles/app.rbs +11 -0
  218. data/sig/examples/widget_box_demo/app.rbs +11 -0
  219. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  220. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  221. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  222. data/sig/examples/widget_gauge_demo/app.rbs +11 -0
  223. data/sig/examples/widget_layout_split/app.rbs +10 -0
  224. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  225. data/sig/examples/widget_list_demo/app.rbs +12 -0
  226. data/sig/examples/widget_list_styles/app.rbs +11 -0
  227. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  228. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  229. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  230. data/sig/examples/widget_rect/app.rbs +12 -0
  231. data/sig/examples/widget_render/app.rbs +10 -0
  232. data/sig/examples/widget_rich_text/app.rbs +11 -0
  233. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  234. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  235. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  236. data/sig/examples/widget_style_colors/app.rbs +14 -0
  237. data/sig/examples/widget_table_flex/app.rbs +11 -0
  238. data/sig/ratatui_ruby/event.rbs +69 -0
  239. data/sig/ratatui_ruby/frame.rbs +9 -0
  240. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
  244. data/sig/ratatui_ruby/schema/block.rbs +5 -4
  245. data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
  246. data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
  247. data/sig/ratatui_ruby/schema/center.rbs +3 -3
  248. data/sig/ratatui_ruby/schema/chart.rbs +8 -5
  249. data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
  250. data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
  251. data/sig/ratatui_ruby/schema/draw.rbs +27 -0
  252. data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
  253. data/sig/ratatui_ruby/schema/layout.rbs +11 -1
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
  255. data/sig/ratatui_ruby/schema/list.rbs +5 -1
  256. data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
  257. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
  258. data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
  259. data/sig/ratatui_ruby/schema/rect.rbs +2 -1
  260. data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
  261. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
  262. data/sig/ratatui_ruby/schema/table.rbs +8 -1
  263. data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
  264. data/sig/ratatui_ruby/schema/text.rbs +22 -0
  265. data/sig/ratatui_ruby/session.rbs +94 -0
  266. data/tasks/autodoc/inventory.rb +61 -0
  267. data/tasks/autodoc/member.rb +56 -0
  268. data/tasks/autodoc/name.rb +19 -0
  269. data/tasks/autodoc/notice.rb +26 -0
  270. data/tasks/autodoc/rbs.rb +38 -0
  271. data/tasks/autodoc/rdoc.rb +45 -0
  272. data/tasks/autodoc.rake +47 -0
  273. data/tasks/bump/history.rb +2 -2
  274. data/tasks/doc.rake +600 -6
  275. data/tasks/example_viewer.html.erb +172 -0
  276. data/tasks/lint.rake +8 -4
  277. data/tasks/resources/build.yml.erb +13 -11
  278. data/tasks/resources/index.html.erb +6 -0
  279. data/tasks/sourcehut.rake +4 -4
  280. data/tasks/terminal_preview/app_screenshot.rb +33 -0
  281. data/tasks/terminal_preview/crash_report.rb +52 -0
  282. data/tasks/terminal_preview/example_app.rb +25 -0
  283. data/tasks/terminal_preview/launcher_script.rb +46 -0
  284. data/tasks/terminal_preview/preview_collection.rb +58 -0
  285. data/tasks/terminal_preview/preview_timing.rb +22 -0
  286. data/tasks/terminal_preview/safety_confirmation.rb +56 -0
  287. data/tasks/terminal_preview/saved_screenshot.rb +53 -0
  288. data/tasks/terminal_preview/system_appearance.rb +11 -0
  289. data/tasks/terminal_preview/terminal_window.rb +136 -0
  290. data/tasks/terminal_preview/window_id.rb +14 -0
  291. data/tasks/terminal_preview.rake +28 -0
  292. data/tasks/test.rake +2 -2
  293. data/tasks/website/index_page.rb +3 -3
  294. data/tasks/website/version.rb +10 -10
  295. data/tasks/website/version_menu.rb +10 -12
  296. data/tasks/website/versioned_documentation.rb +49 -17
  297. data/tasks/website/website.rb +6 -8
  298. data/tasks/website.rake +4 -4
  299. metadata +206 -54
  300. data/LICENSES/BSD-2-Clause.txt +0 -9
  301. data/doc/images/examples-analytics.rb.png +0 -0
  302. data/doc/images/examples-box_demo.rb.png +0 -0
  303. data/doc/images/examples-calendar_demo.rb.png +0 -0
  304. data/doc/images/examples-chart_demo.rb.png +0 -0
  305. data/doc/images/examples-custom_widget.rb.png +0 -0
  306. data/doc/images/examples-dashboard.rb.png +0 -0
  307. data/doc/images/examples-list_styles.rb.png +0 -0
  308. data/doc/images/examples-login_form.rb.png +0 -0
  309. data/doc/images/examples-map_demo.rb.png +0 -0
  310. data/doc/images/examples-mouse_events.rb.png +0 -0
  311. data/doc/images/examples-popup_demo.rb.gif +0 -0
  312. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  313. data/doc/images/examples-scroll_text.rb.png +0 -0
  314. data/doc/images/examples-scrollbar_demo.rb.png +0 -0
  315. data/doc/images/examples-stock_ticker.rb.png +0 -0
  316. data/doc/images/examples-system_monitor.rb.png +0 -0
  317. data/doc/images/examples-table_select.rb.png +0 -0
  318. data/examples/analytics.rb +0 -88
  319. data/examples/box_demo.rb +0 -71
  320. data/examples/calendar_demo.rb +0 -55
  321. data/examples/chart_demo.rb +0 -84
  322. data/examples/custom_widget.rb +0 -43
  323. data/examples/dashboard.rb +0 -72
  324. data/examples/list_styles.rb +0 -66
  325. data/examples/login_form.rb +0 -115
  326. data/examples/map_demo.rb +0 -58
  327. data/examples/mouse_events.rb +0 -95
  328. data/examples/popup_demo.rb +0 -105
  329. data/examples/quickstart_dsl.rb +0 -30
  330. data/examples/quickstart_lifecycle.rb +0 -40
  331. data/examples/readme_usage.rb +0 -21
  332. data/examples/scroll_text.rb +0 -74
  333. data/examples/scrollbar_demo.rb +0 -75
  334. data/examples/stock_ticker.rb +0 -93
  335. data/examples/system_monitor.rb +0 -94
  336. data/examples/table_select.rb +0 -70
  337. data/examples/test_analytics.rb +0 -65
  338. data/examples/test_box_demo.rb +0 -38
  339. data/examples/test_calendar_demo.rb +0 -66
  340. data/examples/test_dashboard.rb +0 -38
  341. data/examples/test_list_styles.rb +0 -61
  342. data/examples/test_login_form.rb +0 -63
  343. data/examples/test_map_demo.rb +0 -100
  344. data/examples/test_popup_demo.rb +0 -62
  345. data/examples/test_scroll_text.rb +0 -130
  346. data/examples/test_stock_ticker.rb +0 -39
  347. data/examples/test_system_monitor.rb +0 -40
  348. data/examples/test_table_select.rb +0 -37
  349. data/ext/ratatui_ruby/src/buffer.rs +0 -54
  350. data/lib/ratatui_ruby/dsl.rb +0 -64
@@ -0,0 +1,172 @@
1
+ <%#
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: AGPL-3.0-or-later
4
+ %>
5
+ <!DOCTYPE html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1">
10
+ <title><%= page_title %> - Example Viewer</title>
11
+
12
+ <script>
13
+ var rdoc_rel_prefix = "<%= doc_root_link.sub('index.html', '') %>";
14
+ var index_rel_prefix = "<%= '../' * (relative_path.split('/').size - 1) %>";
15
+ </script>
16
+
17
+ <script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_navigation.js" defer></script>
18
+ <script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_data.js" defer></script>
19
+ <script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_ranker.js" defer></script>
20
+ <script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_controller.js" defer></script>
21
+ <script src="<%= '../' * (relative_path.split('/').size - 1) %>js/aliki.js" defer></script>
22
+
23
+ <link href="<%= doc_root_link.sub('index.html', '') %>css/rdoc.css" rel="stylesheet">
24
+ <link href="<%= doc_root_link.sub('index.html', '') %>custom.css" rel="stylesheet">
25
+ </head>
26
+ <body class="file<%= ' has-toc' unless toc_items.empty? %>">
27
+ <%= icons_svg %>
28
+ <header class="top-navbar">
29
+ <div class="navbar-brand">
30
+ Example Viewer
31
+ </div>
32
+
33
+ <!-- Desktop search bar -->
34
+ <div class="navbar-search navbar-search-desktop" role="search">
35
+ <form action="#" method="get" accept-charset="utf-8">
36
+ <input id="search-field" role="combobox" aria-label="Search"
37
+ aria-autocomplete="list" aria-controls="search-results-desktop"
38
+ type="text" name="search" placeholder="Search (/) examples..."
39
+ spellcheck="false" autocomplete="off"
40
+ title="Type to search, Up and Down to navigate, Enter to load">
41
+ <ul id="search-results-desktop" aria-label="Search Results"
42
+ aria-busy="false" aria-expanded="false"
43
+ aria-atomic="false" class="initially-hidden search-results"></ul>
44
+ </form>
45
+ </div>
46
+
47
+ <!-- Mobile search icon button -->
48
+ <button id="search-toggle" class="navbar-search-mobile" aria-label="Open search" type="button">
49
+ <span aria-hidden="true">🔍</span>
50
+ </button>
51
+
52
+ <button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
53
+ <span class="theme-toggle-icon" aria-hidden="true">🌙</span>
54
+ </button>
55
+ </header>
56
+
57
+ <!-- Search Modal (Mobile) -->
58
+ <div id="search-modal" class="search-modal" hidden aria-modal="true" role="dialog" aria-label="Search">
59
+ <div class="search-modal-backdrop"></div>
60
+ <div class="search-modal-content">
61
+ <div class="search-modal-header">
62
+ <form class="search-modal-form" action="#" method="get" accept-charset="utf-8">
63
+ <span class="search-modal-icon" aria-hidden="true">🔍</span>
64
+ <input id="search-field-mobile" role="combobox" aria-label="Search"
65
+ aria-autocomplete="list" aria-controls="search-results-mobile"
66
+ type="text" name="search" placeholder="Search examples"
67
+ spellcheck="false" autocomplete="off">
68
+ <button type="button" class="search-modal-close" aria-label="Close search" id="search-modal-close">
69
+ <span aria-hidden="true">esc</span>
70
+ </button>
71
+ </form>
72
+ </div>
73
+ <div class="search-modal-body">
74
+ <ul id="search-results-mobile" aria-label="Search Results"
75
+ aria-busy="false" aria-expanded="false"
76
+ aria-atomic="false" class="search-results search-modal-results initially-hidden"></ul>
77
+ <div class="search-modal-empty">
78
+ <p>No recent searches</p>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <nav id="navigation" role="navigation">
85
+ <div id="fileindex-section" class="nav-section">
86
+ <details class="nav-section-collapsible" open>
87
+ <summary class="nav-section-header">
88
+ <span class="nav-section-icon">
89
+ <svg><use href="#icon-file"></use></svg>
90
+ </span>
91
+ <span class="nav-section-title">Examples</span>
92
+ <span class="nav-section-chevron">
93
+ <svg><use href="#icon-chevron"></use></svg>
94
+ </span>
95
+ </summary>
96
+ <ul class="nav-list">
97
+ <li><a href="<%= doc_root_link %>">← Back to Docs</a></li>
98
+ </ul>
99
+ </details>
100
+ </div>
101
+ <div class="nav-section">
102
+ <details class="nav-section-collapsible" open>
103
+ <summary class="nav-section-header">
104
+ <span class="nav-section-icon">
105
+ <svg><use href="#icon-layers"></use></svg>
106
+ </span>
107
+ <span class="nav-section-title">Files</span>
108
+ <span class="nav-section-chevron">
109
+ <svg><use href="#icon-chevron"></use></svg>
110
+ </span>
111
+ </summary>
112
+ <ul class="link-list nav-list">
113
+ <%= render_tree(tree_data, relative_path, current_file_html) %>
114
+ </ul>
115
+ </details>
116
+ </div>
117
+ </nav>
118
+
119
+ <main role="main">
120
+ <div class="breadcrumb">
121
+ <%= breadcrumb_path %>
122
+ </div>
123
+ <%= file_header_html %>
124
+ <div class="content">
125
+ <%= file_content_html %>
126
+ </div>
127
+ </main>
128
+
129
+ <% unless toc_items.empty? %>
130
+ <aside class="table-of-contents" role="complementary" aria-label="Table of Contents">
131
+ <div class="toc-sticky">
132
+ <div class="toc-list">
133
+ <h3>On This Page</h3>
134
+ <%= render_toc(toc_items) %>
135
+ </div>
136
+ </div>
137
+ </aside>
138
+ <% end %>
139
+
140
+
141
+ <script>
142
+ const modes = ['auto', 'light', 'dark'];
143
+ const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
144
+
145
+ function setColorMode(mode) {
146
+ if (mode === 'auto') {
147
+ document.documentElement.removeAttribute('data-theme');
148
+ const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
149
+ document.documentElement.setAttribute('data-theme', systemTheme);
150
+ } else {
151
+ document.documentElement.setAttribute('data-theme', mode);
152
+ }
153
+
154
+ const icon = icons[mode];
155
+ const toggle = document.getElementById('theme-toggle');
156
+ toggle.querySelector('.theme-toggle-icon').textContent = icon;
157
+
158
+ localStorage.setItem('rdoc-theme', mode);
159
+ }
160
+
161
+ function cycleColorMode() {
162
+ const current = localStorage.getItem('rdoc-theme') || 'auto';
163
+ const currentIndex = modes.indexOf(current);
164
+ const nextMode = modes[(currentIndex + 1) % modes.length];
165
+ setColorMode(nextMode);
166
+ }
167
+
168
+ const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
169
+ setColorMode(savedMode);
170
+ </script>
171
+ </body>
172
+ </html>
data/tasks/lint.rake CHANGED
@@ -9,9 +9,9 @@ require "inch/rake"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- RubyCritic::RakeTask.new do |task|
13
- task.options = "--no-browser"
14
- task.paths = FileList.new.include("exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs")
12
+ # Run rubycritic in a shell to prevent it from exiting the rake process
13
+ task :rubycritic do
14
+ sh "bundle exec rubycritic --no-browser exe lib sig"
15
15
  end
16
16
 
17
17
  Inch::Rake::Suggest.new("doc:suggest", "exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs") do |suggest|
@@ -39,7 +39,11 @@ end
39
39
  task(:reuse) { Rake::Task["reuse:lint"].invoke }
40
40
 
41
41
  namespace :lint do
42
- task docs: %w[rubycritic rdoc:coverage reuse:lint]
42
+ task :safe_rdoc_coverage do
43
+ sh "bundle exec rake rdoc:coverage"
44
+ end
45
+
46
+ task docs: %w[autodoc rubycritic safe_rdoc_coverage reuse:lint]
43
47
  task code: %w[rubocop rubycritic cargo:fmt cargo:clippy cargo:test]
44
48
  task licenses: %w[reuse:lint]
45
49
  task all: %w[docs code licenses]
@@ -1,19 +1,19 @@
1
1
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
2
  # SPDX-License-Identifier: AGPL-3.0-or-later
3
3
 
4
- image: alpine/edge
4
+ image: archlinux
5
5
  packages:
6
6
  - bash
7
- - build-base
7
+ - base-devel
8
8
  - curl
9
- - openssl-dev
10
- - yaml-dev
11
- - zlib-dev
12
- - readline-dev
13
- - gdbm-dev
14
- - ncurses-dev
15
- - libffi-dev
16
- - clang-dev
9
+ - openssl
10
+ - libyaml
11
+ - zlib
12
+ - readline
13
+ - gdbm
14
+ - ncurses
15
+ - libffi
16
+ - clang
17
17
  - git
18
18
  artifacts:
19
19
  - ratatui_ruby/pkg/<%= gem_filename %>
@@ -24,8 +24,10 @@ tasks:
24
24
  curl https://mise.jdx.dev/install.sh | sh
25
25
  echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
26
26
  echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
27
+ echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
28
+ echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
29
+ echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
27
30
  . ~/.buildenv
28
- export RUSTFLAGS="-C target-feature=-crt-static"
29
31
  export CI="true"
30
32
  cd ratatui_ruby
31
33
  sed -i 's/ruby = .*/ruby = "<%= ruby_version %>"/' mise.toml
@@ -1,3 +1,9 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ -->
6
+
1
7
  <!DOCTYPE html>
2
8
  <html>
3
9
  <head>
data/tasks/sourcehut.rake CHANGED
@@ -9,18 +9,18 @@ task :sourcehut do
9
9
  require "yaml"
10
10
 
11
11
  spec = Gem::Specification.load("ratatui_ruby.gemspec")
12
-
12
+
13
13
  # Read version directly from file to ensure we get the latest version
14
14
  # even if it was just bumped in the same Rake execution
15
15
  version_content = File.read("lib/ratatui_ruby/version.rb")
16
16
  version = version_content.match(/VERSION = "(.+?)"/)[1]
17
-
17
+
18
18
  gem_filename = "#{spec.name}-#{version}.gem"
19
19
 
20
20
  rubies = YAML.load_file("tasks/resources/rubies.yml")
21
-
21
+
22
22
  bundler_version = File.read("Gemfile.lock").match(/BUNDLED WITH\n\s+([\d.]+)/)[1]
23
-
23
+
24
24
  template = File.read("tasks/resources/build.yml.erb")
25
25
  erb = ERB.new(template, trim_mode: "-")
26
26
 
@@ -0,0 +1,33 @@
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
+ require "tmpdir"
7
+ require_relative "launcher_script"
8
+ require_relative "terminal_window"
9
+ require_relative "crash_report"
10
+
11
+ class AppScreenshot < Data.define(:app, :output_path)
12
+ def capture
13
+ print " 📸 #{app}..."
14
+
15
+ LauncherScript.new(app.app_path, Dir.pwd).run do |launcher|
16
+ TerminalWindow.new(launcher.path, launcher.pid_file).open do |window|
17
+ take_snapshot(window.window_id)
18
+ puts " done."
19
+ true
20
+ end
21
+ end
22
+ rescue => e
23
+ puts " FAILED"
24
+ puts
25
+ puts CrashReport.new(app, e, "Program crashed before screenshot could be taken:")
26
+ puts
27
+ false
28
+ end
29
+
30
+ private def take_snapshot(window_id)
31
+ system("screencapture -l #{window_id} -o -x '#{output_path}'")
32
+ end
33
+ end
@@ -0,0 +1,52 @@
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
+ class CrashReport < Data.define(:app, :error, :preamble)
7
+ def self.new(app, error, preamble = nil)
8
+ # Allow preamble to be optional while Data.define requires all fields
9
+ super(app:, error:, preamble:)
10
+ end
11
+
12
+ def to_s
13
+ output = error.message.strip
14
+ formatted_error = output.split("\n").map { |line| format_line(line) }.join("\n")
15
+ preamble_section = preamble ? <<~PREAMBLE.chomp : ""
16
+ #{box_top}
17
+ #{format_line(preamble)}
18
+ #{box_bottom}
19
+ PREAMBLE
20
+
21
+ <<~TEXT
22
+ #{preamble_section}
23
+ #{border_top(app.to_s)}
24
+ #{formatted_error}
25
+ #{box_bottom}
26
+ TEXT
27
+ end
28
+
29
+ private def box_top
30
+ "┌#{'─' * (width - 2)}┐"
31
+ end
32
+
33
+ private def box_bottom
34
+ "└#{'─' * (width - 2)}┘"
35
+ end
36
+
37
+ private def border_top(title)
38
+ left = "┌─ #{title} "
39
+ right = "┐"
40
+ dashes = "─" * (width - left.length - right.length)
41
+ left + dashes + right
42
+ end
43
+
44
+ private def format_line(line)
45
+ truncated = line[0...(width - 4)]
46
+ "│ #{truncated.ljust(width - 4)} │"
47
+ end
48
+
49
+ private def width
50
+ 80
51
+ end
52
+ end
@@ -0,0 +1,25 @@
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
+ class ExampleApp < Data.define(:directory)
7
+ def self.all
8
+ examples_dir = File.expand_path("../../examples", __dir__)
9
+ Dir.glob("#{examples_dir}/*/app.rb").map do |path|
10
+ new(File.basename(File.dirname(path)))
11
+ end.sort_by(&:directory)
12
+ end
13
+
14
+ def app_path
15
+ "examples/#{directory}/app.rb"
16
+ end
17
+
18
+ def screenshot_filename
19
+ "#{directory}.png"
20
+ end
21
+
22
+ def to_s
23
+ directory
24
+ end
25
+ end
@@ -0,0 +1,46 @@
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
+ require "fileutils"
7
+ require "tmpdir"
8
+
9
+ class LauncherScript < Data.define(:app_path, :repo_root)
10
+ def initialize(app_path:, repo_root:)
11
+ super
12
+ write
13
+ end
14
+
15
+ def run
16
+ yield self
17
+ ensure
18
+ cleanup
19
+ end
20
+
21
+ def path
22
+ File.join(Dir.tmpdir, "preview_launcher.sh")
23
+ end
24
+
25
+ def pid_file
26
+ File.join(Dir.tmpdir, "preview_launcher.pid")
27
+ end
28
+
29
+ private def cleanup
30
+ FileUtils.rm_f(path)
31
+ FileUtils.rm_f(pid_file)
32
+ rescue Errno::ENOENT
33
+ # Already deleted
34
+ end
35
+
36
+ private def write
37
+ File.open(path, "w") do |f|
38
+ f.puts "#!/bin/zsh"
39
+ f.puts "cd '#{repo_root}'"
40
+ f.puts "clear"
41
+ f.puts "echo $$ > '#{pid_file}'"
42
+ f.puts "exec bundle exec ruby '#{app_path}'"
43
+ end
44
+ FileUtils.chmod(0o755, path)
45
+ end
46
+ end
@@ -0,0 +1,58 @@
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
+ require "fileutils"
7
+ require_relative "example_app"
8
+ require_relative "app_screenshot"
9
+ require_relative "crash_report"
10
+ require_relative "preview_timing"
11
+ require_relative "safety_confirmation"
12
+ require_relative "saved_screenshot"
13
+
14
+ class PreviewCollection
15
+ def initialize(output_dir)
16
+ @output_dir = output_dir
17
+ end
18
+
19
+ def generate
20
+ abort "Error: This task requires macOS." unless RUBY_PLATFORM.match?(/darwin/)
21
+
22
+ apps = ExampleApp.all
23
+ stale_count = count_stale_apps(apps)
24
+
25
+ if stale_count.zero?
26
+ puts "\n✨ All #{apps.count} screenshots are up to date."
27
+ return
28
+ end
29
+
30
+ SafetyConfirmation.new(stale_count, apps.count).request
31
+
32
+ puts "\nHere we go!"
33
+ failures = apps.count { |app| !capture_app(app) }
34
+
35
+ if failures.zero?
36
+ puts "\n✨ All captures complete. Check doc/images/."
37
+ else
38
+ abort "\n❌ #{failures} capture(s) failed."
39
+ end
40
+ end
41
+
42
+ private def count_stale_apps(apps)
43
+ apps.count { |app| SavedScreenshot.for(app, @output_dir).stale? }
44
+ end
45
+
46
+ private def capture_app(app)
47
+ saved = SavedScreenshot.for(app, @output_dir)
48
+
49
+ if saved.stale?
50
+ success = AppScreenshot.new(app, saved.path).capture
51
+ sleep PreviewTiming.between_captures
52
+ success
53
+ else
54
+ puts " ⏭️ #{app} (unchanged)"
55
+ true
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
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
+ class PreviewTiming
7
+ def self.window_startup
8
+ 1.5
9
+ end
10
+
11
+ def self.between_captures
12
+ 0.2
13
+ end
14
+
15
+ def self.close_delay
16
+ 1.0
17
+ end
18
+
19
+ def self.total
20
+ window_startup + close_delay + between_captures
21
+ end
22
+ end
@@ -0,0 +1,56 @@
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
+ require_relative "preview_timing"
7
+ require_relative "system_appearance"
8
+
9
+ class SafetyConfirmation
10
+ def initialize(stale_count, total_count)
11
+ @stale_count = stale_count
12
+ @total_count = total_count
13
+ end
14
+
15
+ def request
16
+ print_warning
17
+ wait_for_user
18
+ end
19
+
20
+ private def print_warning
21
+ unchanged_count = @total_count - @stale_count
22
+ puts "\n#{'=' * 60}"
23
+ puts " 📸 NATIVE TERMINAL CAPTURE 📸"
24
+ puts "=" * 60
25
+ puts "This task will:"
26
+ puts " 1. Take control of your mouse/keyboard focus"
27
+ puts " 2. Rapidly open and close Terminal windows"
28
+ puts " 3. Capture #{@stale_count} screenshots (#{unchanged_count} unchanged)"
29
+ puts
30
+ puts "Before starting, be sure Terminal.app has the following permissions"
31
+ puts "in System Settings.app -> Privacy & Security:"
32
+ puts " - Screen & System Audio Recording"
33
+ puts " - Automation -> System Events"
34
+ puts
35
+ puts "⚠️ PLEASE DO NOT TOUCH YOUR COMPUTER WHILE THIS RUNS."
36
+ min_time = (@stale_count * PreviewTiming.total).to_i
37
+ max_time = (@stale_count * (PreviewTiming.total + PreviewTiming.close_delay)).to_i
38
+ puts " (Estimated time: #{min_time}-#{max_time} seconds)"
39
+ puts
40
+ end
41
+
42
+ private def wait_for_user
43
+ loop do
44
+ print "Continue? [Y/n]: "
45
+ response = $stdin.gets.strip.downcase
46
+
47
+ if response.empty? || response == "y"
48
+ return if SystemAppearance.dark?
49
+ puts "⚠️ Dark Mode is not enabled. Please enable it in System Settings or Control Center before proceeding."
50
+ puts
51
+ elsif response == "n"
52
+ abort "Cancelled."
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,53 @@
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
+ require "time"
7
+
8
+ class SavedScreenshot < Data.define(:app, :path)
9
+ def self.for(app, output_dir)
10
+ new(app, File.join(output_dir, app.screenshot_filename))
11
+ end
12
+
13
+ def stale?
14
+ return true unless exists?
15
+
16
+ app_last_modified > screenshot_last_commit_time
17
+ end
18
+
19
+ private def exists?
20
+ File.exist?(path)
21
+ end
22
+
23
+ private def app_last_modified
24
+ # If the file has staged or unstaged changes, it's definitely stale
25
+ return Time.now.to_i if changed?
26
+
27
+ # Otherwise, compare against the last git commit time
28
+ app_last_commit_time
29
+ end
30
+
31
+ private def changed?
32
+ system("git diff HEAD --quiet #{app.app_path} 2>/dev/null")
33
+ !$?.success?
34
+ end
35
+
36
+ private def app_last_commit_time
37
+ output = `git log -1 --format=%cI "#{app.app_path}" 2>/dev/null`.strip
38
+ return 0 if output.empty?
39
+
40
+ Time.iso8601(output).to_i
41
+ rescue
42
+ 0
43
+ end
44
+
45
+ private def screenshot_last_commit_time
46
+ output = `git log -1 --format=%cI "#{path}" 2>/dev/null`.strip
47
+ return Time.now.to_i if output.empty?
48
+
49
+ Time.iso8601(output).to_i
50
+ rescue
51
+ Time.now.to_i
52
+ end
53
+ end
@@ -0,0 +1,11 @@
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
+ class SystemAppearance
7
+ def self.dark?
8
+ result = `osascript -e 'tell application "System Events" to tell appearance preferences to get dark mode'`.strip
9
+ result == "true"
10
+ end
11
+ end