ratatui_ruby 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +10 -4
- data/CHANGELOG.md +79 -7
- data/README.md +37 -5
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +96 -22
- data/doc/application_testing.md +76 -30
- data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
- data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
- data/doc/contributors/architectural_overhaul/task.md +37 -0
- data/doc/contributors/design/ruby_frontend.md +288 -56
- data/doc/contributors/design/rust_backend.md +349 -54
- data/doc/contributors/developing_examples.md +134 -49
- data/doc/contributors/index.md +7 -5
- data/doc/contributors/v1.0.0_blockers.md +1729 -0
- data/doc/event_handling.md +11 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart_demo.png +0 -0
- data/doc/images/widget_block_demo.png +0 -0
- data/doc/images/widget_canvas_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_center_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_overlay_demo.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +11 -6
- data/doc/interactive_design.md +2 -2
- data/doc/quickstart.md +127 -165
- data/doc/terminal_limitations.md +92 -0
- data/doc/v0.7.0_migration.md +236 -0
- data/doc/why.md +93 -0
- data/examples/app_all_events/README.md +47 -27
- data/examples/app_all_events/app.rb +38 -35
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_entry.rb +17 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +9 -9
- data/examples/app_all_events/view/controls_view.rb +9 -7
- data/examples/app_all_events/view/counts_view.rb +13 -9
- data/examples/app_all_events/view/live_view.rb +9 -8
- data/examples/app_all_events/view/log_view.rb +11 -16
- data/examples/app_color_picker/README.md +84 -42
- data/examples/app_color_picker/app.rb +24 -62
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +45 -49
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/input.rb +99 -67
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +55 -26
- data/examples/app_login_form/README.md +49 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +33 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +49 -0
- data/examples/verify_quickstart_dsl/app.rb +2 -0
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +2 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +10 -4
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +8 -2
- data/examples/widget_barchart_demo/README.md +50 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +36 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_calendar_demo/app.rb +5 -1
- data/examples/widget_canvas_demo/README.md +27 -0
- data/examples/widget_canvas_demo/app.rb +123 -0
- data/examples/widget_cell_demo/README.md +36 -0
- data/examples/widget_cell_demo/app.rb +31 -24
- data/examples/widget_center_demo/README.md +29 -0
- data/examples/widget_center_demo/app.rb +116 -0
- data/examples/widget_chart_demo/README.md +41 -0
- data/examples/widget_chart_demo/app.rb +7 -2
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +91 -107
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
- data/examples/widget_overlay_demo/README.md +36 -0
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_logo_demo/app.rb +1 -1
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_render/app.rb +3 -3
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +62 -33
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +0 -1
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +4 -3
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +15 -1
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +113 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +238 -26
- data/ext/ratatui_ruby/src/frame.rs +116 -3
- data/ext/ratatui_ruby/src/lib.rs +37 -6
- data/ext/ratatui_ruby/src/rendering.rs +22 -21
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/terminal.rs +39 -15
- data/ext/ratatui_ruby/src/text.rs +13 -4
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
- data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/buffer/cell.rb +168 -0
- data/lib/ratatui_ruby/buffer.rb +15 -0
- data/lib/ratatui_ruby/cell.rb +4 -4
- data/lib/ratatui_ruby/event/key/character.rb +35 -0
- data/lib/ratatui_ruby/event/key/media.rb +44 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
- data/lib/ratatui_ruby/event/key/system.rb +45 -0
- data/lib/ratatui_ruby/event/key.rb +111 -51
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/frame.rb +100 -4
- data/lib/ratatui_ruby/layout/constraint.rb +95 -0
- data/lib/ratatui_ruby/layout/layout.rb +106 -0
- data/lib/ratatui_ruby/layout/rect.rb +118 -0
- data/lib/ratatui_ruby/layout.rb +19 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/cursor.rb +5 -0
- data/lib/ratatui_ruby/schema/gauge.rb +3 -1
- data/lib/ratatui_ruby/schema/layout.rb +1 -1
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +25 -4
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/rect.rb +43 -0
- data/lib/ratatui_ruby/schema/row.rb +66 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +29 -11
- data/lib/ratatui_ruby/schema/text.rb +96 -3
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/style/style.rb +81 -0
- data/lib/ratatui_ruby/style.rb +15 -0
- data/lib/ratatui_ruby/table_state.rb +90 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
- data/lib/ratatui_ruby/test_helper.rb +65 -358
- data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/core.rb +38 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
- data/lib/ratatui_ruby/tui.rb +75 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
- data/lib/ratatui_ruby/widgets/block.rb +192 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
- data/lib/ratatui_ruby/widgets/cell.rb +47 -0
- data/lib/ratatui_ruby/widgets/center.rb +59 -0
- data/lib/ratatui_ruby/widgets/chart.rb +185 -0
- data/lib/ratatui_ruby/widgets/clear.rb +54 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
- data/lib/ratatui_ruby/widgets/list.rb +127 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
- data/lib/ratatui_ruby/widgets/row.rb +68 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
- data/lib/ratatui_ruby/widgets/table.rb +141 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
- data/lib/ratatui_ruby/widgets.rb +40 -0
- data/lib/ratatui_ruby.rb +64 -57
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_all_events/view_state.rbs +1 -1
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
- data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
- data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +2 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/list.rbs +4 -2
- data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
- data/sig/ratatui_ruby/schema/rect.rbs +3 -0
- data/sig/ratatui_ruby/schema/row.rbs +22 -0
- data/sig/ratatui_ruby/schema/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +9 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +41 -48
- data/sig/ratatui_ruby/table_state.rbs +15 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
- data/sig/ratatui_ruby/test_helper.rbs +5 -4
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/core.rbs +14 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui.rbs +19 -0
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc.rake +7 -35
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +64 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +169 -48
- data/doc/contributors/dwim_dx.md +0 -366
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/examples/app_all_events/model/events.rb +0 -180
- data/examples/app_all_events/model/highlight.rb +0 -57
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
- data/examples/app_all_events/view_state.rb +0 -42
- data/examples/app_color_picker/scene.rb +0 -201
- data/examples/widget_block_padding/app.rb +0 -67
- data/examples/widget_block_titles/app.rb +0 -69
- data/examples/widget_list_styles/app.rb +0 -141
- data/examples/widget_table_flex/app.rb +0 -95
- data/lib/ratatui_ruby/session/autodoc.rb +0 -417
- data/lib/ratatui_ruby/session.rb +0 -163
- data/sig/examples/widget_block_padding/app.rbs +0 -11
- data/sig/examples/widget_block_titles/app.rbs +0 -11
- data/sig/examples/widget_list_styles/app.rbs +0 -11
- data/tasks/autodoc/inventory.rb +0 -61
- data/tasks/autodoc/notice.rb +0 -26
- data/tasks/autodoc/rbs.rb +0 -38
- data/tasks/autodoc/rdoc.rb +0 -45
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
use crate::style::{parse_block, parse_style};
|
|
5
|
+
use crate::text::{parse_line, parse_span};
|
|
6
|
+
use crate::widgets::table_state::RubyTableState;
|
|
5
7
|
use bumpalo::Bump;
|
|
6
|
-
use magnus::{prelude::*, Error, Symbol, Value};
|
|
8
|
+
use magnus::{prelude::*, Error, Symbol, TryConvert, Value};
|
|
7
9
|
use ratatui::{
|
|
8
10
|
layout::{Constraint, Flex, Rect},
|
|
9
11
|
widgets::{Cell, HighlightSpacing, Row, Table, TableState},
|
|
@@ -21,7 +23,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
21
23
|
let widths_val: Value = node.funcall("widths", ())?;
|
|
22
24
|
let widths_array = magnus::RArray::from_value(widths_val)
|
|
23
25
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for widths"))?;
|
|
24
|
-
let
|
|
26
|
+
let row_highlight_style_val: Value = node.funcall("row_highlight_style", ())?;
|
|
25
27
|
let column_highlight_style_val: Value = node.funcall("column_highlight_style", ())?;
|
|
26
28
|
let cell_highlight_style_val: Value = node.funcall("cell_highlight_style", ())?;
|
|
27
29
|
let highlight_symbol_val: Value = node.funcall("highlight_symbol", ())?;
|
|
@@ -72,8 +74,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
72
74
|
table = table.block(parse_block(block_val, &bump)?);
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
if !
|
|
76
|
-
table = table.row_highlight_style(parse_style(
|
|
77
|
+
if !row_highlight_style_val.is_nil() {
|
|
78
|
+
table = table.row_highlight_style(parse_style(row_highlight_style_val)?);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
if !column_highlight_style_val.is_nil() {
|
|
@@ -110,12 +112,170 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
110
112
|
state.select_column(Some(index));
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
let offset_val: Value = node.funcall("offset", ())?;
|
|
116
|
+
if !offset_val.is_nil() {
|
|
117
|
+
let offset: usize = offset_val.funcall("to_int", ())?;
|
|
118
|
+
*state.offset_mut() = offset;
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
frame.render_stateful_widget(table, area, &mut state);
|
|
114
122
|
Ok(())
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
/// Renders a Table with an external state object.
|
|
126
|
+
///
|
|
127
|
+
/// This function ignores `selected_row`, `selected_column`, and `offset` from the widget.
|
|
128
|
+
/// The State object is the single source of truth for selection and scroll position.
|
|
129
|
+
pub fn render_stateful(
|
|
130
|
+
frame: &mut Frame,
|
|
131
|
+
area: Rect,
|
|
132
|
+
node: Value,
|
|
133
|
+
state_wrapper: Value,
|
|
134
|
+
) -> Result<(), Error> {
|
|
135
|
+
let bump = Bump::new();
|
|
136
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
137
|
+
|
|
138
|
+
// Extract the RubyTableState wrapper
|
|
139
|
+
let state: &RubyTableState = TryConvert::try_convert(state_wrapper)?;
|
|
140
|
+
|
|
141
|
+
// Parse rows
|
|
142
|
+
let rows_value: Value = node.funcall("rows", ())?;
|
|
143
|
+
let rows_array = magnus::RArray::from_value(rows_value)
|
|
144
|
+
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for rows"))?;
|
|
145
|
+
let widths_val: Value = node.funcall("widths", ())?;
|
|
146
|
+
let widths_array = magnus::RArray::from_value(widths_val)
|
|
147
|
+
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for widths"))?;
|
|
148
|
+
|
|
149
|
+
let mut rows = Vec::new();
|
|
150
|
+
for i in 0..rows_array.len() {
|
|
151
|
+
let index = isize::try_from(i)
|
|
152
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
153
|
+
let row_val: Value = rows_array.entry(index)?;
|
|
154
|
+
rows.push(parse_row(row_val)?);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let constraints = parse_constraints(widths_array)?;
|
|
158
|
+
|
|
159
|
+
// Build table (ignoring selected_row, selected_column, offset — State is truth)
|
|
160
|
+
let header_val: Value = node.funcall("header", ())?;
|
|
161
|
+
let footer_val: Value = node.funcall("footer", ())?;
|
|
162
|
+
let row_highlight_style_val: Value = node.funcall("row_highlight_style", ())?;
|
|
163
|
+
let column_highlight_style_val: Value = node.funcall("column_highlight_style", ())?;
|
|
164
|
+
let cell_highlight_style_val: Value = node.funcall("cell_highlight_style", ())?;
|
|
165
|
+
let highlight_symbol_val: Value = node.funcall("highlight_symbol", ())?;
|
|
166
|
+
let block_val: Value = node.funcall("block", ())?;
|
|
167
|
+
let flex_sym: Symbol = node.funcall("flex", ())?;
|
|
168
|
+
let highlight_spacing_sym: Symbol = node.funcall("highlight_spacing", ())?;
|
|
169
|
+
let style_val: Value = node.funcall("style", ())?;
|
|
170
|
+
let column_spacing_val: Value = node.funcall("column_spacing", ())?;
|
|
171
|
+
|
|
172
|
+
let flex = match flex_sym.to_string().as_str() {
|
|
173
|
+
"start" => Flex::Start,
|
|
174
|
+
"center" => Flex::Center,
|
|
175
|
+
"end" => Flex::End,
|
|
176
|
+
"space_between" => Flex::SpaceBetween,
|
|
177
|
+
"space_around" => Flex::SpaceAround,
|
|
178
|
+
"space_evenly" => Flex::SpaceEvenly,
|
|
179
|
+
_ => Flex::Legacy,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
let mut table = Table::new(rows, constraints).flex(flex);
|
|
183
|
+
|
|
184
|
+
let highlight_spacing = match highlight_spacing_sym.to_string().as_str() {
|
|
185
|
+
"always" => HighlightSpacing::Always,
|
|
186
|
+
"never" => HighlightSpacing::Never,
|
|
187
|
+
_ => HighlightSpacing::WhenSelected,
|
|
188
|
+
};
|
|
189
|
+
table = table.highlight_spacing(highlight_spacing);
|
|
190
|
+
|
|
191
|
+
if !header_val.is_nil() {
|
|
192
|
+
table = table.header(parse_row(header_val)?);
|
|
193
|
+
}
|
|
194
|
+
if !footer_val.is_nil() {
|
|
195
|
+
table = table.footer(parse_row(footer_val)?);
|
|
196
|
+
}
|
|
197
|
+
if !block_val.is_nil() {
|
|
198
|
+
table = table.block(parse_block(block_val, &bump)?);
|
|
199
|
+
}
|
|
200
|
+
if !row_highlight_style_val.is_nil() {
|
|
201
|
+
table = table.row_highlight_style(parse_style(row_highlight_style_val)?);
|
|
202
|
+
}
|
|
203
|
+
if !column_highlight_style_val.is_nil() {
|
|
204
|
+
table = table.column_highlight_style(parse_style(column_highlight_style_val)?);
|
|
205
|
+
}
|
|
206
|
+
if !cell_highlight_style_val.is_nil() {
|
|
207
|
+
table = table.cell_highlight_style(parse_style(cell_highlight_style_val)?);
|
|
208
|
+
}
|
|
209
|
+
if !highlight_symbol_val.is_nil() {
|
|
210
|
+
let symbol: String = highlight_symbol_val.funcall("to_s", ())?;
|
|
211
|
+
table = table.highlight_symbol(symbol);
|
|
212
|
+
}
|
|
213
|
+
if !style_val.is_nil() {
|
|
214
|
+
table = table.style(parse_style(style_val)?);
|
|
215
|
+
}
|
|
216
|
+
if !column_spacing_val.is_nil() {
|
|
217
|
+
let spacing: u16 = column_spacing_val.funcall("to_int", ())?;
|
|
218
|
+
table = table.column_spacing(spacing);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Borrow the inner TableState, render, and release the borrow immediately
|
|
222
|
+
{
|
|
223
|
+
let mut inner_state = state.borrow_mut();
|
|
224
|
+
frame.render_stateful_widget(table, area, &mut inner_state);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
Ok(())
|
|
228
|
+
}
|
|
229
|
+
|
|
117
230
|
fn parse_row(row_val: Value) -> Result<Row<'static>, Error> {
|
|
118
231
|
let ruby = magnus::Ruby::get().unwrap();
|
|
232
|
+
|
|
233
|
+
// Check if this is a RatatuiRuby::Row object with cells + style + height + margins
|
|
234
|
+
let class = row_val.class();
|
|
235
|
+
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
236
|
+
let class_name = unsafe { class.name() }.into_owned();
|
|
237
|
+
|
|
238
|
+
if class_name == "RatatuiRuby::Widgets::Row" {
|
|
239
|
+
let cells_val: Value = row_val.funcall("cells", ())?;
|
|
240
|
+
let style_val: Value = row_val.funcall("style", ())?;
|
|
241
|
+
let height_val: Value = row_val.funcall("height", ())?;
|
|
242
|
+
let top_margin_val: Value = row_val.funcall("top_margin", ())?;
|
|
243
|
+
let bottom_margin_val: Value = row_val.funcall("bottom_margin", ())?;
|
|
244
|
+
|
|
245
|
+
let cells_array = magnus::RArray::from_value(cells_val).ok_or_else(|| {
|
|
246
|
+
Error::new(ruby.exception_type_error(), "expected array for Row.cells")
|
|
247
|
+
})?;
|
|
248
|
+
|
|
249
|
+
let mut cells = Vec::new();
|
|
250
|
+
for i in 0..cells_array.len() {
|
|
251
|
+
let index = isize::try_from(i)
|
|
252
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
253
|
+
let entry_val: Value = cells_array.entry(index)?;
|
|
254
|
+
cells.push(parse_cell(entry_val)?);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let mut row = Row::new(cells);
|
|
258
|
+
|
|
259
|
+
if !style_val.is_nil() {
|
|
260
|
+
row = row.style(parse_style(style_val)?);
|
|
261
|
+
}
|
|
262
|
+
if !height_val.is_nil() {
|
|
263
|
+
let h: u16 = height_val.funcall("to_int", ())?;
|
|
264
|
+
row = row.height(h);
|
|
265
|
+
}
|
|
266
|
+
if !top_margin_val.is_nil() {
|
|
267
|
+
let m: u16 = top_margin_val.funcall("to_int", ())?;
|
|
268
|
+
row = row.top_margin(m);
|
|
269
|
+
}
|
|
270
|
+
if !bottom_margin_val.is_nil() {
|
|
271
|
+
let m: u16 = bottom_margin_val.funcall("to_int", ())?;
|
|
272
|
+
row = row.bottom_margin(m);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return Ok(row);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Fallback: plain array of cells
|
|
119
279
|
let row_array = magnus::RArray::from_value(row_val)
|
|
120
280
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for row"))?;
|
|
121
281
|
|
|
@@ -134,42 +294,39 @@ fn parse_cell(cell_val: Value) -> Result<Cell<'static>, Error> {
|
|
|
134
294
|
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
135
295
|
let class_name = unsafe { class.name() }.into_owned();
|
|
136
296
|
|
|
137
|
-
|
|
297
|
+
// Try Text::Line first (contains multiple spans)
|
|
298
|
+
if class_name.contains("Line") {
|
|
299
|
+
if let Ok(line) = parse_line(cell_val) {
|
|
300
|
+
return Ok(Cell::from(line));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Try Text::Span
|
|
305
|
+
if class_name.contains("Span") {
|
|
306
|
+
if let Ok(span) = parse_span(cell_val) {
|
|
307
|
+
return Ok(Cell::from(ratatui::text::Line::from(vec![span])));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if class_name == "RatatuiRuby::Widgets::Paragraph" {
|
|
138
312
|
let text: String = cell_val.funcall("text", ())?;
|
|
139
313
|
let style_val: Value = cell_val.funcall("style", ())?;
|
|
140
314
|
let cell_style = parse_style(style_val)?;
|
|
141
315
|
Ok(Cell::from(text).style(cell_style))
|
|
142
|
-
} else if class_name == "RatatuiRuby::Style" {
|
|
316
|
+
} else if class_name == "RatatuiRuby::Style::Style" {
|
|
143
317
|
Ok(Cell::from("").style(parse_style(cell_val)?))
|
|
144
|
-
} else if class_name == "RatatuiRuby::Cell" {
|
|
145
|
-
|
|
146
|
-
let
|
|
147
|
-
let
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
let mut
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if !bg_val.is_nil() {
|
|
157
|
-
if let Some(color) = crate::style::parse_color_value(bg_val)? {
|
|
158
|
-
style = style.bg(color);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if let Some(mods_array) = magnus::RArray::from_value(modifiers_val) {
|
|
162
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
163
|
-
for i in 0..mods_array.len() {
|
|
164
|
-
let index = isize::try_from(i)
|
|
165
|
-
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
166
|
-
let mod_str: String = mods_array.entry::<String>(index)?;
|
|
167
|
-
if let Some(modifier) = crate::style::parse_modifier_str(&mod_str) {
|
|
168
|
-
style = style.add_modifier(modifier);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
318
|
+
} else if class_name == "RatatuiRuby::Widgets::Cell" {
|
|
319
|
+
// Widgets::Cell has content (String/Span/Line) and optional style
|
|
320
|
+
let content_val: Value = cell_val.funcall("content", ())?;
|
|
321
|
+
let style_val: Value = cell_val.funcall("style", ())?;
|
|
322
|
+
|
|
323
|
+
// Recursively parse the content (could be String, Span, or Line)
|
|
324
|
+
let mut cell = parse_cell(content_val)?;
|
|
325
|
+
|
|
326
|
+
if !style_val.is_nil() {
|
|
327
|
+
cell = cell.style(parse_style(style_val)?);
|
|
171
328
|
}
|
|
172
|
-
Ok(
|
|
329
|
+
Ok(cell)
|
|
173
330
|
} else {
|
|
174
331
|
let cell_str: String = cell_val.funcall("to_s", ())?;
|
|
175
332
|
Ok(Cell::from(cell_str))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
//! `TableState` wrapper for exposing Ratatui's `TableState` to Ruby.
|
|
5
|
+
//!
|
|
6
|
+
//! This module provides `RubyTableState`, a Magnus-wrapped struct that holds
|
|
7
|
+
//! a `RefCell<TableState>` for interior mutability during stateful rendering.
|
|
8
|
+
//!
|
|
9
|
+
//! # Design
|
|
10
|
+
//!
|
|
11
|
+
//! When using `render_stateful_widget`, the State object is the single source
|
|
12
|
+
//! of truth for selection and offset. Widget properties (`selected_row`,
|
|
13
|
+
//! `selected_column`, `offset`) are ignored in stateful mode.
|
|
14
|
+
|
|
15
|
+
use magnus::{function, method, prelude::*, Error, Module, Ruby};
|
|
16
|
+
use ratatui::widgets::TableState;
|
|
17
|
+
use std::cell::RefCell;
|
|
18
|
+
|
|
19
|
+
/// A wrapper around Ratatui's `TableState` exposed to Ruby.
|
|
20
|
+
#[magnus::wrap(class = "RatatuiRuby::TableState")]
|
|
21
|
+
pub struct RubyTableState {
|
|
22
|
+
inner: RefCell<TableState>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl RubyTableState {
|
|
26
|
+
/// Creates a new `RubyTableState` with optional initial selection.
|
|
27
|
+
pub fn new(selected: Option<usize>) -> Self {
|
|
28
|
+
let mut state = TableState::default();
|
|
29
|
+
if let Some(idx) = selected {
|
|
30
|
+
state.select(Some(idx));
|
|
31
|
+
}
|
|
32
|
+
Self {
|
|
33
|
+
inner: RefCell::new(state),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Sets the selected row index.
|
|
38
|
+
pub fn select(&self, index: Option<usize>) {
|
|
39
|
+
self.inner.borrow_mut().select(index);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Returns the currently selected row index.
|
|
43
|
+
pub fn selected(&self) -> Option<usize> {
|
|
44
|
+
self.inner.borrow().selected()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Sets the selected column index.
|
|
48
|
+
pub fn select_column(&self, index: Option<usize>) {
|
|
49
|
+
self.inner.borrow_mut().select_column(index);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Returns the currently selected column index.
|
|
53
|
+
pub fn selected_column(&self) -> Option<usize> {
|
|
54
|
+
self.inner.borrow().selected_column()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Returns the current scroll offset.
|
|
58
|
+
pub fn offset(&self) -> usize {
|
|
59
|
+
self.inner.borrow().offset()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Scrolls down by the given number of rows.
|
|
63
|
+
pub fn scroll_down_by(&self, amount: u16) {
|
|
64
|
+
self.inner.borrow_mut().scroll_down_by(amount);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Scrolls up by the given number of rows.
|
|
68
|
+
pub fn scroll_up_by(&self, amount: u16) {
|
|
69
|
+
self.inner.borrow_mut().scroll_up_by(amount);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Borrows the inner `TableState` mutably for rendering.
|
|
73
|
+
pub fn borrow_mut(&self) -> std::cell::RefMut<'_, TableState> {
|
|
74
|
+
self.inner.borrow_mut()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Registers the `TableState` class with Ruby.
|
|
79
|
+
pub fn register(ruby: &Ruby, module: magnus::RModule) -> Result<(), Error> {
|
|
80
|
+
let class = module.define_class("TableState", ruby.class_object())?;
|
|
81
|
+
class.define_singleton_method("new", function!(RubyTableState::new, 1))?;
|
|
82
|
+
class.define_method("select", method!(RubyTableState::select, 1))?;
|
|
83
|
+
class.define_method("selected", method!(RubyTableState::selected, 0))?;
|
|
84
|
+
class.define_method("select_column", method!(RubyTableState::select_column, 1))?;
|
|
85
|
+
class.define_method(
|
|
86
|
+
"selected_column",
|
|
87
|
+
method!(RubyTableState::selected_column, 0),
|
|
88
|
+
)?;
|
|
89
|
+
class.define_method("offset", method!(RubyTableState::offset, 0))?;
|
|
90
|
+
class.define_method("scroll_down_by", method!(RubyTableState::scroll_down_by, 1))?;
|
|
91
|
+
class.define_method("scroll_up_by", method!(RubyTableState::scroll_up_by, 1))?;
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[cfg(test)]
|
|
96
|
+
mod tests {
|
|
97
|
+
use super::*;
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
fn test_new_with_no_selection() {
|
|
101
|
+
let state = RubyTableState::new(None);
|
|
102
|
+
assert_eq!(state.selected(), None);
|
|
103
|
+
assert_eq!(state.selected_column(), None);
|
|
104
|
+
assert_eq!(state.offset(), 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#[test]
|
|
108
|
+
fn test_new_with_selection() {
|
|
109
|
+
let state = RubyTableState::new(Some(3));
|
|
110
|
+
assert_eq!(state.selected(), Some(3));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#[test]
|
|
114
|
+
fn test_column_selection() {
|
|
115
|
+
let state = RubyTableState::new(None);
|
|
116
|
+
state.select_column(Some(2));
|
|
117
|
+
assert_eq!(state.selected_column(), Some(2));
|
|
118
|
+
state.select_column(None);
|
|
119
|
+
assert_eq!(state.selected_column(), None);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
module Buffer
|
|
8
|
+
# Represents a single cell in the terminal buffer.
|
|
9
|
+
#
|
|
10
|
+
# A terminal grid is made of cells. Each cell contains a character (symbol) and styling (colors, modifiers).
|
|
11
|
+
# When testing, you often need to verify that a specific cell renders correctly.
|
|
12
|
+
#
|
|
13
|
+
# This object encapsulates that state. It provides predicate methods for modifiers, making assertions readable.
|
|
14
|
+
#
|
|
15
|
+
# Use it to inspect the visual state of your application in tests.
|
|
16
|
+
#
|
|
17
|
+
# === Examples
|
|
18
|
+
#
|
|
19
|
+
# cell = RatatuiRuby.get_cell_at(0, 0)
|
|
20
|
+
# cell.char # => "H"
|
|
21
|
+
# cell.fg # => :red
|
|
22
|
+
# cell.bold? # => true
|
|
23
|
+
#
|
|
24
|
+
class Cell
|
|
25
|
+
# The character displayed in the cell.
|
|
26
|
+
#
|
|
27
|
+
# Named to match Ratatui's Cell::symbol() method.
|
|
28
|
+
attr_reader :symbol
|
|
29
|
+
|
|
30
|
+
# Alias for Rubyists who prefer a shorter name.
|
|
31
|
+
alias char symbol
|
|
32
|
+
|
|
33
|
+
# The foreground color of the cell (e.g., :red, :blue, "#ff0000").
|
|
34
|
+
attr_reader :fg
|
|
35
|
+
|
|
36
|
+
# The background color of the cell (e.g., :black, nil).
|
|
37
|
+
attr_reader :bg
|
|
38
|
+
|
|
39
|
+
# The list of active modifiers (e.g., ["bold", "italic"]).
|
|
40
|
+
attr_reader :modifiers
|
|
41
|
+
|
|
42
|
+
# Returns an empty cell (space character, no styles).
|
|
43
|
+
#
|
|
44
|
+
# === Example
|
|
45
|
+
#
|
|
46
|
+
# Buffer::Cell.empty # => #<RatatuiRuby::Buffer::Cell char=" ">
|
|
47
|
+
#
|
|
48
|
+
def self.empty
|
|
49
|
+
new(symbol: " ", fg: nil, bg: nil, modifiers: [])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns a default cell (alias for empty).
|
|
53
|
+
#
|
|
54
|
+
# === Example
|
|
55
|
+
#
|
|
56
|
+
# Buffer::Cell.default # => #<RatatuiRuby::Buffer::Cell char=" ">
|
|
57
|
+
#
|
|
58
|
+
def self.default
|
|
59
|
+
empty
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns a cell with a specific character and no styles.
|
|
63
|
+
#
|
|
64
|
+
# [symbol] String (single character).
|
|
65
|
+
#
|
|
66
|
+
# === Example
|
|
67
|
+
#
|
|
68
|
+
# Buffer::Cell.symbol("X") # => #<RatatuiRuby::Buffer::Cell symbol="X">
|
|
69
|
+
#
|
|
70
|
+
def self.symbol(symbol)
|
|
71
|
+
new(symbol:, fg: nil, bg: nil, modifiers: [])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Alias for Rubyists who prefer a shorter name.
|
|
75
|
+
def self.char(char)
|
|
76
|
+
symbol(char)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Creates a new Cell.
|
|
80
|
+
#
|
|
81
|
+
# [symbol] String (single character). Aliased as <tt>char:</tt>.
|
|
82
|
+
# [fg] Symbol or String (nullable).
|
|
83
|
+
# [bg] Symbol or String (nullable).
|
|
84
|
+
# [modifiers] Array of Strings.
|
|
85
|
+
def initialize(symbol: nil, char: nil, fg: nil, bg: nil, modifiers: [])
|
|
86
|
+
@symbol = (symbol || char || " ").freeze
|
|
87
|
+
@fg = fg&.freeze
|
|
88
|
+
@bg = bg&.freeze
|
|
89
|
+
@modifiers = modifiers.map(&:freeze).freeze
|
|
90
|
+
freeze
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns true if the cell has the bold modifier.
|
|
94
|
+
def bold?
|
|
95
|
+
modifiers.include?("bold")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns true if the cell has the dim modifier.
|
|
99
|
+
def dim?
|
|
100
|
+
modifiers.include?("dim")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns true if the cell has the italic modifier.
|
|
104
|
+
def italic?
|
|
105
|
+
modifiers.include?("italic")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns true if the cell has the underlined modifier.
|
|
109
|
+
def underlined?
|
|
110
|
+
modifiers.include?("underlined")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns true if the cell has the slow_blink modifier.
|
|
114
|
+
def slow_blink?
|
|
115
|
+
modifiers.include?("slow_blink")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns true if the cell has the rapid_blink modifier.
|
|
119
|
+
def rapid_blink?
|
|
120
|
+
modifiers.include?("rapid_blink")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns true if the cell has the reversed modifier.
|
|
124
|
+
def reversed?
|
|
125
|
+
modifiers.include?("reversed")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns true if the cell has the hidden modifier.
|
|
129
|
+
def hidden?
|
|
130
|
+
modifiers.include?("hidden")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns true if the cell has the crossed_out modifier.
|
|
134
|
+
def crossed_out?
|
|
135
|
+
modifiers.include?("crossed_out")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Checks equality with another Cell.
|
|
139
|
+
def ==(other)
|
|
140
|
+
other.is_a?(Cell) &&
|
|
141
|
+
char == other.char &&
|
|
142
|
+
fg == other.fg &&
|
|
143
|
+
bg == other.bg &&
|
|
144
|
+
modifiers == other.modifiers
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Returns a string representation of the cell.
|
|
148
|
+
def inspect
|
|
149
|
+
parts = ["symbol=#{symbol.inspect}"]
|
|
150
|
+
parts << "fg=#{fg.inspect}" if fg
|
|
151
|
+
parts << "bg=#{bg.inspect}" if bg
|
|
152
|
+
parts << "modifiers=#{modifiers.inspect}" unless modifiers.empty?
|
|
153
|
+
"#<#{self.class} #{parts.join(' ')}>"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Returns the cell's character.
|
|
157
|
+
def to_s
|
|
158
|
+
symbol
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Support for pattern matching.
|
|
162
|
+
# Supports both <tt>:symbol</tt> and <tt>:char</tt> keys.
|
|
163
|
+
def deconstruct_keys(keys)
|
|
164
|
+
{ symbol:, char: symbol, fg:, bg:, modifiers: }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
# Buffer primitives for terminal cell inspection.
|
|
8
|
+
#
|
|
9
|
+
# This module mirrors +ratatui::buffer+ and contains:
|
|
10
|
+
# - {Cell} — Single terminal cell (for inspection)
|
|
11
|
+
module Buffer
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require_relative "buffer/cell"
|
data/lib/ratatui_ruby/cell.rb
CHANGED
|
@@ -82,10 +82,10 @@ module RatatuiRuby
|
|
|
82
82
|
# [bg] Symbol or String (nullable).
|
|
83
83
|
# [modifiers] Array of Strings.
|
|
84
84
|
def initialize(symbol: nil, char: nil, fg: nil, bg: nil, modifiers: [])
|
|
85
|
-
@symbol = symbol || char || " "
|
|
86
|
-
@fg = fg
|
|
87
|
-
@bg = bg
|
|
88
|
-
@modifiers = modifiers.freeze
|
|
85
|
+
@symbol = (symbol || char || " ").freeze
|
|
86
|
+
@fg = fg&.freeze
|
|
87
|
+
@bg = bg&.freeze
|
|
88
|
+
@modifiers = modifiers.map(&:freeze).freeze
|
|
89
89
|
freeze
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
class Event
|
|
8
|
+
class Key < Event
|
|
9
|
+
# Methods for handling printable characters.
|
|
10
|
+
module Character
|
|
11
|
+
# Returns true if the key represents a single printable character.
|
|
12
|
+
#
|
|
13
|
+
# RatatuiRuby::Event::Key.new(code: "a").text? # => true
|
|
14
|
+
# RatatuiRuby::Event::Key.new(code: "enter").text? # => false
|
|
15
|
+
# RatatuiRuby::Event::Key.new(code: "space").text? # => false ("space" is not 1 char, " " is)
|
|
16
|
+
def text?
|
|
17
|
+
@code.length == 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns the key as a printable character (if applicable).
|
|
21
|
+
#
|
|
22
|
+
# [Printable Characters]
|
|
23
|
+
# Returns the character itself (e.g., <tt>"a"</tt>, <tt>"1"</tt>, <tt>" "</tt>).
|
|
24
|
+
# [Special Keys]
|
|
25
|
+
# Returns <tt>nil</tt> (e.g., <tt>"enter"</tt>, <tt>"up"</tt>, <tt>"f1"</tt>).
|
|
26
|
+
#
|
|
27
|
+
# RatatuiRuby::Event::Key.new(code: "a").char # => "a"
|
|
28
|
+
# RatatuiRuby::Event::Key.new(code: "enter").char # => nil
|
|
29
|
+
def char
|
|
30
|
+
text? ? @code : nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
class Event
|
|
8
|
+
class Key < Event
|
|
9
|
+
# Methods and logic for media keys.
|
|
10
|
+
module Media
|
|
11
|
+
# Returns true if this is a media key.
|
|
12
|
+
#
|
|
13
|
+
# Media keys include: play, pause, stop, track controls, volume controls.
|
|
14
|
+
# These are only available in terminals supporting the Kitty keyboard protocol.
|
|
15
|
+
#
|
|
16
|
+
# event.media? # => true for media_play, media_pause, etc.
|
|
17
|
+
def media?
|
|
18
|
+
@kind == :media
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Handles media-specific DWIM logic for method_missing.
|
|
22
|
+
private def match_media_dwim?(key_name)
|
|
23
|
+
return false unless @kind == :media
|
|
24
|
+
|
|
25
|
+
# Allow unprefixed predicate
|
|
26
|
+
# e.g., pause? returns true for media_pause
|
|
27
|
+
if @code.start_with?("media_")
|
|
28
|
+
base_code = @code.delete_prefix("media_")
|
|
29
|
+
return true if key_name == base_code
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Bidirectional media overlaps
|
|
33
|
+
# e.g., play? and pause? both match media_play_pause
|
|
34
|
+
return true if @code == "media_play_pause" && (key_name == "play" || key_name == "pause")
|
|
35
|
+
|
|
36
|
+
# e.g., play_pause? matches media_play or media_pause
|
|
37
|
+
return true if key_name == "play_pause" && (@code == "media_play" || @code == "media_pause")
|
|
38
|
+
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|