ratatui_ruby 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +14 -12
- data/.builds/ruby-3.3.yml +14 -12
- data/.builds/ruby-3.4.yml +14 -12
- data/.builds/ruby-4.0.0.yml +14 -12
- data/AGENTS.md +89 -132
- data/CHANGELOG.md +223 -1
- data/README.md +23 -16
- data/REUSE.toml +20 -0
- data/doc/application_architecture.md +176 -0
- data/doc/application_testing.md +17 -10
- data/doc/contributors/design/ruby_frontend.md +11 -7
- data/doc/contributors/developing_examples.md +261 -0
- data/doc/contributors/documentation_style.md +104 -0
- data/doc/contributors/dwim_dx.md +366 -0
- data/doc/contributors/index.md +2 -0
- data/doc/custom.css +14 -0
- data/doc/event_handling.md +125 -0
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_map_demo.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.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_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_box_demo.png +0 -0
- data/doc/images/widget_calendar_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_gauge_demo.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/doc/images/widget_popup_demo.png +0 -0
- data/doc/images/widget_ratatui_logo_demo.png +0 -0
- data/doc/images/widget_ratatui_mascot_demo.png +0 -0
- data/doc/images/widget_rect.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_scrollbar_demo.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/index.md +1 -0
- data/doc/interactive_design.md +116 -0
- data/doc/quickstart.md +186 -84
- data/examples/app_all_events/README.md +81 -0
- data/examples/app_all_events/app.rb +93 -0
- data/examples/app_all_events/model/event_color_cycle.rb +41 -0
- data/examples/app_all_events/model/event_entry.rb +75 -0
- data/examples/app_all_events/model/events.rb +180 -0
- data/examples/app_all_events/model/highlight.rb +57 -0
- data/examples/app_all_events/model/timestamp.rb +54 -0
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
- data/examples/app_all_events/view/app_view.rb +78 -0
- data/examples/app_all_events/view/controls_view.rb +50 -0
- data/examples/app_all_events/view/counts_view.rb +55 -0
- data/examples/app_all_events/view/live_view.rb +69 -0
- data/examples/app_all_events/view/log_view.rb +60 -0
- data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
- data/examples/app_all_events/view_state.rb +42 -0
- data/examples/app_color_picker/README.md +94 -0
- data/examples/app_color_picker/app.rb +112 -0
- data/examples/app_color_picker/clipboard.rb +84 -0
- data/examples/app_color_picker/color.rb +191 -0
- data/examples/app_color_picker/copy_dialog.rb +170 -0
- data/examples/app_color_picker/harmony.rb +56 -0
- data/examples/app_color_picker/input.rb +142 -0
- data/examples/app_color_picker/palette.rb +80 -0
- data/examples/app_color_picker/scene.rb +201 -0
- data/examples/app_login_form/app.rb +108 -0
- data/examples/app_map_demo/app.rb +93 -0
- data/examples/app_table_select/app.rb +201 -0
- data/examples/verify_quickstart_dsl/app.rb +45 -0
- data/examples/verify_quickstart_layout/app.rb +69 -0
- data/examples/verify_quickstart_lifecycle/app.rb +48 -0
- data/examples/verify_readme_usage/app.rb +34 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/widget_block_padding/app.rb +67 -0
- data/examples/widget_block_titles/app.rb +69 -0
- data/examples/widget_box_demo/app.rb +250 -0
- data/examples/widget_calendar_demo/app.rb +109 -0
- data/examples/widget_cell_demo/app.rb +104 -0
- data/examples/widget_chart_demo/app.rb +213 -0
- data/examples/widget_gauge_demo/app.rb +212 -0
- data/examples/widget_layout_split/app.rb +246 -0
- data/examples/widget_line_gauge_demo/app.rb +217 -0
- data/examples/widget_list_demo/app.rb +382 -0
- data/examples/widget_list_styles/app.rb +141 -0
- data/examples/widget_popup_demo/app.rb +104 -0
- data/examples/widget_ratatui_logo_demo/app.rb +103 -0
- data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
- data/examples/widget_rect/app.rb +205 -0
- data/examples/widget_render/app.rb +184 -0
- data/examples/widget_rich_text/app.rb +137 -0
- data/examples/widget_scroll_text/app.rb +108 -0
- data/examples/widget_scrollbar_demo/app.rb +153 -0
- data/examples/widget_sparkline_demo/app.rb +274 -0
- data/examples/widget_style_colors/app.rb +102 -0
- data/examples/widget_table_flex/app.rb +95 -0
- data/examples/widget_tabs_demo/app.rb +167 -0
- data/ext/ratatui_ruby/Cargo.lock +889 -115
- data/ext/ratatui_ruby/Cargo.toml +4 -3
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +7 -0
- data/ext/ratatui_ruby/src/events.rs +293 -219
- data/ext/ratatui_ruby/src/frame.rs +115 -0
- data/ext/ratatui_ruby/src/lib.rs +105 -24
- data/ext/ratatui_ruby/src/rendering.rs +94 -10
- data/ext/ratatui_ruby/src/style.rs +357 -93
- data/ext/ratatui_ruby/src/terminal.rs +121 -31
- data/ext/ratatui_ruby/src/text.rs +178 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
- data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
- data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
- data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
- data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
- data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
- data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
- data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
- data/lib/ratatui_ruby/cell.rb +166 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
- data/lib/ratatui_ruby/event/key.rb +211 -0
- data/lib/ratatui_ruby/event/mouse.rb +124 -0
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event/paste.rb +71 -0
- data/lib/ratatui_ruby/event/resize.rb +80 -0
- data/lib/ratatui_ruby/event.rb +131 -0
- data/lib/ratatui_ruby/frame.rb +87 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
- data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
- data/lib/ratatui_ruby/schema/block.rb +178 -11
- data/lib/ratatui_ruby/schema/calendar.rb +70 -14
- data/lib/ratatui_ruby/schema/canvas.rb +213 -46
- data/lib/ratatui_ruby/schema/center.rb +46 -8
- data/lib/ratatui_ruby/schema/chart.rb +134 -32
- data/lib/ratatui_ruby/schema/clear.rb +22 -53
- data/lib/ratatui_ruby/schema/constraint.rb +72 -12
- data/lib/ratatui_ruby/schema/cursor.rb +23 -5
- data/lib/ratatui_ruby/schema/draw.rb +53 -0
- data/lib/ratatui_ruby/schema/gauge.rb +56 -12
- data/lib/ratatui_ruby/schema/layout.rb +91 -9
- data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
- data/lib/ratatui_ruby/schema/list.rb +92 -16
- data/lib/ratatui_ruby/schema/overlay.rb +29 -3
- data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
- data/lib/ratatui_ruby/schema/rect.rb +59 -10
- data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
- data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
- data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
- data/lib/ratatui_ruby/schema/style.rb +39 -11
- data/lib/ratatui_ruby/schema/table.rb +109 -18
- data/lib/ratatui_ruby/schema/tabs.rb +71 -10
- data/lib/ratatui_ruby/schema/text.rb +90 -0
- data/lib/ratatui_ruby/session/autodoc.rb +417 -0
- data/lib/ratatui_ruby/session.rb +163 -0
- data/lib/ratatui_ruby/test_helper.rb +322 -13
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +184 -38
- data/sig/examples/app_all_events/app.rbs +11 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
- data/sig/examples/app_all_events/model/events.rbs +15 -0
- data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
- data/sig/examples/app_all_events/view/app_view.rbs +8 -0
- data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
- data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
- data/sig/examples/app_all_events/view/live_view.rbs +6 -0
- data/sig/examples/app_all_events/view/log_view.rbs +6 -0
- data/sig/examples/app_all_events/view.rbs +8 -0
- data/sig/examples/app_all_events/view_state.rbs +15 -0
- data/sig/examples/app_color_picker/app.rbs +12 -0
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_map_demo/app.rbs +11 -0
- data/sig/examples/app_table_select/app.rbs +11 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
- data/sig/examples/verify_readme_usage/app.rbs +11 -0
- data/sig/examples/widget_block_padding/app.rbs +11 -0
- data/sig/examples/widget_block_titles/app.rbs +11 -0
- data/sig/examples/widget_box_demo/app.rbs +11 -0
- data/sig/examples/widget_calendar_demo/app.rbs +11 -0
- data/sig/examples/widget_cell_demo/app.rbs +11 -0
- data/sig/examples/widget_chart_demo/app.rbs +11 -0
- data/sig/examples/widget_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_layout_split/app.rbs +10 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_list_demo/app.rbs +12 -0
- data/sig/examples/widget_list_styles/app.rbs +11 -0
- data/sig/examples/widget_popup_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
- data/sig/examples/widget_rect/app.rbs +12 -0
- data/sig/examples/widget_render/app.rbs +10 -0
- data/sig/examples/widget_rich_text/app.rbs +11 -0
- data/sig/examples/widget_scroll_text/app.rbs +11 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
- data/sig/examples/widget_style_colors/app.rbs +14 -0
- data/sig/examples/widget_table_flex/app.rbs +11 -0
- data/sig/ratatui_ruby/event.rbs +69 -0
- data/sig/ratatui_ruby/frame.rbs +9 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
- data/sig/ratatui_ruby/schema/block.rbs +5 -4
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
- data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
- data/sig/ratatui_ruby/schema/center.rbs +3 -3
- data/sig/ratatui_ruby/schema/chart.rbs +8 -5
- data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
- data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
- data/sig/ratatui_ruby/schema/draw.rbs +27 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
- data/sig/ratatui_ruby/schema/layout.rbs +11 -1
- data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
- data/sig/ratatui_ruby/schema/list.rbs +5 -1
- data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
- data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
- data/sig/ratatui_ruby/schema/rect.rbs +2 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
- data/sig/ratatui_ruby/schema/table.rbs +8 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
- data/sig/ratatui_ruby/schema/text.rbs +22 -0
- data/sig/ratatui_ruby/session.rbs +94 -0
- data/tasks/autodoc/inventory.rb +61 -0
- data/tasks/autodoc/member.rb +56 -0
- data/tasks/autodoc/name.rb +19 -0
- data/tasks/autodoc/notice.rb +26 -0
- data/tasks/autodoc/rbs.rb +38 -0
- data/tasks/autodoc/rdoc.rb +45 -0
- data/tasks/autodoc.rake +47 -0
- data/tasks/bump/history.rb +2 -2
- data/tasks/doc.rake +600 -6
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/lint.rake +8 -4
- data/tasks/resources/build.yml.erb +13 -11
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +4 -4
- data/tasks/terminal_preview/app_screenshot.rb +33 -0
- data/tasks/terminal_preview/crash_report.rb +52 -0
- data/tasks/terminal_preview/example_app.rb +25 -0
- data/tasks/terminal_preview/launcher_script.rb +46 -0
- data/tasks/terminal_preview/preview_collection.rb +58 -0
- data/tasks/terminal_preview/preview_timing.rb +22 -0
- data/tasks/terminal_preview/safety_confirmation.rb +56 -0
- data/tasks/terminal_preview/saved_screenshot.rb +53 -0
- data/tasks/terminal_preview/system_appearance.rb +11 -0
- data/tasks/terminal_preview/terminal_window.rb +136 -0
- data/tasks/terminal_preview/window_id.rb +14 -0
- data/tasks/terminal_preview.rake +28 -0
- data/tasks/test.rake +2 -2
- data/tasks/website/index_page.rb +3 -3
- data/tasks/website/version.rb +10 -10
- data/tasks/website/version_menu.rb +10 -12
- data/tasks/website/versioned_documentation.rb +49 -17
- data/tasks/website/website.rb +6 -8
- data/tasks/website.rake +4 -4
- metadata +206 -54
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/images/examples-analytics.rb.png +0 -0
- data/doc/images/examples-box_demo.rb.png +0 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-dashboard.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-login_form.rb.png +0 -0
- data/doc/images/examples-map_demo.rb.png +0 -0
- data/doc/images/examples-mouse_events.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-scrollbar_demo.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-system_monitor.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/examples/analytics.rb +0 -88
- data/examples/box_demo.rb +0 -71
- data/examples/calendar_demo.rb +0 -55
- data/examples/chart_demo.rb +0 -84
- data/examples/custom_widget.rb +0 -43
- data/examples/dashboard.rb +0 -72
- data/examples/list_styles.rb +0 -66
- data/examples/login_form.rb +0 -115
- data/examples/map_demo.rb +0 -58
- data/examples/mouse_events.rb +0 -95
- data/examples/popup_demo.rb +0 -105
- data/examples/quickstart_dsl.rb +0 -30
- data/examples/quickstart_lifecycle.rb +0 -40
- data/examples/readme_usage.rb +0 -21
- data/examples/scroll_text.rb +0 -74
- data/examples/scrollbar_demo.rb +0 -75
- data/examples/stock_ticker.rb +0 -93
- data/examples/system_monitor.rb +0 -94
- data/examples/table_select.rb +0 -70
- data/examples/test_analytics.rb +0 -65
- data/examples/test_box_demo.rb +0 -38
- data/examples/test_calendar_demo.rb +0 -66
- data/examples/test_dashboard.rb +0 -38
- data/examples/test_list_styles.rb +0 -61
- data/examples/test_login_form.rb +0 -63
- data/examples/test_map_demo.rb +0 -100
- data/examples/test_popup_demo.rb +0 -62
- data/examples/test_scroll_text.rb +0 -130
- data/examples/test_stock_ticker.rb +0 -39
- data/examples/test_system_monitor.rb +0 -40
- data/examples/test_table_select.rb +0 -37
- data/ext/ratatui_ruby/src/buffer.rs +0 -54
- data/lib/ratatui_ruby/dsl.rb +0 -64
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "timeout"
|
|
4
|
+
require "minitest/mock"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
3
7
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
8
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
9
|
|
|
@@ -8,7 +12,7 @@ module RatatuiRuby
|
|
|
8
12
|
# Helpers for testing RatatuiRuby applications.
|
|
9
13
|
#
|
|
10
14
|
# This module provides methods to set up a test terminal, capture buffer content,
|
|
11
|
-
# and
|
|
15
|
+
# and inject events, making it easier to write unit tests for your TUI apps.
|
|
12
16
|
#
|
|
13
17
|
# == Usage
|
|
14
18
|
#
|
|
@@ -18,24 +22,49 @@ module RatatuiRuby
|
|
|
18
22
|
# include RatatuiRuby::TestHelper
|
|
19
23
|
#
|
|
20
24
|
# def test_rendering
|
|
21
|
-
# with_test_terminal(
|
|
25
|
+
# with_test_terminal(80, 24) do
|
|
22
26
|
# # ... render your app ...
|
|
23
27
|
# assert_includes buffer_content, "Hello World"
|
|
24
28
|
# end
|
|
25
29
|
# end
|
|
30
|
+
#
|
|
31
|
+
# def test_key_handling
|
|
32
|
+
# inject_event(RatatuiRuby::Event::Key.new(code: "q"))
|
|
33
|
+
# result = @app.handle_input
|
|
34
|
+
# assert_equal :quit, result
|
|
35
|
+
# end
|
|
26
36
|
# end
|
|
27
37
|
module TestHelper
|
|
28
38
|
##
|
|
29
39
|
# Initializes a test terminal context with specified dimensions.
|
|
30
40
|
# Restores the original terminal state after the block executes.
|
|
31
41
|
#
|
|
32
|
-
# +width+:: width of the test terminal (default:
|
|
33
|
-
# +height+:: height of the test terminal (default:
|
|
42
|
+
# +width+:: width of the test terminal (default: 80)
|
|
43
|
+
# +height+:: height of the test terminal (default: 24)
|
|
44
|
+
#
|
|
45
|
+
# +timeout+:: maximum execution time in seconds (default: 2). Pass nil to disable.
|
|
34
46
|
#
|
|
35
47
|
# If a block is given, it is executed within the test terminal context.
|
|
36
|
-
def with_test_terminal(width =
|
|
48
|
+
def with_test_terminal(width = 80, height = 24, **opts)
|
|
37
49
|
RatatuiRuby.init_test_terminal(width, height)
|
|
38
|
-
|
|
50
|
+
# Flush any lingering events from previous tests
|
|
51
|
+
while (event = RatatuiRuby.poll_event) && !event.none?; end
|
|
52
|
+
|
|
53
|
+
RatatuiRuby.stub :init_terminal, nil do
|
|
54
|
+
RatatuiRuby.stub :restore_terminal, nil do
|
|
55
|
+
@_ratatui_test_terminal_active = true
|
|
56
|
+
timeout = opts.fetch(:timeout, 2)
|
|
57
|
+
if timeout
|
|
58
|
+
Timeout.timeout(timeout) do
|
|
59
|
+
yield
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
yield
|
|
63
|
+
end
|
|
64
|
+
ensure
|
|
65
|
+
@_ratatui_test_terminal_active = false
|
|
66
|
+
end
|
|
67
|
+
end
|
|
39
68
|
ensure
|
|
40
69
|
RatatuiRuby.restore_terminal
|
|
41
70
|
end
|
|
@@ -61,15 +90,295 @@ module RatatuiRuby
|
|
|
61
90
|
end
|
|
62
91
|
|
|
63
92
|
##
|
|
64
|
-
# Injects
|
|
93
|
+
# Injects an event into the event queue for testing.
|
|
94
|
+
#
|
|
95
|
+
# Pass any RatatuiRuby::Event object. The event will be returned by
|
|
96
|
+
# the next call to RatatuiRuby.poll_event.
|
|
97
|
+
#
|
|
98
|
+
# Raises a +RuntimeError+ if called outside of a +with_test_terminal+ block.
|
|
99
|
+
#
|
|
100
|
+
# == Examples
|
|
101
|
+
#
|
|
102
|
+
# with_test_terminal do
|
|
103
|
+
# # Key events
|
|
104
|
+
# inject_event(RatatuiRuby::Event::Key.new(code: "q"))
|
|
105
|
+
# inject_event(RatatuiRuby::Event::Key.new(code: "s", modifiers: ["ctrl"]))
|
|
106
|
+
#
|
|
107
|
+
# # Mouse events
|
|
108
|
+
# inject_event(RatatuiRuby::Event::Mouse.new(kind: "down", button: "left", x: 10, y: 5))
|
|
109
|
+
#
|
|
110
|
+
# # Resize events
|
|
111
|
+
# inject_event(RatatuiRuby::Event::Resize.new(width: 120, height: 40))
|
|
112
|
+
#
|
|
113
|
+
# # Paste events
|
|
114
|
+
# inject_event(RatatuiRuby::Event::Paste.new(content: "Hello"))
|
|
115
|
+
#
|
|
116
|
+
# # Focus events
|
|
117
|
+
# inject_event(RatatuiRuby::Event::FocusGained.new)
|
|
118
|
+
# inject_event(RatatuiRuby::Event::FocusLost.new)
|
|
119
|
+
# end
|
|
120
|
+
def inject_event(event)
|
|
121
|
+
unless @_ratatui_test_terminal_active
|
|
122
|
+
raise "Events must be injected inside a `with_test_terminal` block. " \
|
|
123
|
+
"Calling this method outside the block causes a race condition where the event " \
|
|
124
|
+
"is flushed before the application starts."
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
case event
|
|
128
|
+
when RatatuiRuby::Event::Key
|
|
129
|
+
RatatuiRuby.inject_test_event("key", { code: event.code, modifiers: event.modifiers })
|
|
130
|
+
when RatatuiRuby::Event::Mouse
|
|
131
|
+
RatatuiRuby.inject_test_event("mouse", {
|
|
132
|
+
kind: event.kind,
|
|
133
|
+
button: event.button,
|
|
134
|
+
x: event.x,
|
|
135
|
+
y: event.y,
|
|
136
|
+
modifiers: event.modifiers,
|
|
137
|
+
})
|
|
138
|
+
when RatatuiRuby::Event::Resize
|
|
139
|
+
RatatuiRuby.inject_test_event("resize", { width: event.width, height: event.height })
|
|
140
|
+
when RatatuiRuby::Event::Paste
|
|
141
|
+
RatatuiRuby.inject_test_event("paste", { content: event.content })
|
|
142
|
+
when RatatuiRuby::Event::FocusGained
|
|
143
|
+
RatatuiRuby.inject_test_event("focus_gained", {})
|
|
144
|
+
when RatatuiRuby::Event::FocusLost
|
|
145
|
+
RatatuiRuby.inject_test_event("focus_lost", {})
|
|
146
|
+
else
|
|
147
|
+
raise ArgumentError, "Unknown event type: #{event.class}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# Injects a mouse event.
|
|
153
|
+
#
|
|
154
|
+
# inject_mouse(x: 10, y: 5, kind: :down, button: :left)
|
|
155
|
+
def inject_mouse(x:, y:, kind: :down, modifiers: [], button: :left)
|
|
156
|
+
event = RatatuiRuby::Event::Mouse.new(
|
|
157
|
+
kind: kind.to_s,
|
|
158
|
+
x:,
|
|
159
|
+
y:,
|
|
160
|
+
button: button.to_s,
|
|
161
|
+
modifiers:
|
|
162
|
+
)
|
|
163
|
+
inject_event(event)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
##
|
|
167
|
+
# Injects a mouse left click (down) event.
|
|
168
|
+
#
|
|
169
|
+
# inject_click(x: 10, y: 5)
|
|
170
|
+
def inject_click(x:, y:, modifiers: [])
|
|
171
|
+
inject_mouse(x:, y:, kind: :down, modifiers:, button: :left)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
##
|
|
175
|
+
# Injects a mouse right click (down) event.
|
|
176
|
+
#
|
|
177
|
+
# inject_right_click(x: 10, y: 5)
|
|
178
|
+
def inject_right_click(x:, y:, modifiers: [])
|
|
179
|
+
inject_mouse(x:, y:, kind: :down, modifiers:, button: :right)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
##
|
|
183
|
+
# Injects a mouse drag event.
|
|
184
|
+
#
|
|
185
|
+
# inject_drag(x: 10, y: 5)
|
|
186
|
+
def inject_drag(x:, y:, modifiers: [], button: :left)
|
|
187
|
+
inject_mouse(x:, y:, kind: :drag, modifiers:, button:)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
##
|
|
191
|
+
# Injects multiple Key events into the queue.
|
|
192
|
+
#
|
|
193
|
+
# Supports multiple formats for convenience:
|
|
194
|
+
#
|
|
195
|
+
# * String: Converted to a Key event with that code.
|
|
196
|
+
# * Symbol: Parsed as modifier_code (e.g., <tt>:ctrl_c</tt>, <tt>:enter</tt>).
|
|
197
|
+
# * Hash: Passed to Key.new constructor.
|
|
198
|
+
# * Key: Passed directly.
|
|
199
|
+
#
|
|
200
|
+
# == Examples
|
|
201
|
+
#
|
|
202
|
+
# with_test_terminal do
|
|
203
|
+
# inject_keys("a", "b", "c")
|
|
204
|
+
# inject_keys(:enter, :esc)
|
|
205
|
+
# inject_keys(:ctrl_c, :alt_shift_left)
|
|
206
|
+
# inject_keys("j", { code: "k", modifiers: ["ctrl"] })
|
|
207
|
+
# end
|
|
208
|
+
def inject_keys(*args)
|
|
209
|
+
args.each do |arg|
|
|
210
|
+
event = case arg
|
|
211
|
+
when String
|
|
212
|
+
RatatuiRuby::Event::Key.new(code: arg)
|
|
213
|
+
when Symbol
|
|
214
|
+
parts = arg.to_s.split("_")
|
|
215
|
+
code = parts.pop
|
|
216
|
+
modifiers = parts
|
|
217
|
+
RatatuiRuby::Event::Key.new(code:, modifiers:)
|
|
218
|
+
when Hash
|
|
219
|
+
RatatuiRuby::Event::Key.new(**arg)
|
|
220
|
+
when RatatuiRuby::Event::Key
|
|
221
|
+
arg
|
|
222
|
+
else
|
|
223
|
+
raise ArgumentError, "Invalid key argument: #{arg.inspect}. Expected String, Symbol, Hash, or Key event."
|
|
224
|
+
end
|
|
225
|
+
inject_event(event)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
alias inject_key inject_keys
|
|
229
|
+
|
|
230
|
+
##
|
|
231
|
+
# Returns the cell attributes at the given coordinates.
|
|
232
|
+
#
|
|
233
|
+
# get_cell(0, 0)
|
|
234
|
+
# # => { "symbol" => "H", "fg" => :red, "bg" => nil }
|
|
235
|
+
def get_cell(x, y)
|
|
236
|
+
RatatuiRuby.get_cell_at(x, y)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
##
|
|
240
|
+
# Asserts that the cell at the given coordinates has the expected attributes.
|
|
241
|
+
#
|
|
242
|
+
# assert_cell_style(0, 0, char: "H", fg: :red)
|
|
243
|
+
def assert_cell_style(x, y, **expected_attributes)
|
|
244
|
+
cell = get_cell(x, y)
|
|
245
|
+
expected_attributes.each do |key, value|
|
|
246
|
+
actual_value = cell.public_send(key)
|
|
247
|
+
if value.nil?
|
|
248
|
+
assert_nil actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=nil, but got #{actual_value.inspect}"
|
|
249
|
+
else
|
|
250
|
+
assert_equal value, actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=#{value.inspect}, but got #{actual_value.inspect}"
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
##
|
|
256
|
+
# Mock frame for unit testing views.
|
|
257
|
+
#
|
|
258
|
+
# Captures widgets passed to +render_widget+ for inspection.
|
|
259
|
+
# Does not render anything—purely captures the output.
|
|
260
|
+
#
|
|
261
|
+
# == Examples
|
|
262
|
+
#
|
|
263
|
+
# frame = MockFrame.new
|
|
264
|
+
# View::Log.new.call(state, tui, frame, area)
|
|
265
|
+
# widget = frame.rendered_widgets.first[:widget]
|
|
266
|
+
# assert_equal "Event Log", widget.block.title
|
|
267
|
+
MockFrame = Data.define(:rendered_widgets) do
|
|
268
|
+
def initialize(rendered_widgets: [])
|
|
269
|
+
super
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def render_widget(widget, area)
|
|
273
|
+
rendered_widgets << { widget:, area: }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
##
|
|
278
|
+
# Stub area for unit testing views.
|
|
279
|
+
#
|
|
280
|
+
# Provides the minimal interface views expect (+width+, +height+).
|
|
281
|
+
#
|
|
282
|
+
# == Examples
|
|
283
|
+
#
|
|
284
|
+
# area = StubRect.new(width: 60, height: 20)
|
|
285
|
+
StubRect = Data.define(:x, :y, :width, :height) do
|
|
286
|
+
def initialize(x: 0, y: 0, width: 80, height: 24)
|
|
287
|
+
super
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
##
|
|
292
|
+
# Asserts that the current screen content matches a stored snapshot.
|
|
293
|
+
#
|
|
294
|
+
# This method simplifies snapshot testing by automatically resolving the snapshot path
|
|
295
|
+
# relative to the test file calling this method. It assumes a "snapshots" directory
|
|
296
|
+
# exists in the same directory as the test file.
|
|
65
297
|
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
298
|
+
# # In test/test_login.rb
|
|
299
|
+
# assert_snapshot("login_screen")
|
|
300
|
+
# # Look for: test/snapshots/login_screen.txt
|
|
68
301
|
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
302
|
+
# # With normalization block
|
|
303
|
+
# assert_snapshot("clock") do |actual|
|
|
304
|
+
# actual.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") }
|
|
305
|
+
# end
|
|
306
|
+
#
|
|
307
|
+
# [name] String name of the snapshot (without extension).
|
|
308
|
+
# [msg] String optional failure message.
|
|
309
|
+
def assert_snapshot(name, msg = nil, &)
|
|
310
|
+
# Get the path of the test file calling this method
|
|
311
|
+
caller_path = caller_locations(1, 1).first.path
|
|
312
|
+
snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
|
|
313
|
+
snapshot_path = File.join(snapshot_dir, "#{name}.txt")
|
|
314
|
+
|
|
315
|
+
assert_screen_matches(snapshot_path, msg, &)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
##
|
|
319
|
+
# Asserts that the current screen content matches the expected content.
|
|
320
|
+
#
|
|
321
|
+
# Users need to verify that the entire TUI screen looks exactly as expected.
|
|
322
|
+
# Manually checking every cell or line is tedious and error-prone.
|
|
323
|
+
#
|
|
324
|
+
# This helper compares the current buffer content against an expected string (file path)
|
|
325
|
+
# or array of strings. It supports automatic snapshot creation and updating via
|
|
326
|
+
# the +UPDATE_SNAPSHOTS+ environment variable.
|
|
327
|
+
#
|
|
328
|
+
# Use it to verify complex UI states, layouts, and renderings.
|
|
329
|
+
#
|
|
330
|
+
# == Usage
|
|
331
|
+
#
|
|
332
|
+
# # Direct comparison
|
|
333
|
+
# assert_screen_matches(["Line 1", "Line 2"])
|
|
334
|
+
#
|
|
335
|
+
# # File comparison
|
|
336
|
+
# assert_screen_matches("test/snapshots/login.txt")
|
|
337
|
+
#
|
|
338
|
+
# # With normalization (e.g., masking dynamic data)
|
|
339
|
+
# assert_screen_matches("test/snapshots/dashboard.txt") do |lines|
|
|
340
|
+
# lines.map { |l| l.gsub(/User ID: \d+/, "User ID: XXX") }
|
|
341
|
+
# end
|
|
342
|
+
#
|
|
343
|
+
# [expected] String (file path) or Array<String> (content).
|
|
344
|
+
# [msg] String optional failure message.
|
|
345
|
+
def assert_screen_matches(expected, msg = nil)
|
|
346
|
+
actual_lines = buffer_content
|
|
347
|
+
|
|
348
|
+
if block_given?
|
|
349
|
+
actual_lines = yield(actual_lines)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
if expected.is_a?(String)
|
|
353
|
+
# Snapshot file mode
|
|
354
|
+
snapshot_path = expected
|
|
355
|
+
update_snapshots = ENV["UPDATE_SNAPSHOTS"] == "1" || ENV["UPDATE_SNAPSHOTS"] == "true"
|
|
356
|
+
|
|
357
|
+
if !File.exist?(snapshot_path) || update_snapshots
|
|
358
|
+
FileUtils.mkdir_p(File.dirname(snapshot_path))
|
|
359
|
+
File.write(snapshot_path, "#{actual_lines.join("\n")}\n")
|
|
360
|
+
if update_snapshots
|
|
361
|
+
puts "Updated snapshot: #{snapshot_path}"
|
|
362
|
+
else
|
|
363
|
+
puts "Created snapshot: #{snapshot_path}"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
expected_lines = File.readlines(snapshot_path, chomp: true)
|
|
368
|
+
else
|
|
369
|
+
# Direct comparison mode
|
|
370
|
+
expected_lines = expected
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
msg ||= "Screen content mismatch"
|
|
374
|
+
|
|
375
|
+
assert_equal expected_lines.size, actual_lines.size, "#{msg}: Line count mismatch"
|
|
376
|
+
|
|
377
|
+
expected_lines.each_with_index do |expected_line, i|
|
|
378
|
+
actual_line = actual_lines[i]
|
|
379
|
+
assert_equal expected_line, actual_line,
|
|
380
|
+
"#{msg}: Line #{i + 1} mismatch.\nExpected: #{expected_line.inspect}\nActual: #{actual_line.inspect}"
|
|
381
|
+
end
|
|
73
382
|
end
|
|
74
383
|
end
|
|
75
384
|
end
|
data/lib/ratatui_ruby/version.rb
CHANGED
data/lib/ratatui_ruby.rb
CHANGED
|
@@ -12,9 +12,12 @@ require_relative "ratatui_ruby/schema/constraint"
|
|
|
12
12
|
require_relative "ratatui_ruby/schema/list"
|
|
13
13
|
require_relative "ratatui_ruby/schema/style"
|
|
14
14
|
require_relative "ratatui_ruby/schema/gauge"
|
|
15
|
+
require_relative "ratatui_ruby/schema/line_gauge"
|
|
15
16
|
require_relative "ratatui_ruby/schema/table"
|
|
16
17
|
require_relative "ratatui_ruby/schema/tabs"
|
|
17
18
|
require_relative "ratatui_ruby/schema/bar_chart"
|
|
19
|
+
require_relative "ratatui_ruby/schema/bar_chart/bar"
|
|
20
|
+
require_relative "ratatui_ruby/schema/bar_chart/bar_group"
|
|
18
21
|
require_relative "ratatui_ruby/schema/sparkline"
|
|
19
22
|
require_relative "ratatui_ruby/schema/chart"
|
|
20
23
|
require_relative "ratatui_ruby/schema/clear"
|
|
@@ -23,7 +26,15 @@ require_relative "ratatui_ruby/schema/overlay"
|
|
|
23
26
|
require_relative "ratatui_ruby/schema/center"
|
|
24
27
|
require_relative "ratatui_ruby/schema/scrollbar"
|
|
25
28
|
require_relative "ratatui_ruby/schema/canvas"
|
|
29
|
+
require_relative "ratatui_ruby/schema/shape/label"
|
|
26
30
|
require_relative "ratatui_ruby/schema/calendar"
|
|
31
|
+
require_relative "ratatui_ruby/schema/ratatui_logo"
|
|
32
|
+
require_relative "ratatui_ruby/schema/ratatui_mascot"
|
|
33
|
+
require_relative "ratatui_ruby/schema/text"
|
|
34
|
+
require_relative "ratatui_ruby/schema/draw"
|
|
35
|
+
require_relative "ratatui_ruby/event"
|
|
36
|
+
require_relative "ratatui_ruby/cell"
|
|
37
|
+
require_relative "ratatui_ruby/frame"
|
|
27
38
|
|
|
28
39
|
begin
|
|
29
40
|
require "ratatui_ruby/ratatui_ruby"
|
|
@@ -32,78 +43,213 @@ rescue LoadError
|
|
|
32
43
|
require_relative "ratatui_ruby/ratatui_ruby"
|
|
33
44
|
end
|
|
34
45
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
46
|
+
# Main entry point for the library.
|
|
47
|
+
#
|
|
48
|
+
# Terminal UIs require low-level control using C/Rust and high-level abstraction in Ruby.
|
|
49
|
+
#
|
|
50
|
+
# This module bridges the gap. It provides the native methods to initialize the terminal, handle raw mode, and render the widget tree.
|
|
51
|
+
#
|
|
52
|
+
# Use `RatatuiRuby.run` to start your application.
|
|
37
53
|
module RatatuiRuby
|
|
38
54
|
# Generic error class for RatatuiRuby.
|
|
39
55
|
class Error < StandardError; end
|
|
40
56
|
|
|
41
57
|
##
|
|
42
|
-
# :method: init_terminal
|
|
43
|
-
# :call-seq: init_terminal() -> nil
|
|
44
|
-
#
|
|
45
58
|
# Initializes the terminal for TUI mode.
|
|
46
59
|
# Enters alternate screen and enables raw mode.
|
|
47
60
|
#
|
|
48
|
-
#
|
|
61
|
+
# [focus_events] whether to enable focus gain/loss events (default: true).
|
|
62
|
+
# [bracketed_paste] whether to enable bracketed paste mode (default: true).
|
|
63
|
+
def self.init_terminal(focus_events: true, bracketed_paste: true)
|
|
64
|
+
_init_terminal(focus_events, bracketed_paste)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@experimental_warnings = true
|
|
68
|
+
class << self
|
|
69
|
+
##
|
|
70
|
+
# :attr_accessor: experimental_warnings
|
|
71
|
+
# Whether to show warnings when using experimental features (default: true).
|
|
72
|
+
attr_accessor :experimental_warnings
|
|
73
|
+
end
|
|
49
74
|
|
|
50
75
|
##
|
|
51
|
-
# :method: restore_terminal
|
|
52
|
-
# :call-seq: restore_terminal() -> nil
|
|
53
|
-
#
|
|
76
|
+
# :singleton-method: restore_terminal
|
|
54
77
|
# Restores the terminal to its original state.
|
|
55
78
|
# Leaves alternate screen and disables raw mode.
|
|
56
79
|
#
|
|
57
80
|
# (Native method implemented in Rust)
|
|
58
81
|
|
|
59
82
|
##
|
|
60
|
-
# :method:
|
|
61
|
-
#
|
|
83
|
+
# :singleton-method: inject_test_event
|
|
84
|
+
# Injects a mock event into the event queue for testing purposes.
|
|
85
|
+
# [event_type] "key" or "mouse"
|
|
86
|
+
# [data] a Hash containing event data
|
|
62
87
|
#
|
|
63
|
-
#
|
|
64
|
-
# [node] the root node of the UI tree (Paragraph, Layout).
|
|
88
|
+
# inject_test_event("key", { code: "a" })
|
|
65
89
|
#
|
|
66
90
|
# (Native method implemented in Rust)
|
|
67
91
|
|
|
68
92
|
##
|
|
69
|
-
#
|
|
70
|
-
# :call-seq: poll_event() -> Hash, nil
|
|
71
|
-
#
|
|
72
|
-
# Polls for a keyboard event.
|
|
93
|
+
# Warns about usage of an experimental feature unless warnings are suppressed.
|
|
73
94
|
#
|
|
74
|
-
#
|
|
75
|
-
# # => { type: :key, code: "a", modifiers: ["ctrl"] }
|
|
95
|
+
# [feature_name] String name of the feature (e.g., "Paragraph#line_count")
|
|
76
96
|
#
|
|
77
|
-
#
|
|
97
|
+
# This warns only once per feature name per session.
|
|
98
|
+
def self.warn_experimental_feature(feature_name)
|
|
99
|
+
return unless experimental_warnings
|
|
100
|
+
|
|
101
|
+
@warned_features ||= {}
|
|
102
|
+
return if @warned_features[feature_name]
|
|
103
|
+
|
|
104
|
+
warn "WARNING: #{feature_name} is an experimental feature and may change in future versions. Disable this warning with RatatuiRuby.experimental_warnings = false."
|
|
105
|
+
@warned_features[feature_name] = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# (Native method _init_terminal implemented in Rust)
|
|
109
|
+
private_class_method :_init_terminal
|
|
78
110
|
|
|
79
111
|
##
|
|
80
|
-
#
|
|
81
|
-
# :call-seq: inject_test_event(event_type, data) -> nil
|
|
112
|
+
# Draws the given UI node tree to the terminal.
|
|
82
113
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
114
|
+
# TUI applications need to render widgets to the screen. Rendering could
|
|
115
|
+
# happen all at once with a pre-built tree, or incrementally with direct
|
|
116
|
+
# frame access.
|
|
86
117
|
#
|
|
87
|
-
#
|
|
118
|
+
# This method handles both. Pass a tree for declarative rendering, or
|
|
119
|
+
# pass a block to manipulate the frame directly. The block receives a
|
|
120
|
+
# {Frame} object for imperative drawing.
|
|
88
121
|
#
|
|
89
|
-
# (
|
|
122
|
+
# [tree] A widget tree (Paragraph, Layout, etc.) to render. Optional if
|
|
123
|
+
# a block is given.
|
|
124
|
+
#
|
|
125
|
+
# === Examples
|
|
126
|
+
#
|
|
127
|
+
# Legacy declarative style (tree-based):
|
|
128
|
+
#
|
|
129
|
+
# RatatuiRuby.draw(Paragraph.new(text: "Hello"))
|
|
130
|
+
#
|
|
131
|
+
# New imperative style (block-based):
|
|
132
|
+
#
|
|
133
|
+
# RatatuiRuby.draw do |frame|
|
|
134
|
+
# frame.render_widget(Paragraph.new(text: "Hello"), frame.area)
|
|
135
|
+
# end
|
|
136
|
+
#
|
|
137
|
+
def self.draw(tree = nil, &block)
|
|
138
|
+
if tree && block
|
|
139
|
+
raise ArgumentError, "Cannot provide both a tree and a block to draw"
|
|
140
|
+
end
|
|
141
|
+
unless tree || block
|
|
142
|
+
raise ArgumentError, "Must provide either a tree or a block to draw"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if tree
|
|
146
|
+
_draw(tree)
|
|
147
|
+
else
|
|
148
|
+
_draw(&block)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# (Native method _draw implemented in Rust)
|
|
153
|
+
private_class_method :_draw
|
|
90
154
|
|
|
91
155
|
##
|
|
92
|
-
#
|
|
93
|
-
#
|
|
156
|
+
# Checks for user input.
|
|
157
|
+
#
|
|
158
|
+
# Returns a discrete event (Key, Mouse, Resize) if one is available in the queue.
|
|
159
|
+
# Returns RatatuiRuby::Event::None if the queue is empty (non-blocking).
|
|
160
|
+
#
|
|
161
|
+
# === Example
|
|
94
162
|
#
|
|
95
|
-
# RatatuiRuby.
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
163
|
+
# event = RatatuiRuby.poll_event
|
|
164
|
+
# if event.none?
|
|
165
|
+
# puts "No input available"
|
|
166
|
+
# elsif event.key?
|
|
167
|
+
# puts "Key pressed"
|
|
99
168
|
# end
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
169
|
+
#
|
|
170
|
+
def self.poll_event
|
|
171
|
+
raw = _poll_event
|
|
172
|
+
return Event::None.new if raw.nil?
|
|
173
|
+
|
|
174
|
+
case raw[:type]
|
|
175
|
+
when :key
|
|
176
|
+
Event::Key.new(code: raw[:code], modifiers: raw[:modifiers] || [])
|
|
177
|
+
when :mouse
|
|
178
|
+
Event::Mouse.new(
|
|
179
|
+
kind: raw[:kind].to_s,
|
|
180
|
+
x: raw[:x],
|
|
181
|
+
y: raw[:y],
|
|
182
|
+
button: raw[:button].to_s,
|
|
183
|
+
modifiers: raw[:modifiers] || []
|
|
184
|
+
)
|
|
185
|
+
when :resize
|
|
186
|
+
Event::Resize.new(width: raw[:width], height: raw[:height])
|
|
187
|
+
when :paste
|
|
188
|
+
Event::Paste.new(content: raw[:content])
|
|
189
|
+
when :focus_gained
|
|
190
|
+
Event::FocusGained.new
|
|
191
|
+
when :focus_lost
|
|
192
|
+
Event::FocusLost.new
|
|
193
|
+
else
|
|
194
|
+
# Fallback for unknown events, though ideally we cover them all
|
|
195
|
+
nil
|
|
105
196
|
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# (Native method _poll_event implemented in Rust)
|
|
200
|
+
private_class_method :_poll_event
|
|
201
|
+
|
|
202
|
+
##
|
|
203
|
+
# Starts the TUI application lifecycle.
|
|
204
|
+
#
|
|
205
|
+
# Managing generic setup/teardown (raw mode, alternate screen) manualy is error-prone. If your app crashes, the terminal might be left in a broken state.
|
|
206
|
+
#
|
|
207
|
+
# This method handles the safety net. It initializes the terminal, yields a {Session}, and ensures the terminal state is restored even if exceptions occur.
|
|
208
|
+
#
|
|
209
|
+
# === Example
|
|
210
|
+
#
|
|
211
|
+
# RatatuiRuby.run(focus_events: false) do |tui|
|
|
212
|
+
# tui.draw(tui.paragraph(text: "Hi"))
|
|
213
|
+
# sleep 1
|
|
214
|
+
# end
|
|
215
|
+
def self.run(focus_events: true, bracketed_paste: true)
|
|
216
|
+
require_relative "ratatui_ruby/session"
|
|
217
|
+
init_terminal(focus_events:, bracketed_paste:)
|
|
218
|
+
yield Session.new
|
|
106
219
|
ensure
|
|
107
220
|
restore_terminal
|
|
108
221
|
end
|
|
222
|
+
|
|
223
|
+
##
|
|
224
|
+
# Inspects the terminal buffer at specific coordinates.
|
|
225
|
+
#
|
|
226
|
+
# When writing tests, you need to verify that your widget drew the correct characters and styles.
|
|
227
|
+
# This method provides deep inspection of the cell's state (symbol, colors, modifiers).
|
|
228
|
+
#
|
|
229
|
+
# Returns a {Cell} object.
|
|
230
|
+
#
|
|
231
|
+
# Values depend on what the backend has rendered. If nothing has been rendered to a cell, it may contain defaults (empty symbol, nil colors).
|
|
232
|
+
#
|
|
233
|
+
# === Example
|
|
234
|
+
#
|
|
235
|
+
# cell = RatatuiRuby.get_cell_at(10, 5)
|
|
236
|
+
# expect(cell.symbol).to eq("X")
|
|
237
|
+
# expect(cell.fg).to eq(:red)
|
|
238
|
+
# expect(cell).to be_bold
|
|
239
|
+
#
|
|
240
|
+
def self.get_cell_at(x, y)
|
|
241
|
+
raw = _get_cell_at(x, y)
|
|
242
|
+
Cell.new(
|
|
243
|
+
char: raw["char"],
|
|
244
|
+
fg: raw["fg"],
|
|
245
|
+
bg: raw["bg"],
|
|
246
|
+
modifiers: raw["modifiers"] || []
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# (Native method _get_cell_at implemented in Rust)
|
|
251
|
+
private_class_method :_get_cell_at
|
|
252
|
+
|
|
253
|
+
# Hide native Layout._split helper
|
|
254
|
+
Layout.singleton_class.__send__(:private, :_split)
|
|
109
255
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
class EventEntry < Data
|
|
5
|
+
attr_reader type: Symbol
|
|
6
|
+
attr_reader sub_key: Symbol | String | nil
|
|
7
|
+
attr_reader color: Symbol
|
|
8
|
+
attr_reader timestamp: Timestamp
|
|
9
|
+
attr_reader data: Hash[Symbol, untyped]
|
|
10
|
+
|
|
11
|
+
def self.from_event(type: Symbol, sub_key: Symbol | String | nil, color: Symbol, timestamp: Timestamp, data: Hash[Symbol, untyped]) -> instance
|
|
12
|
+
def matches_type?: (Symbol check_type) -> bool
|
|
13
|
+
def matches_sub_type?: (Symbol check_type, Symbol | String check_sub_key) -> bool
|
|
14
|
+
def matches_kind?: (String kind) -> bool
|
|
15
|
+
def self.new: (?type: Symbol, ?sub_key: Symbol | String | nil, ?color: Symbol, ?timestamp: Timestamp, ?data: Hash[Symbol, untyped]) -> instance
|
|
16
|
+
end
|