ratatui_ruby 1.5.2-arm64-darwin

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 (295) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +15 -0
  3. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  4. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  5. data/LICENSES/CC0-1.0.txt +121 -0
  6. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  7. data/LICENSES/MIT-0.txt +16 -0
  8. data/LICENSES/MIT.txt +21 -0
  9. data/REUSE.toml +42 -0
  10. data/exe/.gitkeep +0 -0
  11. data/ext/ratatui_ruby/.cargo/config.toml +13 -0
  12. data/ext/ratatui_ruby/.gitignore +4 -0
  13. data/ext/ratatui_ruby/Cargo.lock +1730 -0
  14. data/ext/ratatui_ruby/Cargo.toml +23 -0
  15. data/ext/ratatui_ruby/clippy.toml +7 -0
  16. data/ext/ratatui_ruby/extconf.rb +21 -0
  17. data/ext/ratatui_ruby/src/color.rs +82 -0
  18. data/ext/ratatui_ruby/src/errors.rs +28 -0
  19. data/ext/ratatui_ruby/src/events.rs +706 -0
  20. data/ext/ratatui_ruby/src/frame.rs +241 -0
  21. data/ext/ratatui_ruby/src/lib.rs +343 -0
  22. data/ext/ratatui_ruby/src/lib_header.rs +11 -0
  23. data/ext/ratatui_ruby/src/rendering.rs +158 -0
  24. data/ext/ratatui_ruby/src/string_width.rs +26 -0
  25. data/ext/ratatui_ruby/src/style.rs +469 -0
  26. data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
  27. data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
  28. data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
  29. data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
  30. data/ext/ratatui_ruby/src/terminal/queries.rs +249 -0
  31. data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
  32. data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
  33. data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
  34. data/ext/ratatui_ruby/src/text.rs +225 -0
  35. data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
  36. data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
  37. data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
  38. data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
  39. data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
  40. data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
  41. data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
  42. data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
  43. data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
  44. data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
  45. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
  46. data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
  47. data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
  48. data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
  49. data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
  50. data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
  51. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  52. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
  53. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
  54. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  55. data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
  56. data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
  57. data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
  58. data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
  59. data/lib/ratatui_ruby/3.2/ratatui_ruby.bundle +0 -0
  60. data/lib/ratatui_ruby/3.3/ratatui_ruby.bundle +0 -0
  61. data/lib/ratatui_ruby/3.4/ratatui_ruby.bundle +0 -0
  62. data/lib/ratatui_ruby/4.0/ratatui_ruby.bundle +0 -0
  63. data/lib/ratatui_ruby/backend/window_size.rb +50 -0
  64. data/lib/ratatui_ruby/backend.rb +59 -0
  65. data/lib/ratatui_ruby/buffer/cell.rb +212 -0
  66. data/lib/ratatui_ruby/buffer.rb +149 -0
  67. data/lib/ratatui_ruby/cell.rb +208 -0
  68. data/lib/ratatui_ruby/debug.rb +215 -0
  69. data/lib/ratatui_ruby/draw.rb +63 -0
  70. data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
  71. data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
  72. data/lib/ratatui_ruby/event/key/character.rb +53 -0
  73. data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
  74. data/lib/ratatui_ruby/event/key/media.rb +46 -0
  75. data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
  76. data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
  77. data/lib/ratatui_ruby/event/key/system.rb +47 -0
  78. data/lib/ratatui_ruby/event/key.rb +479 -0
  79. data/lib/ratatui_ruby/event/mouse.rb +291 -0
  80. data/lib/ratatui_ruby/event/none.rb +53 -0
  81. data/lib/ratatui_ruby/event/paste.rb +130 -0
  82. data/lib/ratatui_ruby/event/resize.rb +221 -0
  83. data/lib/ratatui_ruby/event/sync.rb +52 -0
  84. data/lib/ratatui_ruby/event.rb +163 -0
  85. data/lib/ratatui_ruby/frame.rb +257 -0
  86. data/lib/ratatui_ruby/labs/a11y.rb +182 -0
  87. data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
  88. data/lib/ratatui_ruby/labs.rb +47 -0
  89. data/lib/ratatui_ruby/layout/alignment.rb +91 -0
  90. data/lib/ratatui_ruby/layout/constraint.rb +337 -0
  91. data/lib/ratatui_ruby/layout/layout.rb +258 -0
  92. data/lib/ratatui_ruby/layout/position.rb +81 -0
  93. data/lib/ratatui_ruby/layout/rect.rb +733 -0
  94. data/lib/ratatui_ruby/layout/size.rb +62 -0
  95. data/lib/ratatui_ruby/layout.rb +29 -0
  96. data/lib/ratatui_ruby/list_state.rb +201 -0
  97. data/lib/ratatui_ruby/output_guard.rb +171 -0
  98. data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
  99. data/lib/ratatui_ruby/style/color.rb +149 -0
  100. data/lib/ratatui_ruby/style/style.rb +147 -0
  101. data/lib/ratatui_ruby/style.rb +19 -0
  102. data/lib/ratatui_ruby/symbols.rb +435 -0
  103. data/lib/ratatui_ruby/synthetic_events.rb +106 -0
  104. data/lib/ratatui_ruby/table_state.rb +251 -0
  105. data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
  106. data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
  107. data/lib/ratatui_ruby/terminal.rb +66 -0
  108. data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
  109. data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
  110. data/lib/ratatui_ruby/test_helper/event_injection.rb +244 -0
  111. data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
  112. data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
  113. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  114. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  115. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  116. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  117. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  118. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  119. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  120. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  121. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  122. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  123. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  124. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  125. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  126. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  127. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  128. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  129. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  130. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  131. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  132. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  133. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  134. data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
  135. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  136. data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
  137. data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
  138. data/lib/ratatui_ruby/test_helper.rb +115 -0
  139. data/lib/ratatui_ruby/text/line.rb +245 -0
  140. data/lib/ratatui_ruby/text/span.rb +158 -0
  141. data/lib/ratatui_ruby/text.rb +99 -0
  142. data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
  143. data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
  144. data/lib/ratatui_ruby/tui/core.rb +67 -0
  145. data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
  146. data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
  147. data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
  148. data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
  149. data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
  150. data/lib/ratatui_ruby/tui.rb +106 -0
  151. data/lib/ratatui_ruby/version.rb +12 -0
  152. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
  153. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
  154. data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
  155. data/lib/ratatui_ruby/widgets/block.rb +266 -0
  156. data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
  157. data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
  158. data/lib/ratatui_ruby/widgets/cell.rb +59 -0
  159. data/lib/ratatui_ruby/widgets/center.rb +71 -0
  160. data/lib/ratatui_ruby/widgets/chart.rb +172 -0
  161. data/lib/ratatui_ruby/widgets/clear.rb +66 -0
  162. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  163. data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
  164. data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
  165. data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
  166. data/lib/ratatui_ruby/widgets/list.rb +252 -0
  167. data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
  168. data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
  169. data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
  170. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
  171. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
  172. data/lib/ratatui_ruby/widgets/row.rb +123 -0
  173. data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
  174. data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
  175. data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
  176. data/lib/ratatui_ruby/widgets/table.rb +213 -0
  177. data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
  178. data/lib/ratatui_ruby/widgets.rb +43 -0
  179. data/lib/ratatui_ruby.rb +556 -0
  180. data/sig/examples/app_all_events/app.rbs +11 -0
  181. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  182. data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
  183. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  184. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  185. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  186. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  187. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  188. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  189. data/sig/examples/app_all_events/view.rbs +14 -0
  190. data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
  191. data/sig/examples/app_color_picker/app.rbs +17 -0
  192. data/sig/examples/app_external_editor/app.rbs +12 -0
  193. data/sig/examples/app_login_form/app.rbs +11 -0
  194. data/sig/examples/app_stateful_interaction/app.rbs +39 -0
  195. data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
  196. data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
  197. data/sig/examples/verify_readme_usage/app.rbs +17 -0
  198. data/sig/examples/widget_block_demo/app.rbs +38 -0
  199. data/sig/examples/widget_box_demo/app.rbs +17 -0
  200. data/sig/examples/widget_calendar_demo/app.rbs +17 -0
  201. data/sig/examples/widget_cell_demo/app.rbs +17 -0
  202. data/sig/examples/widget_chart_demo/app.rbs +17 -0
  203. data/sig/examples/widget_gauge_demo/app.rbs +17 -0
  204. data/sig/examples/widget_layout_split/app.rbs +16 -0
  205. data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
  206. data/sig/examples/widget_list_demo/app.rbs +17 -0
  207. data/sig/examples/widget_map_demo/app.rbs +17 -0
  208. data/sig/examples/widget_popup_demo/app.rbs +17 -0
  209. data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
  210. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
  211. data/sig/examples/widget_rect/app.rbs +18 -0
  212. data/sig/examples/widget_render/app.rbs +16 -0
  213. data/sig/examples/widget_rich_text/app.rbs +17 -0
  214. data/sig/examples/widget_scroll_text/app.rbs +17 -0
  215. data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
  216. data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
  217. data/sig/examples/widget_style_colors/app.rbs +20 -0
  218. data/sig/examples/widget_table_demo/app.rbs +17 -0
  219. data/sig/examples/widget_text_width/app.rbs +16 -0
  220. data/sig/generated/event_key_predicates.rbs +1348 -0
  221. data/sig/manifest.yaml +5 -0
  222. data/sig/patches/data.rbs +26 -0
  223. data/sig/patches/debugger__.rbs +8 -0
  224. data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
  225. data/sig/ratatui_ruby/backend.rbs +12 -0
  226. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  227. data/sig/ratatui_ruby/buffer.rbs +18 -0
  228. data/sig/ratatui_ruby/cell.rbs +44 -0
  229. data/sig/ratatui_ruby/clear.rbs +18 -0
  230. data/sig/ratatui_ruby/constraint.rbs +26 -0
  231. data/sig/ratatui_ruby/debug.rbs +45 -0
  232. data/sig/ratatui_ruby/draw.rbs +30 -0
  233. data/sig/ratatui_ruby/event.rbs +249 -0
  234. data/sig/ratatui_ruby/frame.rbs +23 -0
  235. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  236. data/sig/ratatui_ruby/labs.rbs +90 -0
  237. data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
  238. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  239. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  240. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  241. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  242. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  243. data/sig/ratatui_ruby/list_state.rbs +23 -0
  244. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  245. data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
  246. data/sig/ratatui_ruby/rect.rbs +17 -0
  247. data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
  248. data/sig/ratatui_ruby/session.rbs +93 -0
  249. data/sig/ratatui_ruby/style/color.rbs +22 -0
  250. data/sig/ratatui_ruby/style/style.rbs +29 -0
  251. data/sig/ratatui_ruby/symbols.rbs +141 -0
  252. data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
  253. data/sig/ratatui_ruby/table_state.rbs +27 -0
  254. data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
  255. data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
  256. data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
  257. data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
  258. data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
  259. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
  260. data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
  261. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
  262. data/sig/ratatui_ruby/test_helper.rbs +18 -0
  263. data/sig/ratatui_ruby/text/line.rbs +27 -0
  264. data/sig/ratatui_ruby/text/span.rbs +23 -0
  265. data/sig/ratatui_ruby/text.rbs +12 -0
  266. data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
  267. data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
  268. data/sig/ratatui_ruby/tui/core.rbs +23 -0
  269. data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
  270. data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
  271. data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
  272. data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
  273. data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
  274. data/sig/ratatui_ruby/tui.rbs +25 -0
  275. data/sig/ratatui_ruby/version.rbs +12 -0
  276. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  277. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  278. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  279. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  280. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  281. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  282. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  283. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  284. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  285. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  286. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  287. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  288. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  289. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  290. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  291. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  292. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  293. data/sig/ratatui_ruby/widgets.rbs +16 -0
  294. data/vendor/goodcop/base.yml +1047 -0
  295. metadata +738 -0
