ratatui_ruby 1.3.0 → 1.3.3

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 (301) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +2 -1
  3. data/ext/ratatui_ruby/Cargo.toml +2 -1
  4. data/ext/ratatui_ruby/src/color.rs +1 -1
  5. data/ext/ratatui_ruby/src/errors.rs +1 -1
  6. data/ext/ratatui_ruby/src/events.rs +158 -19
  7. data/ext/ratatui_ruby/src/frame.rs +1 -1
  8. data/ext/ratatui_ruby/src/lib.rs +1 -1
  9. data/ext/ratatui_ruby/src/lib_header.rs +1 -1
  10. data/ext/ratatui_ruby/src/rendering.rs +1 -1
  11. data/ext/ratatui_ruby/src/string_width.rs +1 -1
  12. data/ext/ratatui_ruby/src/style.rs +1 -1
  13. data/ext/ratatui_ruby/src/terminal/capabilities.rs +1 -1
  14. data/ext/ratatui_ruby/src/terminal/init.rs +1 -1
  15. data/ext/ratatui_ruby/src/terminal/mod.rs +1 -1
  16. data/ext/ratatui_ruby/src/terminal/mutations.rs +1 -1
  17. data/ext/ratatui_ruby/src/terminal/queries.rs +1 -1
  18. data/ext/ratatui_ruby/src/terminal/query.rs +1 -1
  19. data/ext/ratatui_ruby/src/terminal/storage.rs +1 -1
  20. data/ext/ratatui_ruby/src/terminal/wrapper.rs +1 -1
  21. data/ext/ratatui_ruby/src/text.rs +1 -1
  22. data/ext/ratatui_ruby/src/widgets/barchart.rs +1 -1
  23. data/ext/ratatui_ruby/src/widgets/block.rs +1 -1
  24. data/ext/ratatui_ruby/src/widgets/calendar.rs +1 -1
  25. data/ext/ratatui_ruby/src/widgets/canvas.rs +1 -1
  26. data/ext/ratatui_ruby/src/widgets/center.rs +1 -1
  27. data/ext/ratatui_ruby/src/widgets/chart.rs +1 -1
  28. data/ext/ratatui_ruby/src/widgets/clear.rs +1 -1
  29. data/ext/ratatui_ruby/src/widgets/cursor.rs +1 -1
  30. data/ext/ratatui_ruby/src/widgets/gauge.rs +1 -1
  31. data/ext/ratatui_ruby/src/widgets/layout.rs +1 -1
  32. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +1 -1
  33. data/ext/ratatui_ruby/src/widgets/list.rs +1 -1
  34. data/ext/ratatui_ruby/src/widgets/list_state.rs +1 -1
  35. data/ext/ratatui_ruby/src/widgets/mod.rs +1 -1
  36. data/ext/ratatui_ruby/src/widgets/overlay.rs +1 -1
  37. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  38. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +1 -1
  39. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +1 -1
  40. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +1 -1
  41. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +1 -1
  42. data/ext/ratatui_ruby/src/widgets/sparkline.rs +1 -1
  43. data/ext/ratatui_ruby/src/widgets/table.rs +1 -1
  44. data/ext/ratatui_ruby/src/widgets/table_state.rs +1 -1
  45. data/ext/ratatui_ruby/src/widgets/tabs.rs +1 -1
  46. data/lib/ratatui_ruby/version.rb +1 -1
  47. metadata +1 -255
  48. data/.builds/ruby-3.2.yml +0 -54
  49. data/.builds/ruby-3.3.yml +0 -54
  50. data/.builds/ruby-3.4.yml +0 -54
  51. data/.builds/ruby-4.0.0.yml +0 -54
  52. data/.pre-commit-config.yaml +0 -16
  53. data/.rubocop.yml +0 -10
  54. data/AGENTS.md +0 -147
  55. data/CHANGELOG.md +0 -771
  56. data/README.md +0 -187
  57. data/README.rdoc +0 -302
  58. data/Rakefile +0 -11
  59. data/Steepfile +0 -50
  60. data/doc/concepts/application_architecture.md +0 -321
  61. data/doc/concepts/application_testing.md +0 -193
  62. data/doc/concepts/async.md +0 -190
  63. data/doc/concepts/custom_widgets.md +0 -247
  64. data/doc/concepts/debugging.md +0 -401
  65. data/doc/concepts/event_handling.md +0 -162
  66. data/doc/concepts/interactive_design.md +0 -146
  67. data/doc/contributors/auditing/parity.md +0 -239
  68. data/doc/contributors/design/ruby_frontend.md +0 -448
  69. data/doc/contributors/design/rust_backend.md +0 -434
  70. data/doc/contributors/design.md +0 -11
  71. data/doc/contributors/developing_examples.md +0 -400
  72. data/doc/contributors/documentation_style.md +0 -121
  73. data/doc/contributors/index.md +0 -21
  74. data/doc/contributors/releasing.md +0 -215
  75. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  76. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  77. data/doc/contributors/todo/align/term.md +0 -351
  78. data/doc/contributors/todo/align/terminal.md +0 -647
  79. data/doc/contributors/todo/future_work.md +0 -169
  80. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  81. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  82. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  83. data/doc/custom.css +0 -22
  84. data/doc/getting_started/quickstart.md +0 -291
  85. data/doc/getting_started/why.md +0 -93
  86. data/doc/images/app_all_events.png +0 -0
  87. data/doc/images/app_cli_rich_moments.gif +0 -0
  88. data/doc/images/app_color_picker.png +0 -0
  89. data/doc/images/app_debugging_showcase.gif +0 -0
  90. data/doc/images/app_debugging_showcase.png +0 -0
  91. data/doc/images/app_external_editor.gif +0 -0
  92. data/doc/images/app_login_form.png +0 -0
  93. data/doc/images/app_stateful_interaction.png +0 -0
  94. data/doc/images/verify_quickstart_dsl.png +0 -0
  95. data/doc/images/verify_quickstart_layout.png +0 -0
  96. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  97. data/doc/images/verify_readme_usage.png +0 -0
  98. data/doc/images/widget_barchart.png +0 -0
  99. data/doc/images/widget_block.png +0 -0
  100. data/doc/images/widget_box.png +0 -0
  101. data/doc/images/widget_calendar.png +0 -0
  102. data/doc/images/widget_canvas.png +0 -0
  103. data/doc/images/widget_cell.png +0 -0
  104. data/doc/images/widget_center.png +0 -0
  105. data/doc/images/widget_chart.png +0 -0
  106. data/doc/images/widget_gauge.png +0 -0
  107. data/doc/images/widget_layout_split.png +0 -0
  108. data/doc/images/widget_line_gauge.png +0 -0
  109. data/doc/images/widget_list.png +0 -0
  110. data/doc/images/widget_map.png +0 -0
  111. data/doc/images/widget_overlay.png +0 -0
  112. data/doc/images/widget_popup.png +0 -0
  113. data/doc/images/widget_ratatui_logo.png +0 -0
  114. data/doc/images/widget_ratatui_mascot.png +0 -0
  115. data/doc/images/widget_rect.png +0 -0
  116. data/doc/images/widget_render.png +0 -0
  117. data/doc/images/widget_rich_text.png +0 -0
  118. data/doc/images/widget_scroll_text.png +0 -0
  119. data/doc/images/widget_scrollbar.png +0 -0
  120. data/doc/images/widget_sparkline.png +0 -0
  121. data/doc/images/widget_style_colors.png +0 -0
  122. data/doc/images/widget_table.png +0 -0
  123. data/doc/images/widget_tabs.png +0 -0
  124. data/doc/images/widget_text_width.png +0 -0
  125. data/doc/index.md +0 -34
  126. data/doc/troubleshooting/async.md +0 -4
  127. data/doc/troubleshooting/terminal_limitations.md +0 -131
  128. data/doc/troubleshooting/tui_output.md +0 -197
  129. data/examples/app_all_events/README.md +0 -114
  130. data/examples/app_all_events/app.rb +0 -98
  131. data/examples/app_all_events/model/app_model.rb +0 -159
  132. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  133. data/examples/app_all_events/model/event_entry.rb +0 -94
  134. data/examples/app_all_events/model/msg.rb +0 -39
  135. data/examples/app_all_events/model/timestamp.rb +0 -56
  136. data/examples/app_all_events/update.rb +0 -75
  137. data/examples/app_all_events/view/app_view.rb +0 -80
  138. data/examples/app_all_events/view/controls_view.rb +0 -54
  139. data/examples/app_all_events/view/counts_view.rb +0 -61
  140. data/examples/app_all_events/view/live_view.rb +0 -72
  141. data/examples/app_all_events/view/log_view.rb +0 -57
  142. data/examples/app_all_events/view.rb +0 -9
  143. data/examples/app_cli_rich_moments/README.md +0 -81
  144. data/examples/app_cli_rich_moments/app.rb +0 -189
  145. data/examples/app_color_picker/README.md +0 -156
  146. data/examples/app_color_picker/app.rb +0 -76
  147. data/examples/app_color_picker/clipboard.rb +0 -86
  148. data/examples/app_color_picker/color.rb +0 -193
  149. data/examples/app_color_picker/controls.rb +0 -92
  150. data/examples/app_color_picker/copy_dialog.rb +0 -168
  151. data/examples/app_color_picker/export_pane.rb +0 -128
  152. data/examples/app_color_picker/harmony.rb +0 -58
  153. data/examples/app_color_picker/input.rb +0 -176
  154. data/examples/app_color_picker/main_container.rb +0 -180
  155. data/examples/app_color_picker/palette.rb +0 -111
  156. data/examples/app_debugging_showcase/README.md +0 -119
  157. data/examples/app_debugging_showcase/app.rb +0 -318
  158. data/examples/app_external_editor/README.md +0 -62
  159. data/examples/app_external_editor/app.rb +0 -344
  160. data/examples/app_login_form/README.md +0 -58
  161. data/examples/app_login_form/app.rb +0 -109
  162. data/examples/app_stateful_interaction/README.md +0 -35
  163. data/examples/app_stateful_interaction/app.rb +0 -328
  164. data/examples/timeout_demo.rb +0 -45
  165. data/examples/verify_quickstart_dsl/README.md +0 -55
  166. data/examples/verify_quickstart_dsl/app.rb +0 -49
  167. data/examples/verify_quickstart_layout/README.md +0 -77
  168. data/examples/verify_quickstart_layout/app.rb +0 -73
  169. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  170. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  171. data/examples/verify_readme_usage/README.md +0 -49
  172. data/examples/verify_readme_usage/app.rb +0 -42
  173. data/examples/verify_website_managed/README.md +0 -48
  174. data/examples/verify_website_managed/app.rb +0 -36
  175. data/examples/verify_website_menu/README.md +0 -60
  176. data/examples/verify_website_menu/app.rb +0 -84
  177. data/examples/verify_website_spinner/README.md +0 -44
  178. data/examples/verify_website_spinner/app.rb +0 -34
  179. data/examples/widget_barchart/README.md +0 -58
  180. data/examples/widget_barchart/app.rb +0 -240
  181. data/examples/widget_block/README.md +0 -44
  182. data/examples/widget_block/app.rb +0 -258
  183. data/examples/widget_box/README.md +0 -54
  184. data/examples/widget_box/app.rb +0 -255
  185. data/examples/widget_calendar/README.md +0 -48
  186. data/examples/widget_calendar/app.rb +0 -115
  187. data/examples/widget_canvas/README.md +0 -31
  188. data/examples/widget_canvas/app.rb +0 -130
  189. data/examples/widget_cell/README.md +0 -45
  190. data/examples/widget_cell/app.rb +0 -112
  191. data/examples/widget_center/README.md +0 -33
  192. data/examples/widget_center/app.rb +0 -118
  193. data/examples/widget_chart/README.md +0 -50
  194. data/examples/widget_chart/app.rb +0 -220
  195. data/examples/widget_gauge/README.md +0 -50
  196. data/examples/widget_gauge/app.rb +0 -229
  197. data/examples/widget_layout_split/README.md +0 -53
  198. data/examples/widget_layout_split/app.rb +0 -260
  199. data/examples/widget_line_gauge/README.md +0 -50
  200. data/examples/widget_line_gauge/app.rb +0 -219
  201. data/examples/widget_list/README.md +0 -58
  202. data/examples/widget_list/app.rb +0 -382
  203. data/examples/widget_map/README.md +0 -48
  204. data/examples/widget_map/app.rb +0 -95
  205. data/examples/widget_overlay/README.md +0 -45
  206. data/examples/widget_overlay/app.rb +0 -250
  207. data/examples/widget_popup/README.md +0 -45
  208. data/examples/widget_popup/app.rb +0 -106
  209. data/examples/widget_ratatui_logo/README.md +0 -43
  210. data/examples/widget_ratatui_logo/app.rb +0 -104
  211. data/examples/widget_ratatui_mascot/README.md +0 -43
  212. data/examples/widget_ratatui_mascot/app.rb +0 -95
  213. data/examples/widget_rect/README.md +0 -53
  214. data/examples/widget_rect/app.rb +0 -222
  215. data/examples/widget_render/README.md +0 -46
  216. data/examples/widget_render/app.rb +0 -186
  217. data/examples/widget_render/app.rbs +0 -41
  218. data/examples/widget_rich_text/README.md +0 -44
  219. data/examples/widget_rich_text/app.rb +0 -193
  220. data/examples/widget_scroll_text/README.md +0 -46
  221. data/examples/widget_scroll_text/app.rb +0 -109
  222. data/examples/widget_scrollbar/README.md +0 -46
  223. data/examples/widget_scrollbar/app.rb +0 -155
  224. data/examples/widget_sparkline/README.md +0 -51
  225. data/examples/widget_sparkline/app.rb +0 -277
  226. data/examples/widget_style_colors/README.md +0 -43
  227. data/examples/widget_style_colors/app.rb +0 -83
  228. data/examples/widget_table/README.md +0 -57
  229. data/examples/widget_table/app.rb +0 -285
  230. data/examples/widget_tabs/README.md +0 -50
  231. data/examples/widget_tabs/app.rb +0 -183
  232. data/examples/widget_text_width/README.md +0 -44
  233. data/examples/widget_text_width/app.rb +0 -117
  234. data/migrate_to_buffer.rb +0 -145
  235. data/mise.toml +0 -8
  236. data/tasks/autodoc/examples.rb +0 -87
  237. data/tasks/autodoc/member.rb +0 -58
  238. data/tasks/autodoc/name.rb +0 -21
  239. data/tasks/autodoc.rake +0 -21
  240. data/tasks/bump/bump_workflow.rb +0 -49
  241. data/tasks/bump/cargo_lockfile.rb +0 -21
  242. data/tasks/bump/changelog.rb +0 -104
  243. data/tasks/bump/header.rb +0 -32
  244. data/tasks/bump/history.rb +0 -32
  245. data/tasks/bump/links.rb +0 -69
  246. data/tasks/bump/manifest.rb +0 -33
  247. data/tasks/bump/patch_release.rb +0 -19
  248. data/tasks/bump/release_branch.rb +0 -17
  249. data/tasks/bump/release_from_trunk.rb +0 -49
  250. data/tasks/bump/repository.rb +0 -54
  251. data/tasks/bump/ruby_gem.rb +0 -29
  252. data/tasks/bump/sem_ver.rb +0 -44
  253. data/tasks/bump/unreleased_section.rb +0 -73
  254. data/tasks/bump.rake +0 -61
  255. data/tasks/doc/documentation.rb +0 -59
  256. data/tasks/doc/link/file_url.rb +0 -30
  257. data/tasks/doc/link/relative_path.rb +0 -61
  258. data/tasks/doc/link/web_url.rb +0 -55
  259. data/tasks/doc/link.rb +0 -52
  260. data/tasks/doc/link_audit.rb +0 -116
  261. data/tasks/doc/problem.rb +0 -40
  262. data/tasks/doc/source_file.rb +0 -93
  263. data/tasks/doc.rake +0 -905
  264. data/tasks/example_viewer.html.erb +0 -172
  265. data/tasks/extension.rake +0 -14
  266. data/tasks/license/headers_md.rb +0 -223
  267. data/tasks/license/headers_rb.rb +0 -210
  268. data/tasks/license/license_utils.rb +0 -130
  269. data/tasks/license/snippets_md.rb +0 -315
  270. data/tasks/license/snippets_rdoc.rb +0 -150
  271. data/tasks/license.rake +0 -91
  272. data/tasks/lint.rake +0 -170
  273. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  274. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  275. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  276. data/tasks/rbs_predicates.rake +0 -31
  277. data/tasks/rdoc_config.rb +0 -29
  278. data/tasks/resources/build.yml.erb +0 -60
  279. data/tasks/resources/index.html.erb +0 -141
  280. data/tasks/resources/rubies.yml +0 -7
  281. data/tasks/sourcehut.rake +0 -122
  282. data/tasks/steep.rake +0 -11
  283. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  284. data/tasks/terminal_preview/crash_report.rb +0 -54
  285. data/tasks/terminal_preview/example_app.rb +0 -27
  286. data/tasks/terminal_preview/launcher_script.rb +0 -48
  287. data/tasks/terminal_preview/preview_collection.rb +0 -60
  288. data/tasks/terminal_preview/preview_timing.rb +0 -24
  289. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  290. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  291. data/tasks/terminal_preview/system_appearance.rb +0 -13
  292. data/tasks/terminal_preview/terminal_window.rb +0 -138
  293. data/tasks/terminal_preview/window_id.rb +0 -16
  294. data/tasks/terminal_preview.rake +0 -30
  295. data/tasks/test.rake +0 -36
  296. data/tasks/website/index_page.rb +0 -30
  297. data/tasks/website/version.rb +0 -122
  298. data/tasks/website/version_menu.rb +0 -68
  299. data/tasks/website/versioned_documentation.rb +0 -83
  300. data/tasks/website/website.rb +0 -53
  301. data/tasks/website.rake +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65cec825c4cedc983b69cfa150cf30bfc295b1aff70c4866dc18161e97207da0
