ratatui_ruby 1.4.0-x86_64-linux
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 +1737 -0
- data/ext/ratatui_ruby/Cargo.toml +24 -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 +700 -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 +101 -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 +231 -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/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/ratatui_ruby.so +0 -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 +241 -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 +555 -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 +729 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
use crate::errors::type_error_with_context;
|
|
5
|
+
use crate::style::parse_block;
|
|
6
|
+
use crate::text::{parse_line, parse_span};
|
|
7
|
+
use bumpalo::Bump;
|
|
8
|
+
use magnus::{prelude::*, Error, Value};
|
|
9
|
+
use ratatui::buffer::Buffer;
|
|
10
|
+
use ratatui::{layout::Rect, text::Line, widgets::Tabs, widgets::Widget};
|
|
11
|
+
|
|
12
|
+
pub fn render(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
|
|
13
|
+
let bump = Bump::new();
|
|
14
|
+
let tabs = create_tabs(node, &bump)?;
|
|
15
|
+
tabs.render(area, buffer);
|
|
16
|
+
Ok(())
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Parses padding value with duck-typing support:
|
|
20
|
+
/// - Integer: generates that many spaces
|
|
21
|
+
/// - Line: uses styled Line directly
|
|
22
|
+
/// - Span: wraps in Line
|
|
23
|
+
/// - String or `to_s` responder: converts to Line
|
|
24
|
+
fn parse_padding(val: Value) -> Result<Line<'static>, Error> {
|
|
25
|
+
// Handle nil or zero
|
|
26
|
+
if val.is_nil() {
|
|
27
|
+
return Ok(Line::from(""));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Try as Integer first (most common case)
|
|
31
|
+
if let Ok(n) = usize::try_convert(val) {
|
|
32
|
+
if n == 0 {
|
|
33
|
+
return Ok(Line::from(""));
|
|
34
|
+
}
|
|
35
|
+
return Ok(Line::from(" ".repeat(n)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Try to parse as Line
|
|
39
|
+
if let Ok(line) = parse_line(val) {
|
|
40
|
+
return Ok(line);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Try to parse as Span (wrap in Line)
|
|
44
|
+
if let Ok(span) = parse_span(val) {
|
|
45
|
+
return Ok(Line::from(vec![span]));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback: call to_s and convert to Line
|
|
49
|
+
let s: String = val.funcall("to_s", ())?;
|
|
50
|
+
Ok(Line::from(s))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn create_tabs(node: Value, bump: &Bump) -> Result<Tabs<'_>, Error> {
|
|
54
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
55
|
+
let titles_val: Value = node.funcall("titles", ())?;
|
|
56
|
+
let selected_index: usize = node.funcall("selected_index", ())?;
|
|
57
|
+
let block_val: Value = node.funcall("block", ())?;
|
|
58
|
+
let divider_val: Value = node.funcall("divider", ())?;
|
|
59
|
+
let highlight_style_val: Value = node.funcall("highlight_style", ())?;
|
|
60
|
+
let padding_left_val: Value = node.funcall("padding_left", ())?;
|
|
61
|
+
let padding_right_val: Value = node.funcall("padding_right", ())?;
|
|
62
|
+
|
|
63
|
+
let titles_array = magnus::RArray::from_value(titles_val)
|
|
64
|
+
.ok_or_else(|| type_error_with_context(&ruby, "expected array for titles", titles_val))?;
|
|
65
|
+
|
|
66
|
+
let mut titles = Vec::new();
|
|
67
|
+
for i in 0..titles_array.len() {
|
|
68
|
+
let index = isize::try_from(i)
|
|
69
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
70
|
+
let val: Value = titles_array.entry(index)?;
|
|
71
|
+
if let Ok(line) = parse_line(val) {
|
|
72
|
+
titles.push(line);
|
|
73
|
+
} else {
|
|
74
|
+
let s: String = String::try_convert(val)?;
|
|
75
|
+
titles.push(Line::from(s));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let mut tabs = Tabs::new(titles).select(selected_index);
|
|
80
|
+
|
|
81
|
+
if !divider_val.is_nil() {
|
|
82
|
+
if let Ok(span) = parse_span(divider_val) {
|
|
83
|
+
tabs = tabs.divider(span);
|
|
84
|
+
} else {
|
|
85
|
+
let divider: String = divider_val.funcall("to_s", ())?;
|
|
86
|
+
tabs = tabs.divider(divider);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if !highlight_style_val.is_nil() {
|
|
91
|
+
let style = crate::style::parse_style(highlight_style_val)?;
|
|
92
|
+
tabs = tabs.highlight_style(style);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let style_val: Value = node.funcall("style", ())?;
|
|
96
|
+
if !style_val.is_nil() {
|
|
97
|
+
tabs = tabs.style(crate::style::parse_style(style_val)?);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if !block_val.is_nil() {
|
|
101
|
+
tabs = tabs.block(parse_block(block_val, bump)?);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle duck-typed padding: Integer (spaces), String, Line, or anything with to_s
|
|
105
|
+
let left_padding = parse_padding(padding_left_val)?;
|
|
106
|
+
let right_padding = parse_padding(padding_right_val)?;
|
|
107
|
+
if !left_padding.spans.is_empty() || !right_padding.spans.is_empty() {
|
|
108
|
+
tabs = tabs.padding(left_padding, right_padding);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Ok(tabs)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub fn width(node: Value) -> Result<usize, Error> {
|
|
115
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
116
|
+
let titles_val: Value = node.funcall("titles", ())?;
|
|
117
|
+
let divider_val: Value = node.funcall("divider", ())?;
|
|
118
|
+
let padding_left: usize = node.funcall("padding_left", ())?;
|
|
119
|
+
let padding_right: usize = node.funcall("padding_right", ())?;
|
|
120
|
+
|
|
121
|
+
let titles_array = magnus::RArray::from_value(titles_val)
|
|
122
|
+
.ok_or_else(|| type_error_with_context(&ruby, "expected array for titles", titles_val))?;
|
|
123
|
+
|
|
124
|
+
let mut total_width = padding_left + padding_right;
|
|
125
|
+
|
|
126
|
+
let mut titles_count = 0;
|
|
127
|
+
for i in 0..titles_array.len() {
|
|
128
|
+
let index = isize::try_from(i)
|
|
129
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
130
|
+
let val: Value = titles_array.entry(index)?;
|
|
131
|
+
let line_width = if let Ok(line) = parse_line(val) {
|
|
132
|
+
line.width()
|
|
133
|
+
} else {
|
|
134
|
+
let s: String = String::try_convert(val)?;
|
|
135
|
+
ratatui::text::Span::raw(s).width()
|
|
136
|
+
};
|
|
137
|
+
total_width += line_width;
|
|
138
|
+
titles_count += 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if titles_count > 1 {
|
|
142
|
+
let divider_width = if divider_val.is_nil() {
|
|
143
|
+
1 // Default divider is "|"
|
|
144
|
+
} else if let Ok(span) = parse_span(divider_val) {
|
|
145
|
+
span.width()
|
|
146
|
+
} else {
|
|
147
|
+
let d: String = divider_val.funcall("to_s", ())?;
|
|
148
|
+
ratatui::text::Span::raw(d).width()
|
|
149
|
+
};
|
|
150
|
+
total_width += (titles_count - 1) * divider_width;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Ok(total_width)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[cfg(test)]
|
|
157
|
+
mod tests {
|
|
158
|
+
use super::*;
|
|
159
|
+
use ratatui::buffer::Buffer;
|
|
160
|
+
use ratatui::style::{Color, Modifier, Style};
|
|
161
|
+
use ratatui::text::Line;
|
|
162
|
+
use ratatui::widgets::{Tabs, Widget};
|
|
163
|
+
|
|
164
|
+
#[test]
|
|
165
|
+
fn test_tabs_rendering() {
|
|
166
|
+
let titles = vec![Line::from("Tab1"), Line::from("Tab2")];
|
|
167
|
+
let tabs = Tabs::new(titles).select(1).divider("|");
|
|
168
|
+
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
|
169
|
+
tabs.render(Rect::new(0, 0, 15, 1), &mut buf);
|
|
170
|
+
// Should contain tab titles
|
|
171
|
+
let content = buf.content().iter().map(|c| c.symbol()).collect::<String>();
|
|
172
|
+
assert!(content.contains("Tab1"));
|
|
173
|
+
assert!(content.contains("Tab2"));
|
|
174
|
+
assert!(content.contains('|'));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[test]
|
|
178
|
+
fn test_tabs_highlight_style() {
|
|
179
|
+
let titles = vec![Line::from("Tab1"), Line::from("Tab2")];
|
|
180
|
+
let highlight_style = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
|
|
181
|
+
let tabs = Tabs::new(titles).select(0).highlight_style(highlight_style);
|
|
182
|
+
|
|
183
|
+
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
|
184
|
+
tabs.render(Rect::new(0, 0, 15, 1), &mut buf);
|
|
185
|
+
|
|
186
|
+
// Check the first cell of the first tab (which is selected)
|
|
187
|
+
// " Tab1 "
|
|
188
|
+
// Index 1 should be 'T' with Red+Bold
|
|
189
|
+
let cell = &buf.content()[1];
|
|
190
|
+
assert_eq!(cell.symbol(), "T");
|
|
191
|
+
assert_eq!(cell.fg, Color::Red);
|
|
192
|
+
assert!(cell.modifier.contains(Modifier::BOLD));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
module Backend
|
|
10
|
+
# Terminal window dimensions in characters and pixels.
|
|
11
|
+
#
|
|
12
|
+
# Some operations need both character grid size and pixel dimensions.
|
|
13
|
+
# Sixel graphics, image rendering, and precise layout calculations all
|
|
14
|
+
# benefit from knowing both measurements at once.
|
|
15
|
+
#
|
|
16
|
+
# This struct bundles both sizes together. It matches upstream Ratatui's
|
|
17
|
+
# <tt>backend::WindowSize</tt> struct exactly.
|
|
18
|
+
#
|
|
19
|
+
# Both fields are <tt>Layout::Size</tt> instances. This reuses the same
|
|
20
|
+
# type for character and pixel dimensions, matching upstream design.
|
|
21
|
+
#
|
|
22
|
+
# Note: Pixel dimensions may be zero on some systems. Unix marks these
|
|
23
|
+
# fields "unused" in TIOCGWINSZ. Windows does not implement them.
|
|
24
|
+
#
|
|
25
|
+
# === Example
|
|
26
|
+
#
|
|
27
|
+
#--
|
|
28
|
+
# SPDX-SnippetBegin
|
|
29
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
30
|
+
# SPDX-License-Identifier: MIT-0
|
|
31
|
+
#++
|
|
32
|
+
# ws = RatatuiRuby::Terminal.window_size
|
|
33
|
+
# if ws
|
|
34
|
+
# puts "#{ws.columns_rows.width}x#{ws.columns_rows.height} chars"
|
|
35
|
+
# puts "#{ws.pixels.width}x#{ws.pixels.height} pixels"
|
|
36
|
+
# end
|
|
37
|
+
#--
|
|
38
|
+
# SPDX-SnippetEnd
|
|
39
|
+
#++
|
|
40
|
+
class WindowSize < Data.define(:columns_rows, :pixels)
|
|
41
|
+
##
|
|
42
|
+
# :attr_reader: columns_rows
|
|
43
|
+
# Size of the window in characters (columns/rows) as <tt>Layout::Size</tt>.
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :attr_reader: pixels
|
|
47
|
+
# Size of the window in pixels as <tt>Layout::Size</tt>.
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
# Backend abstractions for terminal rendering.
|
|
10
|
+
#
|
|
11
|
+
# This module contains types related to terminal backend operations.
|
|
12
|
+
# It mirrors upstream Ratatui's <tt>backend</tt> module structure.
|
|
13
|
+
module Backend
|
|
14
|
+
class << self
|
|
15
|
+
# Queries terminal window size in characters and pixels.
|
|
16
|
+
#
|
|
17
|
+
# Some operations need both the character grid and pixel dimensions.
|
|
18
|
+
# Querying them separately wastes syscalls. Most backends fetch both
|
|
19
|
+
# at once anyway.
|
|
20
|
+
#
|
|
21
|
+
# This method queries crossterm for window dimensions. It returns a
|
|
22
|
+
# <tt>Backend::WindowSize</tt> with <tt>columns_rows</tt> and
|
|
23
|
+
# <tt>pixels</tt> fields, each as <tt>Layout::Size</tt> instances.
|
|
24
|
+
# Returns <tt>nil</tt> if the query fails.
|
|
25
|
+
#
|
|
26
|
+
# Note: Pixel dimensions may be zero on some systems. Unix marks
|
|
27
|
+
# these fields "unused" in TIOCGWINSZ. Windows does not implement them.
|
|
28
|
+
#
|
|
29
|
+
# === Example
|
|
30
|
+
#
|
|
31
|
+
#--
|
|
32
|
+
# SPDX-SnippetBegin
|
|
33
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
34
|
+
# SPDX-License-Identifier: MIT-0
|
|
35
|
+
#++
|
|
36
|
+
# ws = RatatuiRuby::Backend.window_size
|
|
37
|
+
# if ws
|
|
38
|
+
# puts "#{ws.columns_rows.width}x#{ws.columns_rows.height} chars"
|
|
39
|
+
# puts "#{ws.pixels.width}x#{ws.pixels.height} pixels"
|
|
40
|
+
# end
|
|
41
|
+
#--
|
|
42
|
+
# SPDX-SnippetEnd
|
|
43
|
+
#++
|
|
44
|
+
def window_size
|
|
45
|
+
window_size = Terminal._terminal_window_size
|
|
46
|
+
return nil unless window_size
|
|
47
|
+
columns, rows, px_width, px_height = window_size
|
|
48
|
+
WindowSize.new(
|
|
49
|
+
columns_rows: Layout::Size.new(width: columns, height: rows),
|
|
50
|
+
pixels: Layout::Size.new(width: px_width, height: px_height)
|
|
51
|
+
)
|
|
52
|
+
rescue
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
require_relative "backend/window_size"
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
module Buffer
|
|
10
|
+
# Represents a single cell in the terminal buffer.
|
|
11
|
+
#
|
|
12
|
+
# A terminal grid is made of cells. Each cell contains a character (symbol) and styling (colors, modifiers).
|
|
13
|
+
# When testing, you often need to verify that a specific cell renders correctly.
|
|
14
|
+
#
|
|
15
|
+
# This object encapsulates that state. It provides predicate methods for modifiers, making assertions readable.
|
|
16
|
+
#
|
|
17
|
+
# Use it to inspect the visual state of your application in tests.
|
|
18
|
+
#
|
|
19
|
+
# === Examples
|
|
20
|
+
#
|
|
21
|
+
#--
|
|
22
|
+
# SPDX-SnippetBegin
|
|
23
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
24
|
+
# SPDX-License-Identifier: MIT-0
|
|
25
|
+
#++
|
|
26
|
+
# cell = RatatuiRuby.get_cell_at(0, 0)
|
|
27
|
+
# cell.char # => "H"
|
|
28
|
+
# cell.fg # => :red
|
|
29
|
+
# cell.bold? # => true
|
|
30
|
+
#
|
|
31
|
+
#--
|
|
32
|
+
# SPDX-SnippetEnd
|
|
33
|
+
#++
|
|
34
|
+
class Cell
|
|
35
|
+
# The character displayed in the cell.
|
|
36
|
+
#
|
|
37
|
+
# Named to match Ratatui's Cell::symbol() method.
|
|
38
|
+
attr_reader :symbol
|
|
39
|
+
|
|
40
|
+
# Alias for Rubyists who prefer a shorter name.
|
|
41
|
+
alias char symbol
|
|
42
|
+
|
|
43
|
+
# The foreground color of the cell (e.g., :red, :blue, "#ff0000").
|
|
44
|
+
attr_reader :fg
|
|
45
|
+
|
|
46
|
+
# The background color of the cell (e.g., :black, nil).
|
|
47
|
+
attr_reader :bg
|
|
48
|
+
|
|
49
|
+
# The underline color of the cell.
|
|
50
|
+
#
|
|
51
|
+
# Distinct from foreground color. Some terminals support colored underlines.
|
|
52
|
+
attr_reader :underline_color
|
|
53
|
+
|
|
54
|
+
# The list of active modifiers (e.g., ["bold", "italic"]).
|
|
55
|
+
attr_reader :modifiers
|
|
56
|
+
|
|
57
|
+
# Returns an empty cell (space character, no styles).
|
|
58
|
+
#
|
|
59
|
+
# === Example
|
|
60
|
+
#
|
|
61
|
+
#--
|
|
62
|
+
# SPDX-SnippetBegin
|
|
63
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
64
|
+
# SPDX-License-Identifier: MIT-0
|
|
65
|
+
#++
|
|
66
|
+
# Buffer::Cell.empty # => #<RatatuiRuby::Buffer::Cell char=" ">
|
|
67
|
+
#
|
|
68
|
+
#--
|
|
69
|
+
# SPDX-SnippetEnd
|
|
70
|
+
#++
|
|
71
|
+
def self.empty
|
|
72
|
+
new(symbol: " ", fg: nil, bg: nil, underline_color: nil, modifiers: [])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns a default cell (alias for empty).
|
|
76
|
+
#
|
|
77
|
+
# === Example
|
|
78
|
+
#
|
|
79
|
+
#--
|
|
80
|
+
# SPDX-SnippetBegin
|
|
81
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
82
|
+
# SPDX-License-Identifier: MIT-0
|
|
83
|
+
#++
|
|
84
|
+
# Buffer::Cell.default # => #<RatatuiRuby::Buffer::Cell char=" ">
|
|
85
|
+
#
|
|
86
|
+
#--
|
|
87
|
+
# SPDX-SnippetEnd
|
|
88
|
+
#++
|
|
89
|
+
def self.default
|
|
90
|
+
empty
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns a cell with a specific character and no styles.
|
|
94
|
+
#
|
|
95
|
+
# [symbol] String (single character).
|
|
96
|
+
#
|
|
97
|
+
# === Example
|
|
98
|
+
#
|
|
99
|
+
#--
|
|
100
|
+
# SPDX-SnippetBegin
|
|
101
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
102
|
+
# SPDX-License-Identifier: MIT-0
|
|
103
|
+
#++
|
|
104
|
+
# Buffer::Cell.symbol("X") # => #<RatatuiRuby::Buffer::Cell symbol="X">
|
|
105
|
+
#
|
|
106
|
+
#--
|
|
107
|
+
# SPDX-SnippetEnd
|
|
108
|
+
#++
|
|
109
|
+
def self.symbol(symbol)
|
|
110
|
+
new(symbol:, fg: nil, bg: nil, underline_color: nil, modifiers: [])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Alias for Rubyists who prefer a shorter name.
|
|
114
|
+
def self.char(char)
|
|
115
|
+
symbol(char)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Creates a new Cell.
|
|
119
|
+
#
|
|
120
|
+
# [symbol] String (single character). Aliased as <tt>char:</tt>.
|
|
121
|
+
# [fg] Symbol or String (nullable).
|
|
122
|
+
# [bg] Symbol or String (nullable).
|
|
123
|
+
# [underline_color] Symbol or String (nullable).
|
|
124
|
+
# [modifiers] Array of Strings, Symbols, or any object responding to to_sym or to_s.
|
|
125
|
+
# Normalized to Symbols for consistent output.
|
|
126
|
+
def initialize(symbol: nil, char: nil, fg: nil, bg: nil, underline_color: nil, modifiers: [])
|
|
127
|
+
@symbol = (symbol || char || " ").freeze
|
|
128
|
+
@fg = fg&.freeze
|
|
129
|
+
@bg = bg&.freeze
|
|
130
|
+
@underline_color = underline_color&.freeze
|
|
131
|
+
@modifiers = modifiers.map { |m| m.respond_to?(:to_sym) ? m.to_sym : m.to_s.to_sym }.freeze
|
|
132
|
+
freeze
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns true if the cell has the bold modifier.
|
|
136
|
+
def bold?
|
|
137
|
+
modifiers.include?(:bold)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns true if the cell has the dim modifier.
|
|
141
|
+
def dim?
|
|
142
|
+
modifiers.include?(:dim)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Returns true if the cell has the italic modifier.
|
|
146
|
+
def italic?
|
|
147
|
+
modifiers.include?(:italic)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Returns true if the cell has the underlined modifier.
|
|
151
|
+
def underlined?
|
|
152
|
+
modifiers.include?(:underlined)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns true if the cell has the slow_blink modifier.
|
|
156
|
+
def slow_blink?
|
|
157
|
+
modifiers.include?(:slow_blink)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns true if the cell has the rapid_blink modifier.
|
|
161
|
+
def rapid_blink?
|
|
162
|
+
modifiers.include?(:rapid_blink)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Returns true if the cell has the reversed modifier.
|
|
166
|
+
def reversed?
|
|
167
|
+
modifiers.include?(:reversed)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns true if the cell has the hidden modifier.
|
|
171
|
+
def hidden?
|
|
172
|
+
modifiers.include?(:hidden)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Returns true if the cell has the crossed_out modifier.
|
|
176
|
+
def crossed_out?
|
|
177
|
+
modifiers.include?(:crossed_out)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Checks equality with another Cell.
|
|
181
|
+
def ==(other)
|
|
182
|
+
other.is_a?(Cell) &&
|
|
183
|
+
char == other.char &&
|
|
184
|
+
fg == other.fg &&
|
|
185
|
+
bg == other.bg &&
|
|
186
|
+
underline_color == other.underline_color &&
|
|
187
|
+
modifiers == other.modifiers
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Returns a string representation of the cell.
|
|
191
|
+
def inspect
|
|
192
|
+
parts = ["symbol=#{symbol.inspect}"]
|
|
193
|
+
parts << "fg=#{fg.inspect}" if fg
|
|
194
|
+
parts << "bg=#{bg.inspect}" if bg
|
|
195
|
+
parts << "underline_color=#{underline_color.inspect}" if underline_color
|
|
196
|
+
parts << "modifiers=#{modifiers.inspect}" unless modifiers.empty?
|
|
197
|
+
"#<#{self.class} #{parts.join(' ')}>"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Returns the cell's character.
|
|
201
|
+
def to_s
|
|
202
|
+
symbol
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Support for pattern matching.
|
|
206
|
+
# Supports both <tt>:symbol</tt> and <tt>:char</tt> keys.
|
|
207
|
+
def deconstruct_keys(keys)
|
|
208
|
+
{ symbol:, char: symbol, fg:, bg:, underline_color:, modifiers: }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
# Buffer primitives for terminal cell inspection.
|
|
10
|
+
#
|
|
11
|
+
# Widgets render to an intermediate buffer, not directly to the terminal.
|
|
12
|
+
# Testing and debugging require access to buffer state.
|
|
13
|
+
#
|
|
14
|
+
# This module mirrors +ratatui::buffer+ and provides query methods
|
|
15
|
+
# for inspecting buffer contents, converting between coordinates and indices,
|
|
16
|
+
# and retrieving individual cells.
|
|
17
|
+
#
|
|
18
|
+
# Use it in tests to verify rendered output or in debugging to inspect state.
|
|
19
|
+
module Buffer
|
|
20
|
+
class << self
|
|
21
|
+
# Converts a position to a linear buffer index.
|
|
22
|
+
#
|
|
23
|
+
# Buffers store cells in a flat array, row by row. Widget code
|
|
24
|
+
# works with (x, y) coordinates. Bridging these representations
|
|
25
|
+
# requires index translation.
|
|
26
|
+
#
|
|
27
|
+
# The index is calculated as <tt>y * width + x</tt>.
|
|
28
|
+
#
|
|
29
|
+
# === Example
|
|
30
|
+
#
|
|
31
|
+
#--
|
|
32
|
+
# SPDX-SnippetBegin
|
|
33
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
34
|
+
# SPDX-License-Identifier: MIT-0
|
|
35
|
+
#++
|
|
36
|
+
# # In a 10-wide buffer, position (3, 2) maps to index 23
|
|
37
|
+
# Buffer.index_of(3, 2) # => 23
|
|
38
|
+
#--
|
|
39
|
+
# SPDX-SnippetEnd
|
|
40
|
+
#++
|
|
41
|
+
#
|
|
42
|
+
# [x] Column (0-indexed from left).
|
|
43
|
+
# [y] Row (0-indexed from top).
|
|
44
|
+
#
|
|
45
|
+
# Returns the linear index (Integer).
|
|
46
|
+
def index_of(x, y)
|
|
47
|
+
area = RatatuiRuby._get_terminal_area
|
|
48
|
+
(y * area["width"]) + x
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Converts a linear buffer index to position coordinates.
|
|
52
|
+
#
|
|
53
|
+
# Inverse of +index_of+. When iterating over buffer content
|
|
54
|
+
# by index, use this to recover the original coordinates.
|
|
55
|
+
#
|
|
56
|
+
# === Example
|
|
57
|
+
#
|
|
58
|
+
#--
|
|
59
|
+
# SPDX-SnippetBegin
|
|
60
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
61
|
+
# SPDX-License-Identifier: MIT-0
|
|
62
|
+
#++
|
|
63
|
+
# # In a 10-wide buffer, index 23 maps to position (3, 2)
|
|
64
|
+
# Buffer.pos_of(23) # => [3, 2]
|
|
65
|
+
#--
|
|
66
|
+
# SPDX-SnippetEnd
|
|
67
|
+
#++
|
|
68
|
+
#
|
|
69
|
+
# [index] Linear buffer index (Integer).
|
|
70
|
+
#
|
|
71
|
+
# Returns <tt>[x, y]</tt> coordinates.
|
|
72
|
+
def pos_of(index)
|
|
73
|
+
area = RatatuiRuby._get_terminal_area
|
|
74
|
+
width = area["width"]
|
|
75
|
+
x = index % width
|
|
76
|
+
y = index / width
|
|
77
|
+
[x, y]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the Cell at the specified position.
|
|
81
|
+
#
|
|
82
|
+
# Tests assert on cell contents. This method provides direct
|
|
83
|
+
# access without iterating the entire buffer.
|
|
84
|
+
#
|
|
85
|
+
# Delegates to +RatatuiRuby.get_cell_at+.
|
|
86
|
+
#
|
|
87
|
+
# === Example
|
|
88
|
+
#
|
|
89
|
+
#--
|
|
90
|
+
# SPDX-SnippetBegin
|
|
91
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
92
|
+
# SPDX-License-Identifier: MIT-0
|
|
93
|
+
#++
|
|
94
|
+
# cell = Buffer.get(0, 0)
|
|
95
|
+
# assert_equal "H", cell.char
|
|
96
|
+
#--
|
|
97
|
+
# SPDX-SnippetEnd
|
|
98
|
+
#++
|
|
99
|
+
#
|
|
100
|
+
# [x] Column (0-indexed from left).
|
|
101
|
+
# [y] Row (0-indexed from top).
|
|
102
|
+
#
|
|
103
|
+
# Returns a Buffer::Cell containing the character and style at that position.
|
|
104
|
+
def get(x, y)
|
|
105
|
+
RatatuiRuby.get_cell_at(x, y)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns all cells in the buffer as a flat array.
|
|
109
|
+
#
|
|
110
|
+
# Snapshot testing compares entire buffer states. Manually
|
|
111
|
+
# iterating coordinates is verbose and error-prone.
|
|
112
|
+
#
|
|
113
|
+
# This method returns every cell, ordered row by row
|
|
114
|
+
# (top to bottom, left to right).
|
|
115
|
+
#
|
|
116
|
+
# === Example
|
|
117
|
+
#
|
|
118
|
+
#--
|
|
119
|
+
# SPDX-SnippetBegin
|
|
120
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
121
|
+
# SPDX-License-Identifier: MIT-0
|
|
122
|
+
#++
|
|
123
|
+
# cells = Buffer.content
|
|
124
|
+
# cells.each { |cell| puts cell.char }
|
|
125
|
+
#--
|
|
126
|
+
# SPDX-SnippetEnd
|
|
127
|
+
#++
|
|
128
|
+
#
|
|
129
|
+
# Returns an Array of Buffer::Cell objects.
|
|
130
|
+
def content
|
|
131
|
+
area = RatatuiRuby._get_terminal_area
|
|
132
|
+
width = area["width"]
|
|
133
|
+
height = area["height"]
|
|
134
|
+
cells = [] #: Array[Buffer::Cell]
|
|
135
|
+
(0...height).each do |y|
|
|
136
|
+
(0...width).each do |x|
|
|
137
|
+
cells << RatatuiRuby.get_cell_at(x, y)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
cells
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Ruby-idiomatic alias (TIMTOWTDI)
|
|
144
|
+
alias [] get
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
require_relative "buffer/cell"
|