@@ -0,0 +1,109 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ //! Private terminal storage with safe accessor functions.
5
+ //!
6
+ //! This module provides thread-local storage for the terminal and draw snapshot,
7
+ //! with functions that route queries correctly:
8
+ //! - During draw: queries go to snapshot (no lock needed)
9
+ //! - Outside draw: queries go to live terminal
10
+
11
+ use super::query::{DrawSnapshot, LiveTerminal, TerminalQuery};
12
+ use super::wrapper::TerminalWrapper;
13
+ use std::cell::RefCell;
14
+
15
+ thread_local! {
16
+ static TERMINAL: RefCell<Option<TerminalWrapper>> = const { RefCell::new(None) };
17
+ static DRAW_SNAPSHOT: RefCell<Option<DrawSnapshot>> = const { RefCell::new(None) };
18
+ }
19
+
20
+ /// Check if we're currently inside a draw operation.
21
+ pub fn is_in_draw_mode() -> bool {
22
+ DRAW_SNAPSHOT.with(|cell| cell.borrow().is_some())
23
+ }
24
+
25
+ /// Execute a query. Routes to snapshot during draw, live terminal outside.
26
+ pub fn with_query<R, F>(f: F) -> Option<R>
27
+ where
28
+ F: Fn(&dyn TerminalQuery) -> R,
29
+ {
30
+ // During draw: use snapshot
31
+ DRAW_SNAPSHOT
32
+ .with(|cell| cell.borrow().as_ref().map(|snapshot| f(snapshot)))
33
+ .or_else(|| {
34
+ // Outside draw: use live terminal from thread-local storage
35
+ TERMINAL.with(|cell| {
36
+ cell.borrow_mut().as_mut().map(|t| {
37
+ let live = LiveTerminal::new(t);
38
+ f(&live)
39
+ })
40
+ })
41
+ })
42
+ }
43
+
44
+ /// Mutable access to terminal. Only works OUTSIDE draw.
45
+ pub fn with_terminal_mut<R, F>(f: F) -> Option<R>
46
+ where
47
+ F: FnOnce(&mut TerminalWrapper) -> R,
48
+ {
49
+ // During draw: reject
50
+ if is_in_draw_mode() {
51
+ return None;
52
+ }
53
+ TERMINAL.with(|cell| cell.borrow_mut().as_mut().map(f))
54
+ }
55
+
56
+ /// Execute draw with snapshot for queries.
57
+ pub fn lend_for_draw<R, F>(f: F) -> Option<R>
58
+ where
59
+ F: FnOnce(&mut TerminalWrapper) -> R,
60
+ {
61
+ // Take terminal out of storage
62
+ let mut terminal = TERMINAL.with(|cell| cell.borrow_mut().take())?;
63
+
64
+ // Capture snapshot BEFORE draw (while we have &mut)
65
+ let snapshot = DrawSnapshot::capture(&mut terminal);
66
+ DRAW_SNAPSHOT.with(|cell| *cell.borrow_mut() = Some(snapshot));
67
+
68
+ let result = f(&mut terminal);
69
+
70
+ // Clear snapshot, restore terminal
71
+ DRAW_SNAPSHOT.with(|cell| *cell.borrow_mut() = None);
72
+ TERMINAL.with(|cell| *cell.borrow_mut() = Some(terminal));
73
+
74
+ Some(result)
75
+ }
76
+
77
+ /// Set the terminal (used during initialization).
78
+ pub fn set_terminal(wrapper: TerminalWrapper) {
79
+ TERMINAL.with(|cell| {
80
+ *cell.borrow_mut() = Some(wrapper);
81
+ });
82
+ }
83
+
84
+ /// Take the terminal out of storage (used during cleanup).
85
+ pub fn take_terminal() -> Option<TerminalWrapper> {
86
+ TERMINAL.with(|cell| cell.borrow_mut().take())
87
+ }
88
+
89
+ /// Check if terminal is initialized.
90
+ pub fn is_initialized() -> bool {
91
+ TERMINAL.with(|cell| cell.borrow().is_some())
92
+ }
93
+
94
+ #[cfg(test)]
95
+ mod tests {
96
+ use crate::terminal::query::DrawSnapshot;
97
+ use ratatui::layout::Rect;
98
+
99
+ #[test]
100
+ fn test_is_in_draw_mode_false_by_default() {
101
+ assert!(!super::is_in_draw_mode());
102
+ }
103
+
104
+ #[test]
105
+ fn test_with_query_returns_none_when_not_initialized() {
106
+ let result = super::with_query(|q| q.size());
107
+ assert!(result.is_none());
108
+ }
109
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ //! Terminal wrapper enum for different backend types.
5
+
6
+ use ratatui::{
7
+ backend::{CrosstermBackend, TestBackend},
8
+ Terminal,
9
+ };
10
+ use std::io;
11
+
12
+ /// Unified terminal type supporting different backends.
13
+ pub enum TerminalWrapper {
14
+ Crossterm(Terminal<CrosstermBackend<io::Stdout>>),
15
+ Test(Terminal<TestBackend>),
16
+ }
@@ -0,0 +1,225 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ use crate::errors::type_error_with_context;
5
+ use crate::style::parse_style;
6
+ use magnus::{prelude::*, Error, Value};
7
+ use ratatui::text::{Line, Span};
8
+
9
+ /// Parses a Ruby value into a ratatui Text structure.
10
+ ///
11
+ /// Supports:
12
+ /// - String: Plain text without styling
13
+ /// - `Text::Span`: A single styled fragment
14
+ /// - `Text::Line`: A line composed of multiple spans
15
+ /// - Array: Array of `Text::Lines` or Strings
16
+ pub fn parse_text(value: Value) -> Result<Vec<Line<'static>>, Error> {
17
+ let ruby = magnus::Ruby::get().unwrap();
18
+
19
+ if value.is_nil() {
20
+ return Ok(vec![Line::from("")]);
21
+ }
22
+
23
+ // Check if it's a String
24
+ if let Ok(s) = String::try_convert(value) {
25
+ // Split on newlines and create a Line for each.
26
+ // We need to own the strings, so we convert each line string to a String
27
+ let lines: Vec<Line> = s
28
+ .split('\n')
29
+ .map(|line| Line::from(line.to_string()))
30
+ .collect();
31
+ return if lines.is_empty() {
32
+ Ok(vec![Line::from("")])
33
+ } else {
34
+ Ok(lines)
35
+ };
36
+ }
37
+
38
+ // Check if it's an Array
39
+ if let Some(arr) = magnus::RArray::from_value(value) {
40
+ let mut lines = Vec::new();
41
+ for i in 0..arr.len() {
42
+ let ruby = magnus::Ruby::get().unwrap();
43
+ let index = isize::try_from(i)
44
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
45
+ let elem: Value = arr.entry(index)?;
46
+
47
+ // Try to convert to String
48
+ if let Ok(s) = String::try_convert(elem) {
49
+ lines.push(Line::from(s));
50
+ } else if let Ok(line) = parse_line(elem) {
51
+ lines.push(line);
52
+ } else if let Ok(span) = parse_span(elem) {
53
+ lines.push(Line::from(vec![span]));
54
+ }
55
+ }
56
+ return if lines.is_empty() {
57
+ Ok(vec![Line::from("")])
58
+ } else {
59
+ Ok(lines)
60
+ };
61
+ }
62
+
63
+ // Try to parse as Line
64
+ if let Ok(line) = parse_line(value) {
65
+ return Ok(vec![line]);
66
+ }
67
+
68
+ // Try to parse as Span
69
+ if let Ok(span) = parse_span(value) {
70
+ return Ok(vec![Line::from(vec![span])]);
71
+ }
72
+
73
+ // Fallback: try to convert to string
74
+ match String::try_convert(value) {
75
+ Ok(s) => {
76
+ let lines: Vec<Line> = s
77
+ .split('\n')
78
+ .map(|line| Line::from(line.to_string()))
79
+ .collect();
80
+ if lines.is_empty() {
81
+ Ok(vec![Line::from("")])
82
+ } else {
83
+ Ok(lines)
84
+ }
85
+ }
86
+ Err(_) => Err(type_error_with_context(
87
+ &ruby,
88
+ "expected String, Text::Span, Text::Line, or Array of Text::Lines/Spans",
89
+ value,
90
+ )),
91
+ }
92
+ }
93
+
94
+ /// Parses a Ruby Span object into a ratatui Span.
95
+ pub fn parse_span(value: Value) -> Result<Span<'static>, Error> {
96
+ let ruby = magnus::Ruby::get().unwrap();
97
+
98
+ // Get class name
99
+ let class_obj: Value = value.funcall("class", ())?;
100
+ let class_name: String = class_obj.funcall("name", ())?;
101
+
102
+ if !class_name.contains("Span") {
103
+ return Err(type_error_with_context(
104
+ &ruby,
105
+ "expected a Text::Span object",
106
+ value,
107
+ ));
108
+ }
109
+
110
+ // Extract content and style from the Ruby Span
111
+ let content: Value = value.funcall("content", ())?;
112
+ let style_val: Value = value.funcall("style", ())?;
113
+
114
+ let content_str: String = content.funcall("to_s", ())?;
115
+ let style = parse_style(style_val)?;
116
+
117
+ Ok(Span::styled(content_str, style))
118
+ }
119
+
120
+ /// Parses a Ruby `Text::Line` object into a ratatui Line.
121
+ /// Also accepts `Text::Span` objects and auto-coerces them to a Line.
122
+ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
123
+ let ruby = magnus::Ruby::get().unwrap();
124
+
125
+ // Get class name
126
+ let class_obj: Value = value.funcall("class", ())?;
127
+ let class_name: String = class_obj.funcall("name", ())?;
128
+
129
+ // Auto-coerce Span to Line: wrap a single Span in a Line
130
+ if class_name.contains("Span") {
131
+ let span = parse_span(value)?;
132
+ return Ok(Line::from(vec![span]));
133
+ }
134
+
135
+ if !class_name.contains("Line") {
136
+ return Err(type_error_with_context(
137
+ &ruby,
138
+ "expected a Text::Line or Text::Span object",
139
+ value,
140
+ ));
141
+ }
142
+
143
+ // Extract spans from the Ruby Line
144
+ let spans_val: Value = value.funcall("spans", ())?;
145
+ // v0.7.0: Extract style from the Ruby Line
146
+ let style_val: Value = value.funcall("style", ())?;
147
+ // Extract alignment from the Ruby Line
148
+ let alignment_val: Value = value.funcall("alignment", ())?;
149
+
150
+ if spans_val.is_nil() {
151
+ return Ok(Line::from(""));
152
+ }
153
+
154
+ let spans_array = magnus::RArray::from_value(spans_val).ok_or_else(|| {
155
+ type_error_with_context(
156
+ &ruby,
157
+ "expected array of Spans in Text::Line.spans",
158
+ spans_val,
159
+ )
160
+ })?;
161
+
162
+ let mut spans = Vec::new();
163
+ for i in 0..spans_array.len() {
164
+ let ruby = magnus::Ruby::get().unwrap();
165
+ let index = isize::try_from(i)
166
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
167
+ let span_elem: Value = spans_array.entry(index)?;
168
+
169
+ // If it's a string, convert to span without style
170
+ if let Ok(s) = String::try_convert(span_elem) {
171
+ spans.push(Span::raw(s));
172
+ } else {
173
+ // Try to parse as Span object
174
+ if let Ok(span) = parse_span(span_elem) {
175
+ spans.push(span);
176
+ } else if let Ok(s) = String::try_convert(span_elem) {
177
+ // If it fails, try converting to string
178
+ spans.push(Span::raw(s));
179
+ }
180
+ }
181
+ }
182
+
183
+ let mut line = if spans.is_empty() {
184
+ Line::from("")
185
+ } else {
186
+ Line::from(spans)
187
+ };
188
+
189
+ // v0.7.0: Apply line-level style if present
190
+ if !style_val.is_nil() {
191
+ line = line.style(parse_style(style_val)?);
192
+ }
193
+
194
+ // Apply alignment if present
195
+ if !alignment_val.is_nil() {
196
+ if let Ok(alignment_sym) = magnus::Symbol::try_convert(alignment_val) {
197
+ let alignment_str = alignment_sym.name().map_err(|e| {
198
+ Error::new(
199
+ ruby.exception_type_error(),
200
+ format!("Invalid alignment symbol: {e}"),
201
+ )
202
+ })?;
203
+ let alignment = match alignment_str.as_ref() {
204
+ "left" => ratatui::layout::Alignment::Left,
205
+ "center" => ratatui::layout::Alignment::Center,
206
+ "right" => ratatui::layout::Alignment::Right,
207
+ _ => {
208
+ return Err(Error::new(
209
+ ruby.exception_arg_error(),
210
+ format!("Invalid alignment: {alignment_str}. Valid values: :left, :center, :right"),
211
+ ));
212
+ }
213
+ };
214
+ line = line.alignment(alignment);
215
+ }
216
+ }
217
+
218
+ Ok(line)
219
+ }
220
+
221
+ #[cfg(test)]
222
+ mod tests {
223
+ #[test]
224
+ fn test_parse_plain_string() {}
225
+ }
@@ -0,0 +1,169 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ use crate::style::{parse_bar_set, parse_block, parse_style};
5
+ use crate::text::{parse_line, parse_span};
6
+ use bumpalo::Bump;
7
+ use magnus::{prelude::*, Error, RArray, Symbol, Value};
8
+ use ratatui::buffer::Buffer;
9
+ use ratatui::{
10
+ layout::{Direction, Rect},
11
+ text::Line,
12
+ widgets::Widget,
13
+ widgets::{Bar, BarChart, BarGroup},
14
+ };
15
+
16
+ #[allow(clippy::too_many_lines)]
17
+ pub fn render(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
18
+ let bump = Bump::new();
19
+ let data_val: Value = node.funcall("data", ())?;
20
+ let bar_width: u16 = node.funcall("bar_width", ())?;
21
+ let bar_gap: u16 = node.funcall("bar_gap", ())?;
22
+ let group_gap: u16 = node.funcall("group_gap", ())?;
23
+ let max_val: Value = node.funcall("max", ())?;
24
+ let style_val: Value = node.funcall("style", ())?;
25
+ let block_val: Value = node.funcall("block", ())?;
26
+ let direction_sym: Symbol = node.funcall("direction", ())?;
27
+ let label_style_val: Value = node.funcall("label_style", ())?;
28
+ let value_style_val: Value = node.funcall("value_style", ())?;
29
+ let bar_set_val: Value = node.funcall("bar_set", ())?;
30
+
31
+ let direction = if direction_sym.to_string() == "horizontal" {
32
+ Direction::Horizontal
33
+ } else {
34
+ Direction::Vertical
35
+ };
36
+
37
+ let mut bar_chart = BarChart::default()
38
+ .bar_width(bar_width)
39
+ .bar_gap(bar_gap)
40
+ .group_gap(group_gap)
41
+ .direction(direction);
42
+
43
+ // Data parsing
44
+ let ruby = magnus::Ruby::get().unwrap();
45
+ if let Some(array) = RArray::from_value(data_val) {
46
+ for i in 0..array.len() {
47
+ let index = isize::try_from(i)
48
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
49
+ let group_obj: Value = array.entry(index)?;
50
+
51
+ let label_val: Value = group_obj.funcall("label", ())?;
52
+ let group_label: Line = if label_val.is_nil() {
53
+ Line::from("")
54
+ } else if let Ok(line) = parse_line(label_val) {
55
+ line
56
+ } else {
57
+ let label_str: String = label_val.funcall("to_s", ())?;
58
+ Line::from(label_str)
59
+ };
60
+
61
+ let bars_array: RArray = group_obj.funcall("bars", ())?;
62
+ let mut bars: Vec<Bar> = Vec::new();
63
+
64
+ for j in 0..bars_array.len() {
65
+ let bar_idx = isize::try_from(j)
66
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
67
+ let bar_obj: Value = bars_array.entry(bar_idx)?;
68
+
69
+ let value: u64 = bar_obj.funcall("value", ())?;
70
+ let mut bar = Bar::default().value(value);
71
+
72
+ let label_val: Value = bar_obj.funcall("label", ())?;
73
+ if !label_val.is_nil() {
74
+ // Try to parse as Line (rich text)
75
+ if let Ok(line) = parse_line(label_val) {
76
+ bar = bar.label(line);
77
+ } else if let Ok(span) = parse_span(label_val) {
78
+ bar = bar.label(Line::from(vec![span]));
79
+ } else {
80
+ // Fallback to string
81
+ let s: String = label_val.funcall("to_s", ())?;
82
+ let s_ref = bump.alloc_str(&s) as &str;
83
+ bar = bar.label(Line::from(s_ref));
84
+ }
85
+ }
86
+
87
+ let text_val: Value = bar_obj.funcall("text_value", ())?;
88
+ if !text_val.is_nil() {
89
+ // Try to parse as Line (rich text)
90
+ if let Ok(line) = parse_line(text_val) {
91
+ bar = bar.text_value(line);
92
+ } else if let Ok(span) = parse_span(text_val) {
93
+ bar = bar.text_value(Line::from(vec![span]));
94
+ } else {
95
+ // Fallback to string
96
+ let s: String = text_val.funcall("to_s", ())?;
97
+ let s_ref = bump.alloc_str(&s) as &str;
98
+ bar = bar.text_value(s_ref);
99
+ }
100
+ }
101
+
102
+ let style_val: Value = bar_obj.funcall("style", ())?;
103
+ if !style_val.is_nil() {
104
+ bar = bar.style(parse_style(style_val)?);
105
+ }
106
+
107
+ let val_style_val: Value = bar_obj.funcall("value_style", ())?;
108
+ if !val_style_val.is_nil() {
109
+ bar = bar.value_style(parse_style(val_style_val)?);
110
+ }
111
+
112
+ bars.push(bar);
113
+ }
114
+
115
+ let mut group = BarGroup::new(bars);
116
+ if !group_label.spans.is_empty() {
117
+ group = group.label(group_label);
118
+ }
119
+ bar_chart = bar_chart.data(group);
120
+ }
121
+ }
122
+
123
+ if !max_val.is_nil() {
124
+ let max: u64 = u64::try_convert(max_val)?;
125
+ bar_chart = bar_chart.max(max);
126
+ }
127
+
128
+ if !style_val.is_nil() {
129
+ bar_chart = bar_chart.style(parse_style(style_val)?);
130
+ }
131
+
132
+ if !block_val.is_nil() {
133
+ bar_chart = bar_chart.block(parse_block(block_val, &bump)?);
134
+ }
135
+
136
+ if !label_style_val.is_nil() {
137
+ bar_chart = bar_chart.label_style(parse_style(label_style_val)?);
138
+ }
139
+
140
+ if !value_style_val.is_nil() {
141
+ bar_chart = bar_chart.value_style(parse_style(value_style_val)?);
142
+ }
143
+
144
+ if !bar_set_val.is_nil() {
145
+ bar_chart = bar_chart.bar_set(parse_bar_set(bar_set_val, &bump)?);
146
+ }
147
+
148
+ bar_chart.render(area, buffer);
149
+ Ok(())
150
+ }
151
+
152
+ #[cfg(test)]
153
+ mod tests {
154
+ use super::*;
155
+ use ratatui::buffer::Buffer;
156
+ use ratatui::widgets::{BarChart, Widget};
157
+
158
+ #[test]
159
+ fn test_barchart_rendering() {
160
+ let data = [("B1", 10), ("B2", 20)];
161
+ let chart = BarChart::default().data(&data).bar_width(3);
162
+ let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
163
+ chart.render(Rect::new(0, 0, 10, 5), &mut buf);
164
+ // Should have bars rendered (non-space characters)
165
+ assert!(buf.content().iter().any(|c| c.symbol() != " "));
166
+ // Should have labels
167
+ assert!(buf.content().iter().any(|c| c.symbol().contains('B')));
168
+ }
169
+ }
@@ -0,0 +1,41 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ use crate::rendering::render_node;
5
+ use crate::style::parse_block;
6
+ use bumpalo::Bump;
7
+ use magnus::{prelude::*, Error, Value};
8
+ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
9
+
10
+ pub fn render(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
11
+ let bump = Bump::new();
12
+ let block = parse_block(node, &bump)?;
13
+ let block_clone = block.clone();
14
+
15
+ // Render the block itself (borders, styling)
16
+ block_clone.render(area, buffer);
17
+
18
+ // Get children and render them within the block's inner area
19
+ let children_val: Value = node.funcall("children", ())?;
20
+ let children_array = magnus::RArray::from_value(children_val);
21
+
22
+ if let Some(arr) = children_array {
23
+ if !arr.is_empty() {
24
+ // Calculate the inner area of the block (excluding borders and padding)
25
+ let inner = block.inner(area);
26
+
27
+ // Render each child in the block's inner area
28
+ for i in 0..arr.len() {
29
+ let ruby = magnus::Ruby::get().unwrap();
30
+ let index = isize::try_from(i)
31
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
32
+ let child: Value = arr.entry(index)?;
33
+ if let Err(e) = render_node(buffer, inner, child) {
34
+ eprintln!("Error rendering block child {i}: {e:?}");
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ Ok(())
41
+ }
@@ -0,0 +1,84 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ use crate::style::{parse_block, parse_style};
5
+ use bumpalo::Bump;
6
+ use magnus::{prelude::*, Error, Value};
7
+ use ratatui::{
8
+ buffer::Buffer,
9
+ layout::Rect,
10
+ widgets::calendar::{CalendarEventStore, Monthly},
11
+ widgets::Widget,
12
+ };
13
+ use std::convert::TryFrom;
14
+ use time::{Date, Month};
15
+
16
+ pub fn render(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
17
+ let bump = Bump::new();
18
+ let ruby = magnus::Ruby::get().unwrap();
19
+ let year: i32 = node.funcall("year", ())?;
20
+ let month_u8: u8 = node.funcall("month", ())?;
21
+ let events_val: Value = node.funcall("events", ())?;
22
+ let default_style_val: Value = node.funcall("default_style", ())?;
23
+ let header_style_val: Value = node.funcall("header_style", ())?;
24
+ let block_val: Value = node.funcall("block", ())?;
25
+ let show_weekdays_header: bool = node.funcall("show_weekdays_header", ())?;
26
+ let show_surrounding_val: Value = node.funcall("show_surrounding", ())?;
27
+
28
+ let month = Month::try_from(month_u8)
29
+ .map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))?;
30
+
31
+ let date = Date::from_calendar_date(year, month, 1)
32
+ .map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))?;
33
+
34
+ let mut event_store = CalendarEventStore::default();
35
+ if !events_val.is_nil() {
36
+ let events_hash = magnus::RHash::try_convert(events_val)?;
37
+ events_hash.foreach(|date_val: Value, style_val: Value| {
38
+ let year = date_val.funcall("year", ())?;
39
+ let month_u8: u8 = date_val.funcall("month", ())?;
40
+ let day: u8 = date_val.funcall("day", ())?;
41
+
42
+ let month = Month::try_from(month_u8)
43
+ .map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))?;
44
+ let date = Date::from_calendar_date(year, month, day)
45
+ .map_err(|e| Error::new(ruby.exception_arg_error(), e.to_string()))?;
46
+ let style = parse_style(style_val)?;
47
+
48
+ event_store.add(date, style);
49
+ Ok(magnus::r_hash::ForEach::Continue)
50
+ })?;
51
+ }
52
+
53
+ let mut calendar = Monthly::new(date, event_store);
54
+
55
+ let header_style = if header_style_val.is_nil() {
56
+ ratatui::style::Style::default()
57
+ } else {
58
+ parse_style(header_style_val)?
59
+ };
60
+
61
+ let show_month_header: bool = node.funcall("show_month_header", ())?;
62
+ if show_month_header {
63
+ calendar = calendar.show_month_header(header_style);
64
+ }
65
+
66
+ if show_weekdays_header {
67
+ calendar = calendar.show_weekdays_header(header_style);
68
+ }
69
+
70
+ if !show_surrounding_val.is_nil() {
71
+ calendar = calendar.show_surrounding(parse_style(show_surrounding_val)?);
72
+ }
73
+
74
+ if !default_style_val.is_nil() {
75
+ calendar = calendar.default_style(parse_style(default_style_val)?);
76
+ }
77
+
78
+ if !block_val.is_nil() {
79
+ calendar = calendar.block(parse_block(block_val, &bump)?);
80
+ }
81
+
82
+ calendar.render(area, buffer);
83
+ Ok(())
84
+ }