4
- data.tar.gz: '085eef207a3cb0d29e0992c1885ff788867387a725925d596bba309086ce6c2d'
3
+ metadata.gz: 5315973120c1bcf306641ec56035952c701e264ed0ef13b6d68079c8229cd780
4
+ data.tar.gz: 9cfb4d3fa480e9080a8e0b61c5977513837db87109a2c7290456230a02fbd5c2
5
5
  SHA512:
6
- metadata.gz: c3e69e5096a3d8790d6b81ec6a3fa0589565d5cbf3661f6bc78070c2ee91d29bd068fb5a31fe84e81364544e127e629f080faf6cb533d729fda58a18ed862ba1
7
- data.tar.gz: 5deeb497d1c9663d7c35efee84f8d2ca8b501810772eaf57750f2d452939e0d45c7847b13d18f59943872e3c46f649141b1e9cce09fa2242f6593168c83d3922
6
+ metadata.gz: e80dd1511c0952bfc7394ab998d6212152c668d8061945a53944fbc691d4cb1685fd9855157baf1a8062c79aeb64e6789f4d2e8836501408579a4a87e1c77b16
7
+ data.tar.gz: 7b07d46ace5da4082211afa665dc50e52e79d3639a608004fde81db371bc02ea6609820545cdf6240c11b56ffa7c51731d0972cf258bbf2a139fbb91cdc0257f
@@ -1059,12 +1059,13 @@ dependencies = [
1059
1059
 
1060
1060
  [[package]]
1061
1061
  name = "ratatui_ruby"
1062
- version = "1.3.0"
1062
+ version = "1.3.3"
1063
1063
  dependencies = [
1064
1064
  "bumpalo",
1065
1065
  "lazy_static",
1066
1066
  "magnus",
1067
1067
  "ratatui",
1068
+ "rb-sys",
1068
1069
  "time",
1069
1070
  "unicode-width 0.1.14",
1070
1071
  ]
@@ -3,7 +3,7 @@
3
3
 
4
4
  [package]
5
5
  name = "ratatui_ruby"
6
- version = "1.3.0"
6
+ version = "1.3.3"
7
7
  edition = "2021"
8
8
 
9
9
  [lib]
@@ -11,6 +11,7 @@ crate-type = ["cdylib", "staticlib"]
11
11
 
12
12
  [dependencies]
13
13
  magnus = "0.8.2"
14
+ rb-sys = "*"
14
15
  ratatui = { version = "0.30", features = ["widget-calendar", "layout-cache", "unstable-rendered-line-info", "palette"] }
15
16
  unicode-width = "0.1"
16
17
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Color conversion functions exposed to Ruby.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use magnus::{prelude::*, Error, Value};
5
5
 
@@ -1,8 +1,10 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use magnus::{Error, IntoValue, TryConvert, Value};
5
+ use rb_sys::rb_thread_call_without_gvl;
5
6
  use std::cell::RefCell;
7
+ use std::os::raw::c_void;
6
8
 
7
9
  /// Wrapper enum for test events - includes crossterm events and our Sync event.
8
10
  #[derive(Debug, Clone)]
@@ -314,6 +316,92 @@ pub fn clear_events() {
314
316
  EVENT_QUEUE.with(|q| q.borrow_mut().clear());
315
317
  }
316
318
 
319
+ /// Result of polling crossterm from outside the GVL.
320
+ ///
321
+ /// The blocking I/O (poll + read) happens without the Ruby GVL held,
322
+ /// so other Ruby threads can run concurrently. This enum carries the
323
+ /// result back across the GVL boundary for Ruby object construction.
324
+ #[derive(Debug)]
325
+ enum PollResult {
326
+ /// A crossterm event was read successfully.
327
+ Event(ratatui::crossterm::event::Event),
328
+ /// The timeout expired with no event available.
329
+ NoEvent,
330
+ /// An I/O error occurred during poll or read.
331
+ Error(String),
332
+ }
333
+
334
+ /// Data passed to the GVL-free polling callback.
335
+ ///
336
+ /// The `timeout` field controls the poll behavior:
337
+ /// - `Some(duration)`: poll with a timeout, return `NoEvent` if it expires.
338
+ /// - `None`: block indefinitely until an event arrives.
339
+ struct PollData {
340
+ timeout: Option<std::time::Duration>,
341
+ result: PollResult,
342
+ }
343
+
344
+ /// Polls crossterm for events without the Ruby GVL held.
345
+ ///
346
+ /// This is the work function passed to `rb_thread_call_without_gvl`.
347
+ /// It performs blocking I/O that would otherwise starve Ruby threads.
348
+ extern "C" fn poll_without_gvl(data: *mut c_void) -> *mut c_void {
349
+ // SAFETY: `data` is a valid pointer to `PollData`, passed by
350
+ // `poll_crossterm_without_gvl` via `rb_thread_call_without_gvl`. The
351
+ // pointer remains valid for the duration of this callback because the
352
+ // caller owns the `PollData` on the stack and waits for us to return.
353
+ let data = unsafe { &mut *data.cast::<PollData>() };
354
+
355
+ data.result = match data.timeout {
356
+ Some(duration) => match ratatui::crossterm::event::poll(duration) {
357
+ Ok(true) => match ratatui::crossterm::event::read() {
358
+ Ok(event) => PollResult::Event(event),
359
+ Err(e) => PollResult::Error(e.to_string()),
360
+ },
361
+ Ok(false) => PollResult::NoEvent,
362
+ Err(e) => PollResult::Error(e.to_string()),
363
+ },
364
+ None => match ratatui::crossterm::event::read() {
365
+ Ok(event) => PollResult::Event(event),
366
+ Err(e) => PollResult::Error(e.to_string()),
367
+ },
368
+ };
369
+
370
+ std::ptr::null_mut()
371
+ }
372
+
373
+ /// Polls crossterm with the GVL released, then converts the result
374
+ /// to a Ruby value after reacquiring the GVL.
375
+ fn poll_crossterm_without_gvl(
376
+ ruby: &magnus::Ruby,
377
+ timeout: Option<std::time::Duration>,
378
+ ) -> Result<Value, Error> {
379
+ let mut data = PollData {
380
+ timeout,
381
+ result: PollResult::NoEvent,
382
+ };
383
+
384
+ // SAFETY: `data` is a valid stack-local `PollData` whose lifetime
385
+ // spans this entire call. `poll_without_gvl` only reads/writes
386
+ // through the pointer while `rb_thread_call_without_gvl` blocks,
387
+ // and we don't access `data` again until that function returns.
388
+ unsafe {
389
+ rb_thread_call_without_gvl(
390
+ Some(poll_without_gvl),
391
+ (&raw mut data).cast::<c_void>(),
392
+ None,
393
+ std::ptr::null_mut(),
394
+ );
395
+ }
396
+
397
+ // GVL is now re-held — safe to create Ruby objects.
398
+ match data.result {
399
+ PollResult::Event(event) => handle_crossterm_event(event),
400
+ PollResult::NoEvent => Ok(ruby.qnil().into_value_with(ruby)),
401
+ PollResult::Error(msg) => Err(Error::new(ruby.exception_runtime_error(), msg)),
402
+ }
403
+ }
404
+
317
405
  pub fn poll_event(ruby: &magnus::Ruby, timeout_val: Option<f64>) -> Result<Value, Error> {
318
406
  let event = EVENT_QUEUE.with(|q| {
319
407
  let mut queue = q.borrow_mut();
@@ -334,24 +422,8 @@ pub fn poll_event(ruby: &magnus::Ruby, timeout_val: Option<f64>) -> Result<Value
334
422
  return Ok(ruby.qnil().into_value_with(ruby));
335
423
  }
336
424
 
337
- if let Some(secs) = timeout_val {
338
- // Timed poll: wait up to the specified duration
339
- let duration = std::time::Duration::from_secs_f64(secs);
340
- if ratatui::crossterm::event::poll(duration)
341
- .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?
342
- {
343
- let event = ratatui::crossterm::event::read()
344
- .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
345
- handle_crossterm_event(event)
346
- } else {
347
- Ok(ruby.qnil().into_value_with(ruby))
348
- }
349
- } else {
350
- // Blocking: wait indefinitely for an event
351
- let event = ratatui::crossterm::event::read()
352
- .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
353
- handle_crossterm_event(event)
354
- }
425
+ let timeout = timeout_val.map(std::time::Duration::from_secs_f64);
426
+ poll_crossterm_without_gvl(ruby, timeout)
355
427
  }
356
428
 
357
429
  fn handle_test_event(event: TestEvent) -> Result<Value, Error> {
@@ -559,3 +631,70 @@ fn handle_focus_event(event_type: &str) -> Result<Value, Error> {
559
631
  hash.aset(ruby.to_symbol("type"), ruby.to_symbol(event_type))?;
560
632
  Ok(hash.into_value_with(&ruby))
561
633
  }
634
+
635
+ #[cfg(test)]
636
+ mod tests {
637
+ use super::*;
638
+
639
+ #[test]
640
+ fn poll_data_defaults_to_no_event() {
641
+ let data = PollData {
642
+ timeout: Some(std::time::Duration::from_millis(0)),
643
+ result: PollResult::NoEvent,
644
+ };
645
+ assert!(matches!(data.result, PollResult::NoEvent));
646
+ }
647
+
648
+ #[test]
649
+ fn poll_data_accepts_none_timeout_for_indefinite_blocking() {
650
+ let data = PollData {
651
+ timeout: None,
652
+ result: PollResult::NoEvent,
653
+ };
654
+ assert!(data.timeout.is_none());
655
+ }
656
+
657
+ #[test]
658
+ fn poll_result_error_preserves_message() {
659
+ let result = PollResult::Error("connection reset".to_string());
660
+ match result {
661
+ PollResult::Error(msg) => assert_eq!(msg, "connection reset"),
662
+ _ => panic!("Expected PollResult::Error"),
663
+ }
664
+ }
665
+
666
+ #[test]
667
+ fn poll_result_event_wraps_crossterm_event() {
668
+ let key_event =
669
+ ratatui::crossterm::event::Event::Key(ratatui::crossterm::event::KeyEvent::new(
670
+ ratatui::crossterm::event::KeyCode::Char('q'),
671
+ ratatui::crossterm::event::KeyModifiers::empty(),
672
+ ));
673
+ let result = PollResult::Event(key_event.clone());
674
+ match result {
675
+ PollResult::Event(e) => assert_eq!(format!("{e:?}"), format!("{key_event:?}")),
676
+ _ => panic!("Expected PollResult::Event"),
677
+ }
678
+ }
679
+
680
+ #[test]
681
+ fn poll_without_gvl_returns_no_event_on_zero_timeout() {
682
+ // With a zero-duration timeout, poll returns immediately with no event.
683
+ // In headless environments (CI), crossterm may return an Error because
684
+ // there is no terminal to read from — that's also a valid outcome.
685
+ let mut data = PollData {
686
+ timeout: Some(std::time::Duration::from_millis(0)),
687
+ result: PollResult::Error("sentinel — should be overwritten".to_string()),
688
+ };
689
+
690
+ poll_without_gvl(&mut data as *mut PollData as *mut c_void);
691
+
692
+ // The callback must have overwritten the sentinel value.
693
+ match &data.result {
694
+ PollResult::Error(msg) if msg == "sentinel — should be overwritten" => {
695
+ panic!("poll_without_gvl did not write to data.result")
696
+ }
697
+ _ => {} // NoEvent, Event, or a *different* Error are all fine.
698
+ }
699
+ }
700
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Frame wrapper for exposing Ratatui's Frame to Ruby.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  // Require SAFETY comments on all unsafe blocks
5
5
  #![warn(clippy::undocumented_unsafe_blocks)]
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  // Require SAFETY comments on all unsafe blocks
5
5
  #![warn(clippy::undocumented_unsafe_blocks)]
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_color_value, parse_modifier_str, parse_style};
5
5
  use crate::widgets;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use magnus::{prelude::*, Error, Value};
5
5
 
@@ -1,6 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
2
  //
3
- // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // SPDX-License-Identifier: LGPL-3.0-or-later
4
4
 
5
5
  use crate::errors::type_error_with_context;
6
6
  use bumpalo::Bump;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal capability detection functions.
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal initialization and restoration functions.
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal management module.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal mutation functions (write operations).
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal query functions (read-only access to terminal state).
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal query trait and implementations.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Private terminal storage with safe accessor functions.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! Terminal wrapper enum for different backend types.
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::style::parse_style;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_bar_set, parse_block, parse_style};
5
5
  use crate::text::{parse_line, parse_span};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::rendering::render_node;
5
5
  use crate::style::parse_block;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_block, parse_style};
5
5
  use bumpalo::Bump;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_block, parse_color, parse_style};
