ratatui_ruby 1.5.2-arm64-darwin
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +15 -0
- data/LICENSES/AGPL-3.0-or-later.txt +661 -0
- data/LICENSES/CC-BY-SA-4.0.txt +427 -0
- data/LICENSES/CC0-1.0.txt +121 -0
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/LICENSES/MIT.txt +21 -0
- data/REUSE.toml +42 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/.cargo/config.toml +13 -0
- data/ext/ratatui_ruby/.gitignore +4 -0
- data/ext/ratatui_ruby/Cargo.lock +1730 -0
- data/ext/ratatui_ruby/Cargo.toml +23 -0
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +21 -0
- data/ext/ratatui_ruby/src/color.rs +82 -0
- data/ext/ratatui_ruby/src/errors.rs +28 -0
- data/ext/ratatui_ruby/src/events.rs +706 -0
- data/ext/ratatui_ruby/src/frame.rs +241 -0
- data/ext/ratatui_ruby/src/lib.rs +343 -0
- data/ext/ratatui_ruby/src/lib_header.rs +11 -0
- data/ext/ratatui_ruby/src/rendering.rs +158 -0
- data/ext/ratatui_ruby/src/string_width.rs +26 -0
- data/ext/ratatui_ruby/src/style.rs +469 -0
- data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
- data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
- data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
- data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
- data/ext/ratatui_ruby/src/terminal/queries.rs +249 -0
- data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
- data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
- data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
- data/ext/ratatui_ruby/src/text.rs +225 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
- data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
- data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
- data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
- data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
- data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
- data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
- data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
- data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
- data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
- data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
- data/lib/ratatui_ruby/3.2/ratatui_ruby.bundle +0 -0
- data/lib/ratatui_ruby/3.3/ratatui_ruby.bundle +0 -0
- data/lib/ratatui_ruby/3.4/ratatui_ruby.bundle +0 -0
- data/lib/ratatui_ruby/4.0/ratatui_ruby.bundle +0 -0
- data/lib/ratatui_ruby/backend/window_size.rb +50 -0
- data/lib/ratatui_ruby/backend.rb +59 -0
- data/lib/ratatui_ruby/buffer/cell.rb +212 -0
- data/lib/ratatui_ruby/buffer.rb +149 -0
- data/lib/ratatui_ruby/cell.rb +208 -0
- data/lib/ratatui_ruby/debug.rb +215 -0
- data/lib/ratatui_ruby/draw.rb +63 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
- data/lib/ratatui_ruby/event/key/character.rb +53 -0
- data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
- data/lib/ratatui_ruby/event/key/media.rb +46 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
- data/lib/ratatui_ruby/event/key/system.rb +47 -0
- data/lib/ratatui_ruby/event/key.rb +479 -0
- data/lib/ratatui_ruby/event/mouse.rb +291 -0
- data/lib/ratatui_ruby/event/none.rb +53 -0
- data/lib/ratatui_ruby/event/paste.rb +130 -0
- data/lib/ratatui_ruby/event/resize.rb +221 -0
- data/lib/ratatui_ruby/event/sync.rb +52 -0
- data/lib/ratatui_ruby/event.rb +163 -0
- data/lib/ratatui_ruby/frame.rb +257 -0
- data/lib/ratatui_ruby/labs/a11y.rb +182 -0
- data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
- data/lib/ratatui_ruby/labs.rb +47 -0
- data/lib/ratatui_ruby/layout/alignment.rb +91 -0
- data/lib/ratatui_ruby/layout/constraint.rb +337 -0
- data/lib/ratatui_ruby/layout/layout.rb +258 -0
- data/lib/ratatui_ruby/layout/position.rb +81 -0
- data/lib/ratatui_ruby/layout/rect.rb +733 -0
- data/lib/ratatui_ruby/layout/size.rb +62 -0
- data/lib/ratatui_ruby/layout.rb +29 -0
- data/lib/ratatui_ruby/list_state.rb +201 -0
- data/lib/ratatui_ruby/output_guard.rb +171 -0
- data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
- data/lib/ratatui_ruby/style/color.rb +149 -0
- data/lib/ratatui_ruby/style/style.rb +147 -0
- data/lib/ratatui_ruby/style.rb +19 -0
- data/lib/ratatui_ruby/symbols.rb +435 -0
- data/lib/ratatui_ruby/synthetic_events.rb +106 -0
- data/lib/ratatui_ruby/table_state.rb +251 -0
- data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
- data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
- data/lib/ratatui_ruby/terminal.rb +66 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +244 -0
- data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
- data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
- data/lib/ratatui_ruby/test_helper.rb +115 -0
- data/lib/ratatui_ruby/text/line.rb +245 -0
- data/lib/ratatui_ruby/text/span.rb +158 -0
- data/lib/ratatui_ruby/text.rb +99 -0
- data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
- data/lib/ratatui_ruby/tui/core.rb +67 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
- data/lib/ratatui_ruby/tui.rb +106 -0
- data/lib/ratatui_ruby/version.rb +12 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
- data/lib/ratatui_ruby/widgets/block.rb +266 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
- data/lib/ratatui_ruby/widgets/cell.rb +59 -0
- data/lib/ratatui_ruby/widgets/center.rb +71 -0
- data/lib/ratatui_ruby/widgets/chart.rb +172 -0
- data/lib/ratatui_ruby/widgets/clear.rb +66 -0
- data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
- data/lib/ratatui_ruby/widgets/list.rb +252 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
- data/lib/ratatui_ruby/widgets/row.rb +123 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
- data/lib/ratatui_ruby/widgets/table.rb +213 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
- data/lib/ratatui_ruby/widgets.rb +43 -0
- data/lib/ratatui_ruby.rb +556 -0
- data/sig/examples/app_all_events/app.rbs +11 -0
- data/sig/examples/app_all_events/model/app_model.rbs +23 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +23 -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 +14 -0
- data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
- data/sig/examples/app_color_picker/app.rbs +17 -0
- data/sig/examples/app_external_editor/app.rbs +12 -0
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_stateful_interaction/app.rbs +39 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
- data/sig/examples/verify_readme_usage/app.rbs +17 -0
- data/sig/examples/widget_block_demo/app.rbs +38 -0
- data/sig/examples/widget_box_demo/app.rbs +17 -0
- data/sig/examples/widget_calendar_demo/app.rbs +17 -0
- data/sig/examples/widget_cell_demo/app.rbs +17 -0
- data/sig/examples/widget_chart_demo/app.rbs +17 -0
- data/sig/examples/widget_gauge_demo/app.rbs +17 -0
- data/sig/examples/widget_layout_split/app.rbs +16 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
- data/sig/examples/widget_list_demo/app.rbs +17 -0
- data/sig/examples/widget_map_demo/app.rbs +17 -0
- data/sig/examples/widget_popup_demo/app.rbs +17 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
- data/sig/examples/widget_rect/app.rbs +18 -0
- data/sig/examples/widget_render/app.rbs +16 -0
- data/sig/examples/widget_rich_text/app.rbs +17 -0
- data/sig/examples/widget_scroll_text/app.rbs +17 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
- data/sig/examples/widget_style_colors/app.rbs +20 -0
- data/sig/examples/widget_table_demo/app.rbs +17 -0
- data/sig/examples/widget_text_width/app.rbs +16 -0
- data/sig/generated/event_key_predicates.rbs +1348 -0
- data/sig/manifest.yaml +5 -0
- data/sig/patches/data.rbs +26 -0
- data/sig/patches/debugger__.rbs +8 -0
- data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
- data/sig/ratatui_ruby/backend.rbs +12 -0
- data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
- data/sig/ratatui_ruby/buffer.rbs +18 -0
- data/sig/ratatui_ruby/cell.rbs +44 -0
- data/sig/ratatui_ruby/clear.rbs +18 -0
- data/sig/ratatui_ruby/constraint.rbs +26 -0
- data/sig/ratatui_ruby/debug.rbs +45 -0
- data/sig/ratatui_ruby/draw.rbs +30 -0
- data/sig/ratatui_ruby/event.rbs +249 -0
- data/sig/ratatui_ruby/frame.rbs +23 -0
- data/sig/ratatui_ruby/interfaces.rbs +25 -0
- data/sig/ratatui_ruby/labs.rbs +90 -0
- data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
- data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
- data/sig/ratatui_ruby/layout/layout.rbs +45 -0
- data/sig/ratatui_ruby/layout/position.rbs +18 -0
- data/sig/ratatui_ruby/layout/rect.rbs +64 -0
- data/sig/ratatui_ruby/layout/size.rbs +18 -0
- data/sig/ratatui_ruby/list_state.rbs +23 -0
- data/sig/ratatui_ruby/output_guard.rbs +23 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
- data/sig/ratatui_ruby/rect.rbs +17 -0
- data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
- data/sig/ratatui_ruby/session.rbs +93 -0
- data/sig/ratatui_ruby/style/color.rbs +22 -0
- data/sig/ratatui_ruby/style/style.rbs +29 -0
- data/sig/ratatui_ruby/symbols.rbs +141 -0
- data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
- data/sig/ratatui_ruby/table_state.rbs +27 -0
- data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
- data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
- data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
- data/sig/ratatui_ruby/test_helper.rbs +18 -0
- data/sig/ratatui_ruby/text/line.rbs +27 -0
- data/sig/ratatui_ruby/text/span.rbs +23 -0
- data/sig/ratatui_ruby/text.rbs +12 -0
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
- data/sig/ratatui_ruby/tui/core.rbs +23 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
- data/sig/ratatui_ruby/tui.rbs +25 -0
- data/sig/ratatui_ruby/version.rbs +12 -0
- data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
- data/sig/ratatui_ruby/widgets/block.rbs +51 -0
- data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
- data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
- data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
- data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
- data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
- data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
- data/sig/ratatui_ruby/widgets/list.rbs +63 -0
- data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
- data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
- data/sig/ratatui_ruby/widgets/row.rbs +43 -0
- data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
- data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
- data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
- data/sig/ratatui_ruby/widgets/table.rbs +78 -0
- data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
- data/sig/ratatui_ruby/widgets.rbs +16 -0
- data/vendor/goodcop/base.yml +1047 -0
- metadata +738 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
//! Terminal query functions (read-only access to terminal state).
|
|
5
|
+
|
|
6
|
+
use magnus::value::ReprValue;
|
|
7
|
+
use magnus::{Error, Module, Value};
|
|
8
|
+
|
|
9
|
+
use super::TerminalWrapper;
|
|
10
|
+
|
|
11
|
+
pub fn get_buffer_content() -> Result<String, Error> {
|
|
12
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
13
|
+
|
|
14
|
+
// This function builds a string from all cells - we need direct terminal access
|
|
15
|
+
crate::terminal::with_terminal_mut(|wrapper| {
|
|
16
|
+
if let TerminalWrapper::Test(terminal) = wrapper {
|
|
17
|
+
let buffer = terminal.backend().buffer();
|
|
18
|
+
let area = buffer.area;
|
|
19
|
+
let mut result = String::new();
|
|
20
|
+
for y in 0..area.height {
|
|
21
|
+
// SPDX-SnippetBegin
|
|
22
|
+
// SPDX-License-Identifier: MIT
|
|
23
|
+
// SPDX-SnippetCopyrightText: The Ratatui Developers
|
|
24
|
+
// Derived from ratatui-core/src/buffer/buffer.rs (Buffer Debug impl)
|
|
25
|
+
let mut skip: usize = 0;
|
|
26
|
+
for x in 0..area.width {
|
|
27
|
+
if skip > 0 {
|
|
28
|
+
skip -= 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
let cell = buffer.cell((x, y)).unwrap();
|
|
32
|
+
let symbol = cell.symbol();
|
|
33
|
+
result.push_str(symbol);
|
|
34
|
+
// Skip continuation cells after wide characters (e.g. emoji, CJK).
|
|
35
|
+
// Ratatui's set_stringn resets these to " ", but they don't represent
|
|
36
|
+
// an additional display column — the preceding wide symbol covers them.
|
|
37
|
+
let width = ratatui::text::Text::raw(symbol).width();
|
|
38
|
+
if width > 1 {
|
|
39
|
+
skip = width - 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// SPDX-SnippetEnd
|
|
43
|
+
result.push('\n');
|
|
44
|
+
}
|
|
45
|
+
Ok(result)
|
|
46
|
+
} else {
|
|
47
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
48
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
49
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
50
|
+
Err(Error::new(
|
|
51
|
+
error_class,
|
|
52
|
+
"Terminal is not initialized as TestBackend",
|
|
53
|
+
))
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.unwrap_or_else(|| {
|
|
57
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
58
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
59
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
60
|
+
Err(Error::new(error_class, "Terminal is not initialized"))
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn get_terminal_area() -> Result<magnus::RHash, Error> {
|
|
65
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
66
|
+
|
|
67
|
+
crate::terminal::with_query(|q| {
|
|
68
|
+
let area = q.viewport_area();
|
|
69
|
+
let hash = ruby.hash_new();
|
|
70
|
+
hash.aset("x", area.x).unwrap();
|
|
71
|
+
hash.aset("y", area.y).unwrap();
|
|
72
|
+
hash.aset("width", area.width).unwrap();
|
|
73
|
+
hash.aset("height", area.height).unwrap();
|
|
74
|
+
Ok(hash)
|
|
75
|
+
})
|
|
76
|
+
.unwrap_or_else(|| {
|
|
77
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
78
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
79
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
80
|
+
Err(Error::new(error_class, "Terminal is not initialized"))
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Returns the full terminal backend size (not the viewport)
|
|
85
|
+
pub fn get_terminal_size() -> Result<magnus::RHash, Error> {
|
|
86
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
87
|
+
|
|
88
|
+
crate::terminal::with_query(|q| {
|
|
89
|
+
let size = q.size();
|
|
90
|
+
let hash = ruby.hash_new();
|
|
91
|
+
hash.aset("x", 0).unwrap();
|
|
92
|
+
hash.aset("y", 0).unwrap();
|
|
93
|
+
hash.aset("width", size.width).unwrap();
|
|
94
|
+
hash.aset("height", size.height).unwrap();
|
|
95
|
+
Ok(hash)
|
|
96
|
+
})
|
|
97
|
+
.unwrap_or_else(|| {
|
|
98
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
99
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
100
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
101
|
+
Err(Error::new(error_class, "Terminal is not initialized"))
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fn get_viewport_type() -> Result<String, Error> {
|
|
106
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
107
|
+
|
|
108
|
+
crate::terminal::with_query(|q| {
|
|
109
|
+
let viewport = q.viewport_area();
|
|
110
|
+
let size = q.size();
|
|
111
|
+
if viewport.height < size.height {
|
|
112
|
+
Ok("inline".to_string())
|
|
113
|
+
} else {
|
|
114
|
+
Ok("fullscreen".to_string())
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
.unwrap_or_else(|| {
|
|
118
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
119
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
120
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
121
|
+
Err(Error::new(error_class, "Terminal not initialized"))
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub fn get_cursor_position() -> Result<Option<(u16, u16)>, Error> {
|
|
126
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
127
|
+
|
|
128
|
+
crate::terminal::with_query(|q| Ok(q.cursor_position())).unwrap_or_else(|| {
|
|
129
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
130
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
131
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
132
|
+
Err(Error::new(
|
|
133
|
+
error_class,
|
|
134
|
+
"Terminal is not initialized as TestBackend",
|
|
135
|
+
))
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pub fn get_cell_at(x: u16, y: u16) -> Result<magnus::RHash, Error> {
|
|
140
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
141
|
+
|
|
142
|
+
crate::terminal::with_query(|q| {
|
|
143
|
+
if let Some(cell) = q.cell_at(x, y) {
|
|
144
|
+
let hash = ruby.hash_new();
|
|
145
|
+
hash.aset("char", cell.symbol()).unwrap();
|
|
146
|
+
hash.aset("fg", color_to_value(cell.fg)).unwrap();
|
|
147
|
+
hash.aset("bg", color_to_value(cell.bg)).unwrap();
|
|
148
|
+
hash.aset("underline_color", color_to_value(cell.underline_color))
|
|
149
|
+
.unwrap();
|
|
150
|
+
hash.aset("modifiers", modifiers_to_value(cell.modifier))
|
|
151
|
+
.unwrap();
|
|
152
|
+
Ok(hash)
|
|
153
|
+
} else {
|
|
154
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
155
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
156
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
157
|
+
Err(Error::new(
|
|
158
|
+
error_class,
|
|
159
|
+
format!("Coordinates ({x}, {y}) out of bounds"),
|
|
160
|
+
))
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.unwrap_or_else(|| {
|
|
164
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
165
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
166
|
+
let error_class = error_base.const_get("Terminal").unwrap();
|
|
167
|
+
Err(Error::new(
|
|
168
|
+
error_class,
|
|
169
|
+
"Terminal is not initialized as TestBackend",
|
|
170
|
+
))
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn color_to_value(color: ratatui::style::Color) -> Value {
|
|
175
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
176
|
+
match color {
|
|
177
|
+
ratatui::style::Color::Reset => ruby.qnil().as_value(),
|
|
178
|
+
ratatui::style::Color::Black => ruby.to_symbol("black").as_value(),
|
|
179
|
+
ratatui::style::Color::Red => ruby.to_symbol("red").as_value(),
|
|
180
|
+
ratatui::style::Color::Green => ruby.to_symbol("green").as_value(),
|
|
181
|
+
ratatui::style::Color::Yellow => ruby.to_symbol("yellow").as_value(),
|
|
182
|
+
ratatui::style::Color::Blue => ruby.to_symbol("blue").as_value(),
|
|
183
|
+
ratatui::style::Color::Magenta => ruby.to_symbol("magenta").as_value(),
|
|
184
|
+
ratatui::style::Color::Cyan => ruby.to_symbol("cyan").as_value(),
|
|
185
|
+
ratatui::style::Color::Gray => ruby.to_symbol("gray").as_value(),
|
|
186
|
+
ratatui::style::Color::DarkGray => ruby.to_symbol("dark_gray").as_value(),
|
|
187
|
+
ratatui::style::Color::LightRed => ruby.to_symbol("light_red").as_value(),
|
|
188
|
+
ratatui::style::Color::LightGreen => ruby.to_symbol("light_green").as_value(),
|
|
189
|
+
ratatui::style::Color::LightYellow => ruby.to_symbol("light_yellow").as_value(),
|
|
190
|
+
ratatui::style::Color::LightBlue => ruby.to_symbol("light_blue").as_value(),
|
|
191
|
+
ratatui::style::Color::LightMagenta => ruby.to_symbol("light_magenta").as_value(),
|
|
192
|
+
ratatui::style::Color::LightCyan => ruby.to_symbol("light_cyan").as_value(),
|
|
193
|
+
ratatui::style::Color::White => ruby.to_symbol("white").as_value(),
|
|
194
|
+
ratatui::style::Color::Rgb(r, g, b) => ruby
|
|
195
|
+
.str_new(&(format!("#{r:02x}{g:02x}{b:02x}")))
|
|
196
|
+
.as_value(),
|
|
197
|
+
ratatui::style::Color::Indexed(i) => ruby.to_symbol(format!("indexed_{i}")).as_value(),
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fn modifiers_to_value(modifier: ratatui::style::Modifier) -> Value {
|
|
202
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
203
|
+
let ary = ruby.ary_new();
|
|
204
|
+
|
|
205
|
+
if modifier.contains(ratatui::style::Modifier::BOLD) {
|
|
206
|
+
let _ = ary.push(ruby.to_symbol("bold"));
|
|
207
|
+
}
|
|
208
|
+
if modifier.contains(ratatui::style::Modifier::ITALIC) {
|
|
209
|
+
let _ = ary.push(ruby.to_symbol("italic"));
|
|
210
|
+
}
|
|
211
|
+
if modifier.contains(ratatui::style::Modifier::DIM) {
|
|
212
|
+
let _ = ary.push(ruby.to_symbol("dim"));
|
|
213
|
+
}
|
|
214
|
+
if modifier.contains(ratatui::style::Modifier::UNDERLINED) {
|
|
215
|
+
let _ = ary.push(ruby.to_symbol("underlined"));
|
|
216
|
+
}
|
|
217
|
+
if modifier.contains(ratatui::style::Modifier::REVERSED) {
|
|
218
|
+
let _ = ary.push(ruby.to_symbol("reversed"));
|
|
219
|
+
}
|
|
220
|
+
if modifier.contains(ratatui::style::Modifier::HIDDEN) {
|
|
221
|
+
let _ = ary.push(ruby.to_symbol("hidden"));
|
|
222
|
+
}
|
|
223
|
+
if modifier.contains(ratatui::style::Modifier::CROSSED_OUT) {
|
|
224
|
+
let _ = ary.push(ruby.to_symbol("crossed_out"));
|
|
225
|
+
}
|
|
226
|
+
if modifier.contains(ratatui::style::Modifier::SLOW_BLINK) {
|
|
227
|
+
let _ = ary.push(ruby.to_symbol("slow_blink"));
|
|
228
|
+
}
|
|
229
|
+
if modifier.contains(ratatui::style::Modifier::RAPID_BLINK) {
|
|
230
|
+
let _ = ary.push(ruby.to_symbol("rapid_blink"));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
ary.as_value()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Returns the number of frames that have been drawn
|
|
237
|
+
pub fn frame_count() -> Result<usize, Error> {
|
|
238
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
239
|
+
|
|
240
|
+
crate::terminal::with_query(|q| Ok(q.frame_count())).unwrap_or_else(|| {
|
|
241
|
+
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
242
|
+
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
243
|
+
let error_class = error_base.const_get("Invariant").unwrap();
|
|
244
|
+
Err(Error::new(
|
|
245
|
+
error_class,
|
|
246
|
+
"Cannot query frame_count: terminal not initialized. Use RatatuiRuby.run or call init_terminal first.",
|
|
247
|
+
))
|
|
248
|
+
})
|
|
249
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
//! Terminal query trait and implementations.
|
|
5
|
+
//!
|
|
6
|
+
//! This module provides a compiler-enforced interface for querying terminal state.
|
|
7
|
+
//! All queries go through `TerminalQuery` trait which has two implementations:
|
|
8
|
+
//! - `LiveTerminal`: Used outside draw, queries actual terminal
|
|
9
|
+
//! - `DrawSnapshot`: Used during draw, queries pre-captured snapshot
|
|
10
|
+
|
|
11
|
+
use ratatui::buffer::Cell;
|
|
12
|
+
use ratatui::layout::Rect;
|
|
13
|
+
|
|
14
|
+
/// All terminal queries MUST go through this trait.
|
|
15
|
+
/// Compiler enforces both implementations when adding new queries.
|
|
16
|
+
pub trait TerminalQuery {
|
|
17
|
+
fn size(&self) -> Rect;
|
|
18
|
+
fn viewport_area(&self) -> Rect;
|
|
19
|
+
fn is_test_mode(&self) -> bool;
|
|
20
|
+
fn cursor_position(&self) -> Option<(u16, u16)>;
|
|
21
|
+
fn cell_at(&self, x: u16, y: u16) -> Option<Cell>;
|
|
22
|
+
fn frame_count(&self) -> usize;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Snapshot of terminal state captured before draw.
|
|
26
|
+
/// Answers queries during draw without accessing terminal.
|
|
27
|
+
#[derive(Clone)]
|
|
28
|
+
pub struct DrawSnapshot {
|
|
29
|
+
pub size: Rect,
|
|
30
|
+
pub viewport_area: Rect,
|
|
31
|
+
pub is_test_mode: bool,
|
|
32
|
+
pub cursor_position: Option<(u16, u16)>,
|
|
33
|
+
pub buffer: Option<ratatui::buffer::Buffer>,
|
|
34
|
+
pub frame_count: usize,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl DrawSnapshot {
|
|
38
|
+
/// Capture snapshot from a `TerminalWrapper`.
|
|
39
|
+
pub fn capture(wrapper: &mut super::wrapper::TerminalWrapper) -> Self {
|
|
40
|
+
match wrapper {
|
|
41
|
+
super::wrapper::TerminalWrapper::Crossterm(t) => {
|
|
42
|
+
let size = t.size().unwrap_or_default();
|
|
43
|
+
let frame = t.get_frame();
|
|
44
|
+
let viewport = frame.area();
|
|
45
|
+
let count = frame.count();
|
|
46
|
+
Self {
|
|
47
|
+
size: Rect::new(0, 0, size.width, size.height),
|
|
48
|
+
viewport_area: viewport,
|
|
49
|
+
is_test_mode: false,
|
|
50
|
+
cursor_position: None,
|
|
51
|
+
buffer: None,
|
|
52
|
+
frame_count: count,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
super::wrapper::TerminalWrapper::Test(t) => {
|
|
56
|
+
let size = t.size().unwrap_or_default();
|
|
57
|
+
let frame = t.get_frame();
|
|
58
|
+
let viewport = frame.area();
|
|
59
|
+
let count = frame.count();
|
|
60
|
+
let cursor = t.get_cursor_position().ok().map(Into::into);
|
|
61
|
+
let buffer = t.backend().buffer().clone();
|
|
62
|
+
Self {
|
|
63
|
+
size: Rect::new(0, 0, size.width, size.height),
|
|
64
|
+
viewport_area: viewport,
|
|
65
|
+
is_test_mode: true,
|
|
66
|
+
cursor_position: cursor,
|
|
67
|
+
buffer: Some(buffer),
|
|
68
|
+
frame_count: count,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl TerminalQuery for DrawSnapshot {
|
|
76
|
+
fn size(&self) -> Rect {
|
|
77
|
+
self.size
|
|
78
|
+
}
|
|
79
|
+
fn viewport_area(&self) -> Rect {
|
|
80
|
+
self.viewport_area
|
|
81
|
+
}
|
|
82
|
+
fn is_test_mode(&self) -> bool {
|
|
83
|
+
self.is_test_mode
|
|
84
|
+
}
|
|
85
|
+
fn cursor_position(&self) -> Option<(u16, u16)> {
|
|
86
|
+
self.cursor_position
|
|
87
|
+
}
|
|
88
|
+
fn cell_at(&self, x: u16, y: u16) -> Option<Cell> {
|
|
89
|
+
self.buffer.as_ref()?.cell((x, y)).cloned()
|
|
90
|
+
}
|
|
91
|
+
fn frame_count(&self) -> usize {
|
|
92
|
+
self.frame_count
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Live queries against actual terminal (outside draw).
|
|
97
|
+
/// Uses interior mutability since ratatui's API requires &mut for some queries.
|
|
98
|
+
pub struct LiveTerminal<'a>(std::cell::RefCell<&'a mut super::wrapper::TerminalWrapper>);
|
|
99
|
+
|
|
100
|
+
impl<'a> LiveTerminal<'a> {
|
|
101
|
+
pub fn new(wrapper: &'a mut super::wrapper::TerminalWrapper) -> Self {
|
|
102
|
+
Self(std::cell::RefCell::new(wrapper))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
impl TerminalQuery for LiveTerminal<'_> {
|
|
107
|
+
fn size(&self) -> Rect {
|
|
108
|
+
match *self.0.borrow() {
|
|
109
|
+
super::wrapper::TerminalWrapper::Crossterm(ref t) => {
|
|
110
|
+
let s = t.size().unwrap_or_default();
|
|
111
|
+
Rect::new(0, 0, s.width, s.height)
|
|
112
|
+
}
|
|
113
|
+
super::wrapper::TerminalWrapper::Test(ref t) => {
|
|
114
|
+
let s = t.size().unwrap_or_default();
|
|
115
|
+
Rect::new(0, 0, s.width, s.height)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn viewport_area(&self) -> Rect {
|
|
121
|
+
match *self.0.borrow_mut() {
|
|
122
|
+
super::wrapper::TerminalWrapper::Crossterm(ref mut t) => t.get_frame().area(),
|
|
123
|
+
super::wrapper::TerminalWrapper::Test(ref mut t) => t.get_frame().area(),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
fn is_test_mode(&self) -> bool {
|
|
127
|
+
matches!(*self.0.borrow(), super::wrapper::TerminalWrapper::Test(_))
|
|
128
|
+
}
|
|
129
|
+
fn cursor_position(&self) -> Option<(u16, u16)> {
|
|
130
|
+
match *self.0.borrow_mut() {
|
|
131
|
+
super::wrapper::TerminalWrapper::Test(ref mut t) => {
|
|
132
|
+
t.get_cursor_position().ok().map(Into::into)
|
|
133
|
+
}
|
|
134
|
+
super::wrapper::TerminalWrapper::Crossterm(_) => None,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
fn cell_at(&self, x: u16, y: u16) -> Option<Cell> {
|
|
138
|
+
match &*self.0.borrow() {
|
|
139
|
+
super::wrapper::TerminalWrapper::Test(t) => t.backend().buffer().cell((x, y)).cloned(),
|
|
140
|
+
super::wrapper::TerminalWrapper::Crossterm(_) => None,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
fn frame_count(&self) -> usize {
|
|
144
|
+
match *self.0.borrow_mut() {
|
|
145
|
+
super::wrapper::TerminalWrapper::Crossterm(ref mut t) => t.get_frame().count(),
|
|
146
|
+
super::wrapper::TerminalWrapper::Test(ref mut t) => t.get_frame().count(),
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[cfg(test)]
|
|
152
|
+
mod tests {
|
|
153
|
+
use super::*;
|
|
154
|
+
|
|
155
|
+
// Test: TerminalQuery trait can be implemented by a simple struct
|
|
156
|
+
struct MockQuery {
|
|
157
|
+
size: Rect,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
impl TerminalQuery for MockQuery {
|
|
161
|
+
fn size(&self) -> Rect {
|
|
162
|
+
self.size
|
|
163
|
+
}
|
|
164
|
+
fn viewport_area(&self) -> Rect {
|
|
165
|
+
Rect::default()
|
|
166
|
+
}
|
|
167
|
+
fn is_test_mode(&self) -> bool {
|
|
168
|
+
false
|
|
169
|
+
}
|
|
170
|
+
fn cursor_position(&self) -> Option<(u16, u16)> {
|
|
171
|
+
None
|
|
172
|
+
}
|
|
173
|
+
fn cell_at(&self, _x: u16, _y: u16) -> Option<Cell> {
|
|
174
|
+
None
|
|
175
|
+
}
|
|
176
|
+
fn frame_count(&self) -> usize {
|
|
177
|
+
0
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#[test]
|
|
182
|
+
fn test_trait_can_be_implemented() {
|
|
183
|
+
let query = MockQuery {
|
|
184
|
+
size: Rect::new(0, 0, 80, 24),
|
|
185
|
+
};
|
|
186
|
+
assert_eq!(query.size(), Rect::new(0, 0, 80, 24));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[test]
|
|
190
|
+
fn test_draw_snapshot_returns_size() {
|
|
191
|
+
let snapshot = super::DrawSnapshot {
|
|
192
|
+
size: Rect::new(0, 0, 120, 40),
|
|
193
|
+
viewport_area: Rect::new(0, 0, 120, 40),
|
|
194
|
+
is_test_mode: false,
|
|
195
|
+
cursor_position: None,
|
|
196
|
+
buffer: None,
|
|
197
|
+
frame_count: 0,
|
|
198
|
+
};
|
|
199
|
+
assert_eq!(snapshot.size(), Rect::new(0, 0, 120, 40));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#[test]
|
|
203
|
+
fn test_draw_snapshot_returns_viewport_area() {
|
|
204
|
+
let snapshot = super::DrawSnapshot {
|
|
205
|
+
size: Rect::new(0, 0, 120, 40),
|
|
206
|
+
viewport_area: Rect::new(5, 10, 80, 20), // Different from size!
|
|
207
|
+
is_test_mode: false,
|
|
208
|
+
cursor_position: None,
|
|
209
|
+
buffer: None,
|
|
210
|
+
frame_count: 0,
|
|
211
|
+
};
|
|
212
|
+
// This should return the stored viewport_area, not Rect::default()
|
|
213
|
+
assert_eq!(snapshot.viewport_area(), Rect::new(5, 10, 80, 20));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn test_draw_snapshot_returns_is_test_mode() {
|
|
218
|
+
let snapshot = super::DrawSnapshot {
|
|
219
|
+
size: Rect::default(),
|
|
220
|
+
viewport_area: Rect::default(),
|
|
221
|
+
is_test_mode: true, // TRUE!
|
|
222
|
+
cursor_position: None,
|
|
223
|
+
buffer: None,
|
|
224
|
+
frame_count: 0,
|
|
225
|
+
};
|
|
226
|
+
assert!(snapshot.is_test_mode());
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#[test]
|
|
230
|
+
fn test_draw_snapshot_returns_cursor_position() {
|
|
231
|
+
let snapshot = super::DrawSnapshot {
|
|
232
|
+
size: Rect::default(),
|
|
233
|
+
viewport_area: Rect::default(),
|
|
234
|
+
is_test_mode: false,
|
|
235
|
+
cursor_position: Some((15, 20)), // Not None!
|
|
236
|
+
buffer: None,
|
|
237
|
+
frame_count: 0,
|
|
238
|
+
};
|
|
239
|
+
assert_eq!(snapshot.cursor_position(), Some((15, 20)));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#[test]
|
|
243
|
+
fn test_draw_snapshot_returns_cell_at() {
|
|
244
|
+
use ratatui::buffer::Buffer;
|
|
245
|
+
|
|
246
|
+
// Create a buffer with a known cell
|
|
247
|
+
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
|
|
248
|
+
buffer[(5, 5)].set_char('X');
|
|
249
|
+
|
|
250
|
+
let snapshot = super::DrawSnapshot {
|
|
251
|
+
size: Rect::new(0, 0, 10, 10),
|
|
252
|
+
viewport_area: Rect::new(0, 0, 10, 10),
|
|
253
|
+
is_test_mode: true,
|
|
254
|
+
cursor_position: None,
|
|
255
|
+
buffer: Some(buffer),
|
|
256
|
+
frame_count: 0,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
let cell = snapshot.cell_at(5, 5);
|
|
260
|
+
assert!(cell.is_some());
|
|
261
|
+
assert_eq!(cell.unwrap().symbol(), "X");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[test]
|
|
265
|
+
fn test_capture_from_terminal_wrapper() {
|
|
266
|
+
use crate::terminal::TerminalWrapper;
|
|
267
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
268
|
+
|
|
269
|
+
let backend = TestBackend::new(80, 24);
|
|
270
|
+
let terminal = Terminal::new(backend).unwrap();
|
|
271
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
272
|
+
|
|
273
|
+
let snapshot = super::DrawSnapshot::capture(&mut wrapper);
|
|
274
|
+
|
|
275
|
+
assert_eq!(snapshot.size(), Rect::new(0, 0, 80, 24));
|
|
276
|
+
assert!(snapshot.is_test_mode());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#[test]
|
|
280
|
+
fn test_live_terminal_exists() {
|
|
281
|
+
use crate::terminal::TerminalWrapper;
|
|
282
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
283
|
+
|
|
284
|
+
let backend = TestBackend::new(80, 24);
|
|
285
|
+
let terminal = Terminal::new(backend).unwrap();
|
|
286
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
287
|
+
|
|
288
|
+
// LiveTerminal should exist and wrap a TerminalWrapper reference
|
|
289
|
+
let _live = super::LiveTerminal::new(&mut wrapper);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#[test]
|
|
293
|
+
fn test_live_terminal_returns_size() {
|
|
294
|
+
use crate::terminal::TerminalWrapper;
|
|
295
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
296
|
+
|
|
297
|
+
let backend = TestBackend::new(100, 50);
|
|
298
|
+
let terminal = Terminal::new(backend).unwrap();
|
|
299
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
300
|
+
|
|
301
|
+
let live = super::LiveTerminal::new(&mut wrapper);
|
|
302
|
+
// LiveTerminal must implement TerminalQuery
|
|
303
|
+
let size = live.size();
|
|
304
|
+
assert_eq!(size.width, 100);
|
|
305
|
+
assert_eq!(size.height, 50);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#[test]
|
|
309
|
+
fn test_live_terminal_returns_is_test_mode() {
|
|
310
|
+
use crate::terminal::TerminalWrapper;
|
|
311
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
312
|
+
|
|
313
|
+
let backend = TestBackend::new(80, 24);
|
|
314
|
+
let terminal = Terminal::new(backend).unwrap();
|
|
315
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
316
|
+
|
|
317
|
+
let live = super::LiveTerminal::new(&mut wrapper);
|
|
318
|
+
// TestBackend should return true for is_test_mode
|
|
319
|
+
assert!(live.is_test_mode());
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[test]
|
|
323
|
+
fn test_live_terminal_returns_viewport_area() {
|
|
324
|
+
use crate::terminal::TerminalWrapper;
|
|
325
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
326
|
+
|
|
327
|
+
let backend = TestBackend::new(120, 40);
|
|
328
|
+
let terminal = Terminal::new(backend).unwrap();
|
|
329
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
330
|
+
|
|
331
|
+
let live = super::LiveTerminal::new(&mut wrapper);
|
|
332
|
+
let viewport = live.viewport_area();
|
|
333
|
+
// Viewport should match the terminal size for fullscreen mode
|
|
334
|
+
assert_eq!(viewport.width, 120);
|
|
335
|
+
assert_eq!(viewport.height, 40);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[test]
|
|
339
|
+
fn test_live_terminal_viewport_area_differs_from_size_for_inline() {
|
|
340
|
+
use crate::terminal::TerminalWrapper;
|
|
341
|
+
use ratatui::{backend::TestBackend, Terminal, Viewport};
|
|
342
|
+
|
|
343
|
+
// Create terminal with inline viewport of height 5 in a 40-line terminal
|
|
344
|
+
let backend = TestBackend::new(80, 40);
|
|
345
|
+
let options = ratatui::TerminalOptions {
|
|
346
|
+
viewport: Viewport::Inline(5),
|
|
347
|
+
};
|
|
348
|
+
let terminal = Terminal::with_options(backend, options).unwrap();
|
|
349
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
350
|
+
|
|
351
|
+
let live = super::LiveTerminal::new(&mut wrapper);
|
|
352
|
+
let size = live.size();
|
|
353
|
+
let viewport = live.viewport_area();
|
|
354
|
+
|
|
355
|
+
// Terminal size is 80x40
|
|
356
|
+
assert_eq!(size.width, 80);
|
|
357
|
+
assert_eq!(size.height, 40);
|
|
358
|
+
|
|
359
|
+
// But viewport is only 5 lines high (inline mode)
|
|
360
|
+
assert_eq!(viewport.width, 80);
|
|
361
|
+
assert_eq!(viewport.height, 5);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#[test]
|
|
365
|
+
fn test_live_terminal_frame_count_increases_after_draw() {
|
|
366
|
+
use crate::terminal::TerminalWrapper;
|
|
367
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
368
|
+
|
|
369
|
+
let backend = TestBackend::new(80, 24);
|
|
370
|
+
let mut terminal = Terminal::new(backend).unwrap();
|
|
371
|
+
|
|
372
|
+
// Draw once to increment the counter
|
|
373
|
+
terminal.draw(|_frame| {}).unwrap();
|
|
374
|
+
|
|
375
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
376
|
+
let live = super::LiveTerminal::new(&mut wrapper);
|
|
377
|
+
|
|
378
|
+
// Frame count should be 1 after one draw, NOT 0!
|
|
379
|
+
assert_eq!(live.frame_count(), 1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn test_live_terminal_frame_count_increases_with_multiple_draws() {
|
|
384
|
+
use crate::terminal::TerminalWrapper;
|
|
385
|
+
use ratatui::{backend::TestBackend, Terminal};
|
|
386
|
+
|
|
387
|
+
let backend = TestBackend::new(80, 24);
|
|
388
|
+
let mut terminal = Terminal::new(backend).unwrap();
|
|
389
|
+
|
|
390
|
+
// Draw twice
|
|
391
|
+
terminal.draw(|_frame| {}).unwrap();
|
|
392
|
+
terminal.draw(|_frame| {}).unwrap();
|
|
393
|
+
|
|
394
|
+
let mut wrapper = TerminalWrapper::Test(terminal);
|
|
395
|
+
let live = super::LiveTerminal::new(&mut wrapper);
|
|
396
|
+
|
|
397
|
+
// Frame count should be 2 after two draws!
|
|
398
|
+
assert_eq!(live.frame_count(), 2);
|
|
399
|
+
}
|
|
400
|
+
}
|