ratatui_ruby 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +10 -4
- data/CHANGELOG.md +79 -7
- data/README.md +37 -5
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +96 -22
- data/doc/application_testing.md +76 -30
- data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
- data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
- data/doc/contributors/architectural_overhaul/task.md +37 -0
- data/doc/contributors/design/ruby_frontend.md +288 -56
- data/doc/contributors/design/rust_backend.md +349 -54
- data/doc/contributors/developing_examples.md +134 -49
- data/doc/contributors/index.md +7 -5
- data/doc/contributors/v1.0.0_blockers.md +1729 -0
- data/doc/event_handling.md +11 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart_demo.png +0 -0
- data/doc/images/widget_block_demo.png +0 -0
- data/doc/images/widget_canvas_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_center_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_overlay_demo.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +11 -6
- data/doc/interactive_design.md +2 -2
- data/doc/quickstart.md +127 -165
- data/doc/terminal_limitations.md +92 -0
- data/doc/v0.7.0_migration.md +236 -0
- data/doc/why.md +93 -0
- data/examples/app_all_events/README.md +47 -27
- data/examples/app_all_events/app.rb +38 -35
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_entry.rb +17 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +9 -9
- data/examples/app_all_events/view/controls_view.rb +9 -7
- data/examples/app_all_events/view/counts_view.rb +13 -9
- data/examples/app_all_events/view/live_view.rb +9 -8
- data/examples/app_all_events/view/log_view.rb +11 -16
- data/examples/app_color_picker/README.md +84 -42
- data/examples/app_color_picker/app.rb +24 -62
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +45 -49
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/input.rb +99 -67
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +55 -26
- data/examples/app_login_form/README.md +49 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +33 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +49 -0
- data/examples/verify_quickstart_dsl/app.rb +2 -0
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +2 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +10 -4
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +8 -2
- data/examples/widget_barchart_demo/README.md +50 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +36 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_calendar_demo/app.rb +5 -1
- data/examples/widget_canvas_demo/README.md +27 -0
- data/examples/widget_canvas_demo/app.rb +123 -0
- data/examples/widget_cell_demo/README.md +36 -0
- data/examples/widget_cell_demo/app.rb +31 -24
- data/examples/widget_center_demo/README.md +29 -0
- data/examples/widget_center_demo/app.rb +116 -0
- data/examples/widget_chart_demo/README.md +41 -0
- data/examples/widget_chart_demo/app.rb +7 -2
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +91 -107
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
- data/examples/widget_overlay_demo/README.md +36 -0
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_logo_demo/app.rb +1 -1
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_render/app.rb +3 -3
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +62 -33
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +0 -1
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +4 -3
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +15 -1
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +113 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +238 -26
- data/ext/ratatui_ruby/src/frame.rs +116 -3
- data/ext/ratatui_ruby/src/lib.rs +37 -6
- data/ext/ratatui_ruby/src/rendering.rs +22 -21
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/terminal.rs +39 -15
- data/ext/ratatui_ruby/src/text.rs +13 -4
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
- data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/buffer/cell.rb +168 -0
- data/lib/ratatui_ruby/buffer.rb +15 -0
- data/lib/ratatui_ruby/cell.rb +4 -4
- data/lib/ratatui_ruby/event/key/character.rb +35 -0
- data/lib/ratatui_ruby/event/key/media.rb +44 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
- data/lib/ratatui_ruby/event/key/system.rb +45 -0
- data/lib/ratatui_ruby/event/key.rb +111 -51
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/frame.rb +100 -4
- data/lib/ratatui_ruby/layout/constraint.rb +95 -0
- data/lib/ratatui_ruby/layout/layout.rb +106 -0
- data/lib/ratatui_ruby/layout/rect.rb +118 -0
- data/lib/ratatui_ruby/layout.rb +19 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/cursor.rb +5 -0
- data/lib/ratatui_ruby/schema/gauge.rb +3 -1
- data/lib/ratatui_ruby/schema/layout.rb +1 -1
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +25 -4
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/rect.rb +43 -0
- data/lib/ratatui_ruby/schema/row.rb +66 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +29 -11
- data/lib/ratatui_ruby/schema/text.rb +96 -3
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/style/style.rb +81 -0
- data/lib/ratatui_ruby/style.rb +15 -0
- data/lib/ratatui_ruby/table_state.rb +90 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
- data/lib/ratatui_ruby/test_helper.rb +65 -358
- data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/core.rb +38 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
- data/lib/ratatui_ruby/tui.rb +75 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
- data/lib/ratatui_ruby/widgets/block.rb +192 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
- data/lib/ratatui_ruby/widgets/cell.rb +47 -0
- data/lib/ratatui_ruby/widgets/center.rb +59 -0
- data/lib/ratatui_ruby/widgets/chart.rb +185 -0
- data/lib/ratatui_ruby/widgets/clear.rb +54 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
- data/lib/ratatui_ruby/widgets/list.rb +127 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
- data/lib/ratatui_ruby/widgets/row.rb +68 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
- data/lib/ratatui_ruby/widgets/table.rb +141 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
- data/lib/ratatui_ruby/widgets.rb +40 -0
- data/lib/ratatui_ruby.rb +64 -57
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_all_events/view_state.rbs +1 -1
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
- data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
- data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +2 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/list.rbs +4 -2
- data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
- data/sig/ratatui_ruby/schema/rect.rbs +3 -0
- data/sig/ratatui_ruby/schema/row.rbs +22 -0
- data/sig/ratatui_ruby/schema/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +9 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +41 -48
- data/sig/ratatui_ruby/table_state.rbs +15 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
- data/sig/ratatui_ruby/test_helper.rbs +5 -4
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/core.rbs +14 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui.rbs +19 -0
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc.rake +7 -35
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +64 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +169 -48
- data/doc/contributors/dwim_dx.md +0 -366
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/examples/app_all_events/model/events.rb +0 -180
- data/examples/app_all_events/model/highlight.rb +0 -57
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
- data/examples/app_all_events/view_state.rb +0 -42
- data/examples/app_color_picker/scene.rb +0 -201
- data/examples/widget_block_padding/app.rb +0 -67
- data/examples/widget_block_titles/app.rb +0 -69
- data/examples/widget_list_styles/app.rb +0 -141
- data/examples/widget_table_flex/app.rb +0 -95
- data/lib/ratatui_ruby/session/autodoc.rb +0 -417
- data/lib/ratatui_ruby/session.rb +0 -163
- data/sig/examples/widget_block_padding/app.rbs +0 -11
- data/sig/examples/widget_block_titles/app.rbs +0 -11
- data/sig/examples/widget_list_styles/app.rbs +0 -11
- data/tasks/autodoc/inventory.rb +0 -61
- data/tasks/autodoc/notice.rb +0 -26
- data/tasks/autodoc/rbs.rb +0 -38
- data/tasks/autodoc/rdoc.rb +0 -45
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9da9703e1573d331115fd1635c1bf0564a6482c8d3d140c7b68230b56da8311
|
|
4
|
+
data.tar.gz: 41efa864847825eed6d28ee3b9292e923b3f3c97ce28841892a65123df72c16e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efc0e59765a7cfea0108fa79335ae79d4b0bcdb2c9eff93f6e1f5d83dca04724484ce322b45a6d3e6d6cf45f80868a998c9684a8362c3e11b9e1ff02e30eade1
|
|
7
|
+
data.tar.gz: be4afb8fe1b4bf7ed1ed4757521f8b90f68ca11c101aa69b184eae2de6807a2efa8a59fc2c19d0c47d3ed28f6afd89c906ba6480e824146b2b4ea8e7bc7211fa
|
data/.builds/ruby-3.2.yml
CHANGED
data/.builds/ruby-3.3.yml
CHANGED
data/.builds/ruby-3.4.yml
CHANGED
data/.builds/ruby-4.0.0.yml
CHANGED
data/AGENTS.md
CHANGED
|
@@ -19,9 +19,9 @@ Architecture:
|
|
|
19
19
|
## Stability & Compatibility
|
|
20
20
|
|
|
21
21
|
- **Project Status:** Pre-1.0.
|
|
22
|
-
- **User Base:**
|
|
23
|
-
- **Breaking Changes:** Backward compatibility is **NOT** a priority
|
|
24
|
-
- **Requirement:** All breaking changes **MUST** be explicitly documented in the [CHANGELOG.md](CHANGELOG.md)'s **Unreleased** section to ensure transparency as the project evolves toward 1.0.
|
|
22
|
+
- **User Base:** First external users (as of 2026-01-03).
|
|
23
|
+
- **Breaking Changes:** Backward compatibility is **NOT YET** a priority. Since there are few external users, you are encouraged to refactor APIs for better ergonomics and performance even if it breaks existing code. This will when we ship v1.0.0.
|
|
24
|
+
- **Requirement:** All breaking changes **MUST** be explicitly documented in the [CHANGELOG.md](CHANGELOG.md)'s **Unreleased** section to ensure transparency as the project evolves toward 1.0. Migration guides **MUST** be included in `doc/` when we release a new minor version.
|
|
25
25
|
|
|
26
26
|
## 1. Standards
|
|
27
27
|
|
|
@@ -44,6 +44,12 @@ Architecture:
|
|
|
44
44
|
- **Usage:**
|
|
45
45
|
- Runs default task (compile + test + lint): `bin/agent_rake`
|
|
46
46
|
- Runs specific task: `bin/agent_rake test:ruby` (for example)
|
|
47
|
+
- **Snapshot Testing:** When tests fail due to intentional behavior changes (not bugs), update snapshots:
|
|
48
|
+
- **Command:** `UPDATE_SNAPSHOTS=1 bin/agent_rake test:ruby`
|
|
49
|
+
- This regenerates all `snapshot/*{txt,ansi}` files to match current output
|
|
50
|
+
- **When to use:** After modifying widget rendering, changing default values, or updating UI behavior
|
|
51
|
+
- **When NOT to use:** For actual test failures that indicate bugs in your code
|
|
52
|
+
- After updating, verify the changes make sense by reviewing the diff of `.snapshot` files
|
|
47
53
|
- **Setup:** `bin/setup` must handle both Bundler and Cargo dependencies.
|
|
48
54
|
- **Git:** ALWAYS set `PAGER=cat` with `git`, `git`, etc.. **THIS IS CRITICAL!**
|
|
49
55
|
- **Rake:** Our rake tasks use `git ls-files`, so errors happen when you move or delete files. In this case, ask the user to stage changes for you.
|
|
@@ -85,7 +91,7 @@ The project follows a standard Gem layout with an `ext/` directory for Rust code
|
|
|
85
91
|
|
|
86
92
|
## 4. Committing
|
|
87
93
|
|
|
88
|
-
- Who commits: DON'T stage (DON'T `git add`). DON'T commit. DO suggest a commit message
|
|
94
|
+
- Who commits: DON'T stage (DON'T `git add`) unless explicitly instructed. DON'T commit unless explicitly instructed. DO suggest a commit message when you finish, even if not instructed..
|
|
89
95
|
- When: Before reporting the task as complete to the user, suggest the commit message.
|
|
90
96
|
- What: Consider not what you remember, but EVERYTHING in the `git diff` and `git diff --cached`.
|
|
91
97
|
- **Format:**
|
data/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,76 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
18
18
|
|
|
19
19
|
### Removed
|
|
20
20
|
|
|
21
|
+
## [0.7.0] - 2026-01-03
|
|
22
|
+
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> v0.7.0 contains significant breaking changes. See the [Migration Guide](doc/v0.7.0_migration.md) for upgrade instructions.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Rich Text in Table Cells**: `Table` cells (rows, header, footer) now accept `Text::Span` and `Text::Line` objects for per-character styling, matching List widget capabilities.
|
|
29
|
+
- **Row Wrapper**: New `Widgets::Row` class allows applying row-level styling (background color, style) and layout properties (height, top_margin, bottom_margin) to Table rows. Table rows can now be plain arrays or `Widgets::Row` objects.
|
|
30
|
+
- **Cell Wrapper**: New `Widgets::Cell` class wraps table cell content with optional cell-level styling. Distinct from `Buffer::Cell` which is for buffer inspection.
|
|
31
|
+
- **Line#width Method**: `Text::Line` now has a `width` instance method that calculates the display width in terminal cells using unicode-aware measurement. Useful for layout calculations with rich text.
|
|
32
|
+
- **render_rich_buffer**: New `TestHelper::Snapshot#render_rich_buffer` method returns the terminal buffer as an ANSI-encoded string with escape codes for colors and modifiers. Useful for debugging, custom assertions, or programmatic inspection beyond `assert_rich_snapshot`.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **Namespace Restructure (Breaking)**: Classes reorganized to match Ratatui's module hierarchy. See [Migration Guide](doc/v0.7.0_migration.md) for details:
|
|
37
|
+
- `RatatuiRuby::Rect` → `RatatuiRuby::Layout::Rect`
|
|
38
|
+
- `RatatuiRuby::Constraint` → `RatatuiRuby::Layout::Constraint`
|
|
39
|
+
- `RatatuiRuby::Layout` → `RatatuiRuby::Layout::Layout`
|
|
40
|
+
- `RatatuiRuby::Style` → `RatatuiRuby::Style::Style`
|
|
41
|
+
- `RatatuiRuby::Paragraph` → `RatatuiRuby::Widgets::Paragraph`
|
|
42
|
+
- `RatatuiRuby::Block` → `RatatuiRuby::Widgets::Block`
|
|
43
|
+
- `RatatuiRuby::Table` → `RatatuiRuby::Widgets::Table`
|
|
44
|
+
- `RatatuiRuby::List` → `RatatuiRuby::Widgets::List`
|
|
45
|
+
- *(and all other widgets)*
|
|
46
|
+
- **Session → TUI Rename (Breaking)**: `RatatuiRuby::Session` renamed to `RatatuiRuby::TUI` to better reflect its role as a facade/DSL. The `TUI` class now uses explicit factory methods (no metaprogramming) for improved IDE autocomplete support.
|
|
47
|
+
- **Buffer::Cell vs Widgets::Cell (Breaking)**: `RatatuiRuby::Cell` (buffer inspection) renamed to `RatatuiRuby::Buffer::Cell`. New `RatatuiRuby::Widgets::Cell` added for table cell construction.
|
|
48
|
+
- **Text::Line style field (Breaking)**: `Text::Line` now accepts a `style:` parameter for line-level styling, matching Ratatui's `Line` struct which has `style`, `alignment`, and `spans` fields.
|
|
49
|
+
- **Table highlight_style → row_highlight_style (Breaking)**: `Table` parameter `highlight_style:` renamed to `row_highlight_style:` to match Ratatui's API naming convention.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
### Removed
|
|
54
|
+
|
|
55
|
+
## [0.6.0] - 2026-01-03
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **Rich Text Support**: `List`, `Gauge`, `LineGauge`, and `BarChart` widgets now accept rich text objects (`Text::Span`, `Text::Line`) in addition to plain strings. This enables per-character styling, multi-colored labels, and complex text formatting matching Ratatui 0.30.0 capabilities.
|
|
60
|
+
- **ListItem Wrapper**: New `ListItem` data class allows applying row-level styling (background color) independent of text content. `List` items can now be `String`, `Text::Span`, `Text::Line`, or `ListItem` objects.
|
|
61
|
+
- **Non-Blocking Event Polling**: `RatatuiRuby.poll_event` now accepts an optional `timeout:` parameter (Float seconds). Use `timeout: 0.0` for non-blocking checks, or `timeout: 0.1` for fixed timesteps. Defaults to `0.016` (16ms) to preserve existing behavior.
|
|
62
|
+
- **Cursor Positioning**: `Frame#set_cursor_position(x, y)` sets the terminal's hardware cursor position. Using this method is essential for input fields where the user expects visual feedback on their cursor location.
|
|
63
|
+
- **Text Measurement**: `RatatuiRuby::Text.width(string)` calculates the display width of a string in terminal cells, correctly handling unicode including ASCII (1 cell), CJK full-width characters (2 cells), emoji (typically 2 cells), and zero-width combining marks (0 cells). This is essential for auto-sizing widgets and responsive layouts. Delegates to the same unicode-width logic that Ratatui uses internally.
|
|
64
|
+
- **Scroll Offset Control**: `List` and `Table` widgets now accept an optional `offset` parameter to control the viewport's scroll position. Use this for passive scrolling (viewing without selection) or calculating click-to-item mappings. When combined with a selection, Ratatui's natural scrolling may still adjust the viewport to keep the selection visible; set selection to `nil` for fully manual scroll control.
|
|
65
|
+
- **Rect Geometry Helpers**: `Rect#intersects?(other)` tests whether two rectangles overlap. `Rect#intersection(other)` returns the overlapping area as a new `Rect`, or `nil` if disjoint. Essential for viewport clipping and hit testing in component architectures.
|
|
66
|
+
- **Stateful Rendering**: `Frame#render_stateful_widget(widget, area, state)` renders widgets with mutable state objects (`ListState`, `TableState`, `ScrollbarState`). State objects persist across frames, enabling scroll offset read-back and selection tracking. Essential for mouse click-to-row hit testing. **Precedence rule:** State object properties override widget properties (`selected_index`, `offset`).
|
|
67
|
+
- **Full Keyboard Support**: Key events now recognize all keys supported by crossterm: function keys (`f1`–`f24`), navigation (`home`, `end`, `page_up`, `page_down`, `insert`, `delete`), locks (`caps_lock`, `scroll_lock`, `num_lock`), system (`print_screen`, `pause`, `menu`), media controls (`play`, `play_pause`, `track_next`, etc.), and individual modifier keys (`left_shift`, `right_control`, etc.). Previously unmapped keys returned `"unknown"`; they now return proper `snake_case` strings.
|
|
68
|
+
- **Key Categories**: `Event::Key` now has a `kind` attribute (`:standard`, `:function`, `:media`, `:modifier`, `:system`) for logical grouping. Category predicates (`media?`, `system?`, `function?`, `modifier?`, `standard?`) enable clean event routing without string parsing. The `unmodified?` method is an alias for `standard?`.
|
|
69
|
+
- **Smart Predicates (DWIM)**: Key predicates now "Do What I Mean" for media keys. `pause?` returns `true` for both system `pause` and `media_pause` keys. For strict matching, use `media_pause?` or compare `event.code` directly. This reduces boilerplate when responding to conceptual actions regardless of input method.
|
|
70
|
+
- **Modifier Key Predicates**: New methods `super?`, `hyper?`, and `meta?` check for these modifier keys. Platform aliases are provided for `super?`: `command?`/`cmd?` (macOS), `win?` (Windows), and `tux?` (Linux). These work for both modifier flags AND individual modifier key events (e.g., `left_super`). Additionally, `control?` aliases `ctrl?` and `option?` aliases `alt?`.
|
|
71
|
+
- **Navigation Aliases**: Convenient predicate aliases for common keys: `return?` for Enter, `back?` for Backspace, `del?` for Delete, `ins?` for Insert, `escape?` for Esc, `pgup?`/`pageup?` for Page Up, `pgdn?`/`pagedown?` for Page Down. The special `reverse_tab?` predicate matches both the `back_tab` key and `shift+tab` combinations.
|
|
72
|
+
- **Indexed Color Support**: `Style` now supports `Integer` values for `fg` and `bg`, allowing use of the Xterm 256-color palette (0-255). This includes standard ANSI colors (0-15), the 6x6x6 color cube (16-231), and the grayscale ramp (232-255).
|
|
73
|
+
- **Rich Snapshots**: `RatatuiRuby::TestHelper#assert_rich_snapshot` validates both content and styling by comparing against stored ANSI snapshots. This allows for visual regression testing that respects colors, bold, italics, and other terminal modifiers.
|
|
74
|
+
- **Semantic Style Assertions**: New testing helpers `assert_color(expected, x:, y:)`, `assert_cell_style(x, y, **style)`, and `assert_area_style(area, **style)` allow precise verification of terminal cell attributes without full-screen snapshots. Punchy convenience aliases like `assert_fg`/`assert_bg`, `assert_bold`, `assert_italic`, `assert_underlined`, and color-specific assertions (e.g., `assert_red`, `assert_bg_blue`) provide a more natural API for common testing patterns.
|
|
75
|
+
- **Buffer Debugging**: `RatatuiRuby::TestHelper#print_buffer` outputs the current terminal state to STDOUT with full ANSI color support, making it easier to debug rendering issues during test execution.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **Frozen Data Objects (Breaking)**: Events returned by `RatatuiRuby.poll_event` and `Cell` objects from `RatatuiRuby.get_cell_at` are now deeply frozen for Ractor compatibility. Code that mutates these objects (e.g., `event.modifiers << "custom"`) must copy the data before modifying. `Rect` was already frozen. Note: `Frame` and `Session` are *I/O handles* with side effects and remain intentionally non-shareable.
|
|
81
|
+
- **Semantic Exceptions (Breaking)**: Replaced generic `RuntimeError` with `RatatuiRuby::Error::Terminal` for backend/terminal failures and `RatatuiRuby::Error::Safety` for API contract violations (like using `Frame` outside `draw`). This allows finer-grained error handling but breaks code explicitly rescuing `RuntimeError`. `ArgumentError` works as before.
|
|
82
|
+
- **Media Key Codes (Breaking)**: All media key codes now use a consistent `media_` prefix: `play` → `media_play`, `stop` → `media_stop`, `play_pause` → `media_play_pause`, etc. Code comparing against literal media key strings must be updated. Use the Smart Predicates (`play?`, `stop?`) for backward-compatible behavior.
|
|
83
|
+
- **`Key#char` Return Value (Breaking)**: `char` now returns `nil` for non-printable keys (previously returned `""`). Code relying on `event.char.empty?` must change to `event.char.nil?` or use `event.text?` instead.
|
|
84
|
+
|
|
85
|
+
### Fixed
|
|
86
|
+
|
|
87
|
+
- **Frame Safety**: Calling methods on a `Frame` stored outside of a `draw` block now correctly raises a `RatatuiRuby::Error::Safety` (subclass of `RatatuiRuby::Error`) instead instead of causing undefined behavior or crashes. This ensures memory safety by preventing use-after-free scenarios with the underlying Rust frame.
|
|
88
|
+
|
|
89
|
+
### Removed
|
|
90
|
+
|
|
21
91
|
## [0.5.0] - 2026-01-01
|
|
22
92
|
|
|
23
93
|
### Added
|
|
@@ -285,10 +355,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
285
355
|
- **Input Handling**: Robust handling for both Keyboard and Mouse events.
|
|
286
356
|
- **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
|
|
287
357
|
|
|
288
|
-
[Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/
|
|
289
|
-
[0.
|
|
290
|
-
[0.
|
|
291
|
-
[0.
|
|
292
|
-
[0.
|
|
293
|
-
[0.
|
|
294
|
-
[0.
|
|
358
|
+
[Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/HEAD
|
|
359
|
+
[0.7.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.7.0
|
|
360
|
+
[0.6.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.6.0
|
|
361
|
+
[0.5.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.5.0
|
|
362
|
+
[0.4.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.4.0
|
|
363
|
+
[0.3.1]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.3.1
|
|
364
|
+
[0.3.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.3.0
|
|
365
|
+
[0.2.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.2.0
|
|
366
|
+
[0.1.0]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.1.0
|
data/README.md
CHANGED
|
@@ -23,7 +23,9 @@ Mailing List: Announcements](https://img.shields.io/badge/mailing_list-announcem
|
|
|
23
23
|
> [!WARNING]
|
|
24
24
|
> **ratatui_ruby** is currently in **BETA**. The API may change between minor versions.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
**[Why RatatuiRuby?](./doc/why.md)** — Native Rust performance, zero runtime overhead, and Ruby's expressiveness. [See how we compare](./doc/why.md) to CharmRuby, raw Rust, and Go.
|
|
27
|
+
|
|
28
|
+
Please join the **announce** mailing list at https://lists.sr.ht/~kerrick/ratatui_ruby-announce to stay up-to-date on new releases and announcements. See the [`trunk` branch](https://git.sr.ht/~kerrick/ratatui_ruby/tree/trunk) for pre-release updates.
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
## Compatibility
|
|
@@ -63,8 +65,8 @@ gem install ratatui_ruby
|
|
|
63
65
|
|
|
64
66
|
**ratatui_ruby** uses an immediate-mode API. You describe your UI using Ruby objects and call `draw` in a loop.
|
|
65
67
|
|
|
68
|
+
<!-- SYNC:START:examples/verify_readme_usage/app.rb:main -->
|
|
66
69
|
```ruby
|
|
67
|
-
require "ratatui_ruby"
|
|
68
70
|
RatatuiRuby.run do |tui|
|
|
69
71
|
loop do
|
|
70
72
|
tui.draw do |frame|
|
|
@@ -81,18 +83,46 @@ RatatuiRuby.run do |tui|
|
|
|
81
83
|
frame.area
|
|
82
84
|
)
|
|
83
85
|
end
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
case tui.poll_event
|
|
87
|
+
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
88
|
+
break
|
|
89
|
+
else
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
86
92
|
end
|
|
87
93
|
end
|
|
88
94
|
```
|
|
95
|
+
<!-- SYNC:END -->
|
|
96
|
+
|
|
97
|
+

|
|
89
98
|
|
|
90
99
|
For a full tutorial, see [the Quickstart](./doc/quickstart.md). For an explanation of the application architecture, see [Application Architecture](./doc/application_architecture.md).
|
|
91
100
|
|
|
92
101
|
|
|
102
|
+
## Features
|
|
103
|
+
|
|
104
|
+
**ratatui_ruby** gives you 20+ widgets out of the box:
|
|
105
|
+
|
|
106
|
+
| Category | Widgets |
|
|
107
|
+
|----------|---------|
|
|
108
|
+
| **Data Display** | Table, List, Chart, Bar Chart, Sparkline, Calendar |
|
|
109
|
+
| **Layout** | Block, Tabs, Scrollbar, Center, Overlay, Clear |
|
|
110
|
+
| **Input** | Gauge, Line Gauge |
|
|
111
|
+
| **Text** | Paragraph, Rich Text (Spans + Lines), Cursor |
|
|
112
|
+
| **Canvas** | Shapes (Line, Circle, Rectangle, Map), Custom Drawing |
|
|
113
|
+
|
|
114
|
+
Plus: flexible layouts with constraints, full keyboard/mouse/paste/resize events, snapshot testing, and hex/RGB/256-color support.
|
|
115
|
+
|
|
116
|
+
|
|
93
117
|
## Documentation
|
|
94
118
|
|
|
95
|
-
|
|
119
|
+
| Resource | Description |
|
|
120
|
+
|----------|-------------|
|
|
121
|
+
| [Quickstart](./doc/quickstart.md) | Get running in 5 minutes |
|
|
122
|
+
| [Widget Gallery](./doc/quickstart.md#widget-demos) | Every widget with examples |
|
|
123
|
+
| [Application Architecture](./doc/application_architecture.md) | Patterns for scaling your app |
|
|
124
|
+
| [API Reference](./doc/index.md) | Full RDoc documentation |
|
|
125
|
+
| [Wiki](https://man.sr.ht/~kerrick/ratatui_ruby) | Guides and community resources |
|
|
96
126
|
|
|
97
127
|
|
|
98
128
|
## Contributing
|
|
@@ -103,6 +133,8 @@ Issues for the underlying Rust library should be filed at [ratatui/ratatui](http
|
|
|
103
133
|
|
|
104
134
|
Want to help develop **ratatui_ruby**? Check out the [contribution guide on the wiki](https://man.sr.ht/~kerrick/ratatui_ruby/contributing.md).
|
|
105
135
|
|
|
136
|
+
**Note**: Active development happens on the `trunk` branch. Use `trunk` if you are a contributor or want the latest cutting-edge features. `stable` is for stable releases only.
|
|
137
|
+
|
|
106
138
|
|
|
107
139
|
## Copyright & License
|
|
108
140
|
|
data/REUSE.toml
CHANGED
|
@@ -22,16 +22,11 @@ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
|
22
22
|
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
23
23
|
|
|
24
24
|
[[annotations]]
|
|
25
|
-
path = '
|
|
25
|
+
path = '**/snapshots/*.txt'
|
|
26
26
|
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
27
27
|
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
28
28
|
|
|
29
29
|
[[annotations]]
|
|
30
|
-
path = '
|
|
31
|
-
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
32
|
-
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
33
|
-
|
|
34
|
-
[[annotations]]
|
|
35
|
-
path = 'test/examples/app_all_events/snapshots/*.txt'
|
|
30
|
+
path = '**/snapshots/*.ansi'
|
|
36
31
|
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
37
32
|
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
@@ -44,7 +44,7 @@ Need granular control? You can initialize and restore the terminal yourself. Use
|
|
|
44
44
|
RatatuiRuby.init_terminal
|
|
45
45
|
begin
|
|
46
46
|
RatatuiRuby.draw do |frame|
|
|
47
|
-
frame.render_widget(RatatuiRuby::Paragraph.new(text: "Hello"), frame.area)
|
|
47
|
+
frame.render_widget(RatatuiRuby::Widgets::Paragraph.new(text: "Hello"), frame.area)
|
|
48
48
|
end
|
|
49
49
|
ensure
|
|
50
50
|
RatatuiRuby.restore_terminal
|
|
@@ -52,13 +52,48 @@ ensure
|
|
|
52
52
|
end
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### Stateful Widgets
|
|
56
|
+
|
|
57
|
+
Most widgets are stateless configuration. You create them, render them, and they are gone. However, the **runtime status** of some widgets (like Lists and Tables) must persist across frames (e.g., scroll offsets or selection).
|
|
58
|
+
|
|
59
|
+
**The Problem:** If you re-create a List configuration every frame, you lose the context of where it was scrolled or what was selected. If Ratatui auto-scrolls to a selection, you can't read that new offset back from an immutable input widget.
|
|
60
|
+
|
|
61
|
+
**The Solution:** Use "Stateful Rendering". You create a mutable State object (Output/Status) once and pass it to `render_stateful_widget`. **The Widget configuration (Input) is still mandatory**, but the State object (passed separately) captures the runtime changes.
|
|
62
|
+
|
|
63
|
+
> [!IMPORTANT]
|
|
64
|
+
> **Precedence Rule:** When using `render_stateful_widget`, the **State object is the single source of truth** for selection and offset. Widget properties (`selected_index`, `selected_row`, `offset`) are **ignored**.
|
|
65
|
+
>
|
|
66
|
+
> For example: `list(selected_index: 0)` with `state.select(5)` → Item 5 is highlighted, not Item 0.
|
|
67
|
+
|
|
68
|
+
**Use Case:** When you need to read back the scroll offset (e.g., for mouse hit testing) or persist selection without managing indexes manually.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Initialize state once
|
|
72
|
+
@list_state = RatatuiRuby::ListState.new
|
|
73
|
+
|
|
74
|
+
RatatuiRuby.run do |tui|
|
|
75
|
+
loop do
|
|
76
|
+
tui.draw do |frame|
|
|
77
|
+
# Create immutable widget (selected_index is ignored in stateful mode)
|
|
78
|
+
list = tui.list(items: ["A", "B", "C"])
|
|
79
|
+
|
|
80
|
+
# Render with state — state takes precedence
|
|
81
|
+
frame.render_stateful_widget(list, frame.area, @list_state)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Read back offset calculated by Ratatui
|
|
85
|
+
puts "Current Scroll Offset: #{@list_state.offset}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
55
90
|
### API Convenience
|
|
56
91
|
|
|
57
92
|
Writing UI trees involves nesting many widgets.
|
|
58
93
|
|
|
59
|
-
**The Problem:** Explicitly namespacing `RatatuiRuby::` for every widget (e.g., `RatatuiRuby::Paragraph.new`) is tedious. It creates visual noise that hides your layout structure.
|
|
94
|
+
**The Problem:** Explicitly namespacing `RatatuiRuby::` for every widget (e.g., `RatatuiRuby::Widgets::Paragraph.new`) is tedious. It creates visual noise that hides your layout structure.
|
|
60
95
|
|
|
61
|
-
**The Solution:** The
|
|
96
|
+
**The Solution:** The TUI API (`tui`) provides shorthand factories for every widget. It yields a TUI object to your block.
|
|
62
97
|
|
|
63
98
|
```ruby
|
|
64
99
|
RatatuiRuby.run do |tui|
|
|
@@ -112,31 +147,31 @@ RatatuiRuby.run do
|
|
|
112
147
|
loop do
|
|
113
148
|
RatatuiRuby.draw do |frame|
|
|
114
149
|
# Manual split
|
|
115
|
-
rects = RatatuiRuby::Layout.split(
|
|
150
|
+
rects = RatatuiRuby::Layout::Layout.split(
|
|
116
151
|
frame.area,
|
|
117
152
|
direction: :horizontal,
|
|
118
153
|
constraints: [
|
|
119
|
-
RatatuiRuby::Constraint.length(20),
|
|
120
|
-
RatatuiRuby::Constraint.min(0)
|
|
154
|
+
RatatuiRuby::Layout::Constraint.length(20),
|
|
155
|
+
RatatuiRuby::Layout::Constraint.min(0)
|
|
121
156
|
]
|
|
122
157
|
)
|
|
123
158
|
|
|
124
159
|
frame.render_widget(
|
|
125
|
-
RatatuiRuby::Paragraph.new(
|
|
160
|
+
RatatuiRuby::Widgets::Paragraph.new(
|
|
126
161
|
text: RatatuiRuby::Text::Line.new(spans: [
|
|
127
|
-
RatatuiRuby::Text::Span.new(content: "Side", style: RatatuiRuby::Style.new(fg: :blue)),
|
|
162
|
+
RatatuiRuby::Text::Span.new(content: "Side", style: RatatuiRuby::Style::Style.new(fg: :blue)),
|
|
128
163
|
RatatuiRuby::Text::Span.new(content: "bar")
|
|
129
164
|
]),
|
|
130
|
-
block: RatatuiRuby::Block.new(borders: [:all], title: "Nav")
|
|
165
|
+
block: RatatuiRuby::Widgets::Block.new(borders: [:all], title: "Nav")
|
|
131
166
|
),
|
|
132
167
|
rects[0]
|
|
133
168
|
)
|
|
134
169
|
|
|
135
170
|
frame.render_widget(
|
|
136
|
-
RatatuiRuby::Paragraph.new(
|
|
171
|
+
RatatuiRuby::Widgets::Paragraph.new(
|
|
137
172
|
text: "Main Content",
|
|
138
|
-
style: RatatuiRuby::Style.new(fg: :green),
|
|
139
|
-
block: RatatuiRuby::Block.new(borders: [:all], title: "Content")
|
|
173
|
+
style: RatatuiRuby::Style::Style.new(fg: :green),
|
|
174
|
+
block: RatatuiRuby::Widgets::Block.new(borders: [:all], title: "Content")
|
|
140
175
|
),
|
|
141
176
|
rects[1]
|
|
142
177
|
)
|
|
@@ -148,29 +183,68 @@ RatatuiRuby.run do
|
|
|
148
183
|
end
|
|
149
184
|
```
|
|
150
185
|
|
|
186
|
+
## Thread and Ractor Safety
|
|
187
|
+
|
|
188
|
+
Building for Ruby 4.0's parallel future? Know which objects can travel between Ractors.
|
|
189
|
+
|
|
190
|
+
### Data Objects (Shareable)
|
|
191
|
+
|
|
192
|
+
These are deeply frozen and `Ractor.shareable?`. Include them in immutable Models/Messages freely:
|
|
193
|
+
|
|
194
|
+
| Object | Source |
|
|
195
|
+
|--------|--------|
|
|
196
|
+
| `Event::*` | `poll_event` |
|
|
197
|
+
| `Cell` | `get_cell_at` |
|
|
198
|
+
| `Rect` | `Layout.split`, `Frame#area` |
|
|
199
|
+
|
|
200
|
+
### I/O Handles (Not Shareable)
|
|
201
|
+
|
|
202
|
+
These have side effects and are intentionally not shareable:
|
|
203
|
+
|
|
204
|
+
| Object | Valid Usage |
|
|
205
|
+
|--------|-------------|
|
|
206
|
+
| `TUI` | Cache in `@tui` during run loop. Don't include in Models. |
|
|
207
|
+
| `Frame` | Pass to helpers during draw block. Invalid after block returns. |
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# Good: Cache session in instance variable
|
|
211
|
+
RatatuiRuby.run do |tui|
|
|
212
|
+
@tui = tui
|
|
213
|
+
loop { render; handle_input }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Bad: Include in immutable Model (won't work with Ractors)
|
|
217
|
+
Model = Data.define(:tui, :count) # Don't do this
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
|
|
151
221
|
## Reference Architectures
|
|
152
222
|
|
|
153
223
|
Simple scripts work well with valid linear code. Complex apps need structure.
|
|
154
224
|
|
|
155
225
|
We provide these reference architectures to inspire you:
|
|
156
226
|
|
|
157
|
-
###
|
|
227
|
+
### Model-View-Update
|
|
158
228
|
|
|
159
229
|
**Source:** [examples/app_all_events](../examples/app_all_events/README.md)
|
|
160
230
|
|
|
161
|
-
This pattern
|
|
162
|
-
* **Model:**
|
|
163
|
-
* **
|
|
164
|
-
* **
|
|
231
|
+
This pattern implements unidirectional data flow inspired by The Elm Architecture:
|
|
232
|
+
* **Model:** A single immutable `Data.define` object holding all application state.
|
|
233
|
+
* **Msg:** Semantic value objects that decouple raw events from business logic.
|
|
234
|
+
* **Update:** A pure function that computes the next state: `Update.call(msg, model) -> Model`.
|
|
235
|
+
* **View:** Pure rendering logic that accepts the immutable Model.
|
|
165
236
|
|
|
166
|
-
Use this when
|
|
237
|
+
Use this when you want predictable state management and easy-to-test logic.
|
|
167
238
|
|
|
168
|
-
###
|
|
239
|
+
### Component-Based
|
|
169
240
|
|
|
170
241
|
**Source:** [examples/app_color_picker](../examples/app_color_picker/README.md)
|
|
171
242
|
|
|
172
|
-
This pattern addresses the difficulty of mouse interaction and
|
|
173
|
-
* **
|
|
174
|
-
* **
|
|
243
|
+
This pattern addresses the difficulty of mouse interaction and complex UI orchestration:
|
|
244
|
+
* **Component Contract:** Every UI element implements `render(tui, frame, area)` and `handle_event(event)`.
|
|
245
|
+
* **Encapsulated Hit Testing:** Components cache their render area and check `contains?` internally.
|
|
246
|
+
* **Symbolic Signals:** `handle_event` returns semantic symbols (`:consumed`, `:submitted`) instead of just booleans.
|
|
247
|
+
* **Container (Mediator):** A parent container routes events via Chain of Responsibility and coordinates cross-component effects.
|
|
175
248
|
|
|
176
249
|
Use this when you need rich interactivity (mouse clicks, drag-and-drop) or complex dynamic layouts.
|
|
250
|
+
|
data/doc/application_testing.md
CHANGED
|
@@ -8,15 +8,11 @@ This guide explains how to test your RatatuiRuby applications using the provided
|
|
|
8
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
You need to verify that your application looks and behaves correctly. Manually checking every character on a terminal screen is tedious. Dealing with race conditions and complex state management in tests creates friction.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The `TestHelper` module solves this. It provides a headless "test terminal" to capture output and a suite of robust assertions to verify state.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Inspect the cursor position.
|
|
18
|
-
|
|
19
|
-
- Simulate user input (using `inject_event`).
|
|
15
|
+
Use it to write fast, deterministic tests for your TUI applications.
|
|
20
16
|
|
|
21
17
|
## Setup
|
|
22
18
|
|
|
@@ -36,18 +32,20 @@ class MyApplicationTest < Minitest::Test
|
|
|
36
32
|
end
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
##
|
|
35
|
+
## Writing a View Test
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
To test a view or widget, wrap your assertions in `with_test_terminal`. This sets up a temporary, in-memory backend for Ratatui to draw to.
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
1. **Initialize the terminal:** Call `with_test_terminal`.
|
|
40
|
+
2. **Render your code:** Instantiate your widget and draw it to a frame.
|
|
41
|
+
3. **Assert output:** Check the `buffer_content` against your expectations.
|
|
44
42
|
|
|
45
43
|
```ruby
|
|
46
44
|
def test_rendering
|
|
47
45
|
# Uses default 80x24 terminal
|
|
48
46
|
with_test_terminal do
|
|
49
47
|
# 1. Instantiate your widget
|
|
50
|
-
widget = RatatuiRuby::Paragraph.new(text: "Hello World")
|
|
48
|
+
widget = RatatuiRuby::Widgets::Paragraph.new(text: "Hello World")
|
|
51
49
|
|
|
52
50
|
# 2. Render it using the Frame API
|
|
53
51
|
RatatuiRuby.draw do |frame|
|
|
@@ -55,49 +53,97 @@ def test_rendering
|
|
|
55
53
|
end
|
|
56
54
|
|
|
57
55
|
# 3. Assert on the output
|
|
58
|
-
assert_includes buffer_content
|
|
56
|
+
assert_includes buffer_content.first, "Hello World"
|
|
59
57
|
end
|
|
60
58
|
end
|
|
61
59
|
```
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
For the full API list, including `buffer_content` and `cursor_position`, see [RatatuiRuby::TestHelper::Terminal](../lib/ratatui_ruby/test_helper/terminal.rb).
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
rows = buffer_content
|
|
69
|
-
assert_equal "Title", rows[0].strip
|
|
70
|
-
assert_match /Results: \d+/, rows[2]
|
|
71
|
-
```
|
|
63
|
+
## Verifying Styles
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
You often need to check colors and modifiers (bold, italic) to ensure your highlighting logic works.
|
|
74
66
|
|
|
75
|
-
|
|
67
|
+
Use `assert_fg_color`, `assert_bg_color`, and modifier helpers like `assert_bold`.
|
|
76
68
|
|
|
77
69
|
```ruby
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
# Assert specific cell style
|
|
71
|
+
assert_fg_color(:red, 0, 0)
|
|
72
|
+
assert_bold(0, 0)
|
|
73
|
+
|
|
74
|
+
# Or check a whole area
|
|
75
|
+
assert_area_style({ x: 0, y: 0, w: 10, h: 1 }, bg: :blue)
|
|
81
76
|
```
|
|
82
77
|
|
|
83
|
-
|
|
78
|
+
See [RatatuiRuby::TestHelper::StyleAssertions](../lib/ratatui_ruby/test_helper/style_assertions.rb) for the comprehensive list of style helpers.
|
|
79
|
+
|
|
80
|
+
## Simulating Input
|
|
81
|
+
|
|
82
|
+
You need to test user interactions like typing or clicking. Stubbing `poll_event` directly is brittle.
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
Use `inject_event` to push mock events into the queue. This ensures safe, deterministic handling of input.
|
|
86
85
|
|
|
87
86
|
> [!IMPORTANT]
|
|
88
|
-
>
|
|
87
|
+
> Call `inject_event` inside a `with_test_terminal` block to avoid race conditions.
|
|
89
88
|
|
|
90
89
|
```ruby
|
|
91
90
|
with_test_terminal do
|
|
92
91
|
# Simulate 'q' key press
|
|
93
92
|
inject_event("key", { code: "q" })
|
|
94
93
|
|
|
95
|
-
#
|
|
94
|
+
# The application receives the 'q' event
|
|
96
95
|
event = RatatuiRuby.poll_event
|
|
97
96
|
assert_equal "q", event.code
|
|
98
97
|
end
|
|
99
98
|
```
|
|
100
99
|
|
|
100
|
+
See [RatatuiRuby::TestHelper::EventInjection](../lib/ratatui_ruby/test_helper/event_injection.rb) for helper methods like `inject_keys` and `inject_click`.
|
|
101
|
+
|
|
102
|
+
## Snapshot Testing
|
|
103
|
+
|
|
104
|
+
Snapshots let you verify complex layouts without manually asserting every line.
|
|
105
|
+
|
|
106
|
+
Use `assert_snapshot` to compare the current screen against a stored reference file.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
with_test_terminal do
|
|
110
|
+
MyApp.new.run
|
|
111
|
+
assert_snapshot("dashboard_view")
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Handling Non-Determinism
|
|
116
|
+
|
|
117
|
+
Snapshots must be deterministic. Random data or current timestamps will cause test failures ("flakes").
|
|
118
|
+
|
|
119
|
+
To prevent this:
|
|
120
|
+
1. **Seed Randomness:** Use a fixed seed for any RNG.
|
|
121
|
+
2. **Stub Time:** Force the application to use a static time.
|
|
122
|
+
|
|
123
|
+
For detailed strategies and code examples, see [RatatuiRuby::TestHelper::Snapshot](../lib/ratatui_ruby/test_helper/snapshot.rb).
|
|
124
|
+
|
|
125
|
+
## Isolated View Testing
|
|
126
|
+
|
|
127
|
+
Sometimes you want to test a single view component without spinning up the full `TestTerminal` engine.
|
|
128
|
+
|
|
129
|
+
Use `MockFrame` and `StubRect` to test render logic in isolation.
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
def test_logs_view
|
|
133
|
+
frame = RatatuiRuby::TestHelper::TestDoubles::MockFrame.new
|
|
134
|
+
area = RatatuiRuby::TestHelper::TestDoubles::StubRect.new(width: 40, height: 10)
|
|
135
|
+
|
|
136
|
+
# Call your view directly
|
|
137
|
+
MyView.new.render(frame, area)
|
|
138
|
+
|
|
139
|
+
# Inspect what was rendered
|
|
140
|
+
rendered = frame.rendered_widgets.first
|
|
141
|
+
assert_equal "Logs", rendered[:widget].block.title
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
See [RatatuiRuby::TestHelper::TestDoubles](../lib/ratatui_ruby/test_helper/test_doubles.rb).
|
|
146
|
+
|
|
101
147
|
## Example
|
|
102
148
|
|
|
103
|
-
|
|
149
|
+
Check out the [examples directory](../examples/) for fully tested applications showcasing these patterns.
|