5
5
  use crate::text::parse_text;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::rendering::render_node;
5
5
  use magnus::{prelude::*, Error, Value};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::style::{parse_block, parse_style};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use bumpalo::Bump;
5
5
  use magnus::{prelude::*, Error, Value};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use magnus::{Error, Value};
5
5
  use ratatui::{buffer::Buffer, layout::Rect};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_block, parse_style};
5
5
  use crate::text::parse_span;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::rendering::render_node;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_block, parse_style};
5
5
  use crate::text::parse_span;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::style::{parse_block, parse_style};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! `ListState` wrapper for exposing Ratatui's `ListState` to Ruby.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  pub mod barchart;
5
5
  pub mod block;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::rendering::render_node;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_block, parse_style};
5
5
  use bumpalo::Bump;
@@ -1,6 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
2
  //
3
- // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // SPDX-License-Identifier: LGPL-3.0-or-later
4
4
 
5
5
  use magnus::Value;
6
6
  use ratatui::{
@@ -1,6 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
2
  //
3
- // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // SPDX-License-Identifier: LGPL-3.0-or-later
4
4
 
5
5
  use crate::style::parse_block;
6
6
  use bumpalo::Bump;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::parse_block;
5
5
  use crate::widgets::scrollbar_state::RubyScrollbarState;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! `ScrollbarState` wrapper for exposing Ratatui's `ScrollbarState` to Ruby.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::style::{parse_bar_set, parse_block, parse_style};
5
5
  use bumpalo::Bump;
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::style::{parse_block, parse_style};
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  //! `TableState` wrapper for exposing Ratatui's `TableState` to Ruby.
5
5
  //!
@@ -1,5 +1,5 @@
1
1
  // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  use crate::errors::type_error_with_context;
5
5
  use crate::style::parse_block;
@@ -8,5 +8,5 @@
8
8
  module RatatuiRuby
9
9
  # The version of the ratatui_ruby gem.
10
10
  # See https://semver.org/spec/v2.0.0.html
11
- VERSION = "1.3.0"
11
+ VERSION = "1.3.3"
12
12
  end