ratatui_ruby 1.0.0.pre.beta.2 → 1.0.0.pre.beta.3
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/CHANGELOG.md +20 -0
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/events.rs +10 -14
- data/ext/ratatui_ruby/src/lib.rs +61 -50
- data/ext/ratatui_ruby/src/terminal/init.rs +141 -0
- data/ext/ratatui_ruby/src/terminal/mod.rs +33 -0
- data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
- data/ext/ratatui_ruby/src/terminal/queries.rs +216 -0
- data/ext/ratatui_ruby/src/terminal/query.rs +338 -0
- data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
- data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +8 -6
- data/ext/ratatui_ruby/src/lib.rs.bak +0 -286
- data/ext/ratatui_ruby/src/rendering.rs.bak +0 -152
- data/ext/ratatui_ruby/src/terminal.rs +0 -491
- data/ext/ratatui_ruby/src/terminal.rs.bak +0 -381
- data/ext/ratatui_ruby/src/terminal.rs.orig +0 -409
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ratatui_ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0.pre.beta.
|
|
4
|
+
version: 1.0.0.pre.beta.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kerrick Long
|
|
@@ -604,14 +604,16 @@ files:
|
|
|
604
604
|
- ext/ratatui_ruby/src/events.rs
|
|
605
605
|
- ext/ratatui_ruby/src/frame.rs
|
|
606
606
|
- ext/ratatui_ruby/src/lib.rs
|
|
607
|
-
- ext/ratatui_ruby/src/lib.rs.bak
|
|
608
607
|
- ext/ratatui_ruby/src/rendering.rs
|
|
609
|
-
- ext/ratatui_ruby/src/rendering.rs.bak
|
|
610
608
|
- ext/ratatui_ruby/src/string_width.rs
|
|
611
609
|
- ext/ratatui_ruby/src/style.rs
|
|
612
|
-
- ext/ratatui_ruby/src/terminal.rs
|
|
613
|
-
- ext/ratatui_ruby/src/terminal.rs
|
|
614
|
-
- ext/ratatui_ruby/src/terminal.rs
|
|
610
|
+
- ext/ratatui_ruby/src/terminal/init.rs
|
|
611
|
+
- ext/ratatui_ruby/src/terminal/mod.rs
|
|
612
|
+
- ext/ratatui_ruby/src/terminal/mutations.rs
|
|
613
|
+
- ext/ratatui_ruby/src/terminal/queries.rs
|
|
614
|
+
- ext/ratatui_ruby/src/terminal/query.rs
|
|
615
|
+
- ext/ratatui_ruby/src/terminal/storage.rs
|
|
616
|
+
- ext/ratatui_ruby/src/terminal/wrapper.rs
|
|
615
617
|
- ext/ratatui_ruby/src/text.rs
|
|
616
618
|
- ext/ratatui_ruby/src/widgets/barchart.rs
|
|
617
619
|
- ext/ratatui_ruby/src/widgets/block.rs
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
-
|
|
4
|
-
// Require SAFETY comments on all unsafe blocks
|
|
5
|
-
#![warn(clippy::undocumented_unsafe_blocks)]
|
|
6
|
-
// Enable pedantic lints for stricter code quality
|
|
7
|
-
#![warn(clippy::pedantic)]
|
|
8
|
-
// Allow certain pedantic lints that are too noisy for FFI code
|
|
9
|
-
#![allow(clippy::missing_errors_doc)]
|
|
10
|
-
#![allow(clippy::missing_panics_doc)]
|
|
11
|
-
#![allow(clippy::module_name_repetitions)]
|
|
12
|
-
|
|
13
|
-
mod color;
|
|
14
|
-
mod errors;
|
|
15
|
-
mod events;
|
|
16
|
-
mod frame;
|
|
17
|
-
mod rendering;
|
|
18
|
-
mod string_width;
|
|
19
|
-
mod style;
|
|
20
|
-
mod terminal;
|
|
21
|
-
mod text;
|
|
22
|
-
mod widgets;
|
|
23
|
-
|
|
24
|
-
use frame::RubyFrame;
|
|
25
|
-
use magnus::{function, method, Error, Module, Object, Ruby, Value};
|
|
26
|
-
use terminal::{init_terminal, restore_terminal, TERMINAL};
|
|
27
|
-
|
|
28
|
-
/// Draw to the terminal.
|
|
29
|
-
///
|
|
30
|
-
/// Supports two calling conventions:
|
|
31
|
-
/// - Legacy: `RatatuiRuby.draw(tree)` - Renders a widget tree to the full terminal area
|
|
32
|
-
/// - New: `RatatuiRuby.draw { |frame| ... }` - Yields a Frame for explicit widget placement
|
|
33
|
-
fn draw(args: &[Value]) -> Result<(), Error> {
|
|
34
|
-
let ruby = Ruby::get().unwrap();
|
|
35
|
-
|
|
36
|
-
// Parse arguments: check for optional tree argument
|
|
37
|
-
let tree: Option<Value> = if args.is_empty() {
|
|
38
|
-
None
|
|
39
|
-
} else if args.len() == 1 {
|
|
40
|
-
Some(args[0])
|
|
41
|
-
} else {
|
|
42
|
-
return Err(Error::new(
|
|
43
|
-
ruby.exception_arg_error(),
|
|
44
|
-
format!(
|
|
45
|
-
"wrong number of arguments (given {}, expected 0..1)",
|
|
46
|
-
args.len()
|
|
47
|
-
),
|
|
48
|
-
));
|
|
49
|
-
};
|
|
50
|
-
let block_given = ruby.block_given();
|
|
51
|
-
|
|
52
|
-
// Validate: must have either tree or block, but not both
|
|
53
|
-
if tree.is_some() && block_given {
|
|
54
|
-
return Err(Error::new(
|
|
55
|
-
ruby.exception_arg_error(),
|
|
56
|
-
"Cannot provide both a tree and a block to draw",
|
|
57
|
-
));
|
|
58
|
-
}
|
|
59
|
-
if tree.is_none() && !block_given {
|
|
60
|
-
return Err(Error::new(
|
|
61
|
-
ruby.exception_arg_error(),
|
|
62
|
-
"Must provide either a tree or a block to draw",
|
|
63
|
-
));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
67
|
-
let mut render_error: Option<Error> = None;
|
|
68
|
-
|
|
69
|
-
// Helper closure to execute the draw callback logic for either terminal type
|
|
70
|
-
let mut draw_callback = |f: &mut ratatui::Frame<'_>| {
|
|
71
|
-
if block_given {
|
|
72
|
-
// New API: yield RubyFrame to block
|
|
73
|
-
// Create validity flag — set to true while the block is executing
|
|
74
|
-
let active = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
|
|
75
|
-
|
|
76
|
-
let ruby_frame = RubyFrame::new(f, active.clone());
|
|
77
|
-
if let Err(e) = ruby.yield_value::<_, Value>(ruby_frame) {
|
|
78
|
-
render_error = Some(e);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Invalidate frame immediately after block returns
|
|
82
|
-
// This prevents use-after-free if user stored the frame object
|
|
83
|
-
active.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
84
|
-
} else if let Some(tree_value) = tree {
|
|
85
|
-
// Legacy API: render tree to full area
|
|
86
|
-
if let Err(e) = rendering::render_node(f, f.area(), tree_value) {
|
|
87
|
-
render_error = Some(e);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
if let Some(wrapper) = term_lock.as_mut() {
|
|
93
|
-
match wrapper {
|
|
94
|
-
terminal::TerminalWrapper::Crossterm(term) => {
|
|
95
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
96
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
97
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
98
|
-
term.draw(&mut draw_callback)
|
|
99
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
100
|
-
}
|
|
101
|
-
terminal::TerminalWrapper::Test(term) => {
|
|
102
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
103
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
104
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
105
|
-
term.draw(&mut draw_callback)
|
|
106
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
eprintln!("Terminal is None!");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if let Some(e) = render_error {
|
|
114
|
-
return Err(e);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
Ok(())
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/// Storage for the last panic info, to be retrieved and printed after terminal restore.
|
|
121
|
-
static LAST_PANIC: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
|
|
122
|
-
|
|
123
|
-
/// Enables Rust backtraces and installs a custom panic hook.
|
|
124
|
-
///
|
|
125
|
-
/// The panic hook stores the backtrace info instead of printing immediately.
|
|
126
|
-
/// This allows Ruby to retrieve and print it after terminal restoration,
|
|
127
|
-
/// preventing output from being lost on the alternate screen.
|
|
128
|
-
fn enable_rust_backtrace(_ruby: &magnus::Ruby) {
|
|
129
|
-
std::env::set_var("RUST_BACKTRACE", "1");
|
|
130
|
-
std::panic::set_hook(Box::new(|info| {
|
|
131
|
-
let backtrace = std::backtrace::Backtrace::force_capture();
|
|
132
|
-
let message = format!("Rust panic: {info}\n{backtrace}");
|
|
133
|
-
if let Ok(mut guard) = LAST_PANIC.lock() {
|
|
134
|
-
*guard = Some(message);
|
|
135
|
-
}
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/// Returns the last panic info (if any) and clears it.
|
|
140
|
-
///
|
|
141
|
-
/// Call this after terminal restoration to get deferred panic output.
|
|
142
|
-
fn get_last_panic(_ruby: &magnus::Ruby) -> Option<String> {
|
|
143
|
-
if let Ok(mut guard) = LAST_PANIC.lock() {
|
|
144
|
-
guard.take()
|
|
145
|
-
} else {
|
|
146
|
-
None
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/// Intentionally panics to test backtrace output.
|
|
151
|
-
///
|
|
152
|
-
/// Only use this for debugging/testing the backtrace feature.
|
|
153
|
-
fn test_panic(_ruby: &magnus::Ruby) {
|
|
154
|
-
panic!("Test panic triggered by RatatuiRuby._test_panic");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
#[magnus::init]
|
|
158
|
-
fn init() -> Result<(), Error> {
|
|
159
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
160
|
-
let m = ruby.define_module("RatatuiRuby")?;
|
|
161
|
-
|
|
162
|
-
m.define_module_function("_init_terminal", function!(init_terminal, 4))?;
|
|
163
|
-
m.define_module_function("_restore_terminal", function!(restore_terminal, 0))?;
|
|
164
|
-
m.define_module_function("_draw", function!(draw, -1))?;
|
|
165
|
-
m.define_module_function(
|
|
166
|
-
"_enable_rust_backtrace",
|
|
167
|
-
function!(enable_rust_backtrace, 0),
|
|
168
|
-
)?;
|
|
169
|
-
m.define_module_function("_test_panic", function!(test_panic, 0))?;
|
|
170
|
-
m.define_module_function("_get_last_panic", function!(get_last_panic, 0))?;
|
|
171
|
-
|
|
172
|
-
// Register Frame class
|
|
173
|
-
let frame_class = m.define_class("Frame", ruby.class_object())?;
|
|
174
|
-
frame_class.define_method("area", method!(RubyFrame::area, 0))?;
|
|
175
|
-
frame_class.define_method("render_widget", method!(RubyFrame::render_widget, 2))?;
|
|
176
|
-
frame_class.define_method(
|
|
177
|
-
"render_stateful_widget",
|
|
178
|
-
method!(RubyFrame::render_stateful_widget, 3),
|
|
179
|
-
)?;
|
|
180
|
-
frame_class.define_method(
|
|
181
|
-
"set_cursor_position",
|
|
182
|
-
method!(RubyFrame::set_cursor_position, 2),
|
|
183
|
-
)?;
|
|
184
|
-
m.define_module_function("_poll_event", function!(events::poll_event, 1))?;
|
|
185
|
-
m.define_module_function("inject_test_event", function!(events::inject_test_event, 2))?;
|
|
186
|
-
m.define_module_function("clear_events", function!(events::clear_events, 0))?;
|
|
187
|
-
|
|
188
|
-
// Register State classes
|
|
189
|
-
widgets::list_state::register(&ruby, m)?;
|
|
190
|
-
widgets::table_state::register(&ruby, m)?;
|
|
191
|
-
widgets::scrollbar_state::register(&ruby, m)?;
|
|
192
|
-
|
|
193
|
-
// Test backend helpers
|
|
194
|
-
m.define_module_function(
|
|
195
|
-
"_init_test_terminal",
|
|
196
|
-
function!(terminal::init_test_terminal, 4),
|
|
197
|
-
)?;
|
|
198
|
-
m.define_module_function(
|
|
199
|
-
"get_buffer_content",
|
|
200
|
-
function!(terminal::get_buffer_content, 0),
|
|
201
|
-
)?;
|
|
202
|
-
m.define_module_function(
|
|
203
|
-
"get_cursor_position",
|
|
204
|
-
function!(terminal::get_cursor_position, 0),
|
|
205
|
-
)?;
|
|
206
|
-
m.define_module_function("_get_cell_at", function!(terminal::get_cell_at, 2))?;
|
|
207
|
-
m.define_module_function("resize_terminal", function!(terminal::resize_terminal, 2))?;
|
|
208
|
-
m.define_module_function(
|
|
209
|
-
"_get_terminal_area",
|
|
210
|
-
function!(terminal::get_terminal_area, 0),
|
|
211
|
-
)?;
|
|
212
|
-
m.define_module_function(
|
|
213
|
-
"_insert_before",
|
|
214
|
-
function!(terminal::insert_before, 2),
|
|
215
|
-
)?;
|
|
216
|
-
|
|
217
|
-
// Register Layout.split on the Layout::Layout class (inside the Layout module)
|
|
218
|
-
let layout_mod = m.const_get::<_, magnus::RModule>("Layout")?;
|
|
219
|
-
let layout_class = layout_mod.const_get::<_, magnus::RClass>("Layout")?;
|
|
220
|
-
layout_class.define_singleton_method("_split", function!(widgets::layout::split_layout, 4))?;
|
|
221
|
-
layout_class.define_singleton_method(
|
|
222
|
-
"_split_with_spacers",
|
|
223
|
-
function!(widgets::layout::split_with_spacers_layout, 4),
|
|
224
|
-
)?;
|
|
225
|
-
|
|
226
|
-
// Paragraph metrics
|
|
227
|
-
m.define_module_function(
|
|
228
|
-
"_paragraph_line_count",
|
|
229
|
-
function!(widgets::paragraph::line_count, 2),
|
|
230
|
-
)?;
|
|
231
|
-
m.define_module_function(
|
|
232
|
-
"_paragraph_line_width",
|
|
233
|
-
function!(widgets::paragraph::line_width, 1),
|
|
234
|
-
)?;
|
|
235
|
-
|
|
236
|
-
// Tabs metrics
|
|
237
|
-
m.define_module_function("_tabs_width", function!(widgets::tabs::width, 1))?;
|
|
238
|
-
|
|
239
|
-
// Text measurement
|
|
240
|
-
m.define_module_function("_text_width", function!(string_width::text_width, 1))?;
|
|
241
|
-
|
|
242
|
-
// Color conversion
|
|
243
|
-
color::register(&ruby, m)?;
|
|
244
|
-
|
|
245
|
-
Ok(())
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
#[cfg(test)]
|
|
249
|
-
mod tests {
|
|
250
|
-
use ratatui::layout::Rect;
|
|
251
|
-
use ratatui::style::Color;
|
|
252
|
-
use ratatui::widgets::Widget;
|
|
253
|
-
use ratatui::widgets::{Chart, Dataset, Sparkline};
|
|
254
|
-
|
|
255
|
-
#[test]
|
|
256
|
-
fn test_parse_color() {
|
|
257
|
-
// We can test this through the style module directly now
|
|
258
|
-
use crate::style::parse_color;
|
|
259
|
-
assert_eq!(parse_color("red"), Some(Color::Red));
|
|
260
|
-
assert_eq!(parse_color("blue"), Some(Color::Blue));
|
|
261
|
-
assert_eq!(parse_color("#ffffff"), Some(Color::Rgb(255, 255, 255)));
|
|
262
|
-
assert_eq!(parse_color("#000000"), Some(Color::Rgb(0, 0, 0)));
|
|
263
|
-
assert_eq!(parse_color("invalid"), None);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
#[test]
|
|
267
|
-
fn test_sparkline_render() {
|
|
268
|
-
let mut buf = ratatui::buffer::Buffer::empty(Rect::new(0, 0, 10, 1));
|
|
269
|
-
let data = vec![1, 2, 3];
|
|
270
|
-
let sparkline = Sparkline::default().data(&data);
|
|
271
|
-
sparkline.render(Rect::new(0, 0, 10, 1), &mut buf);
|
|
272
|
-
assert!(buf.content().iter().any(|c| c.symbol() != " "));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
#[test]
|
|
276
|
-
fn test_line_chart_render() {
|
|
277
|
-
let mut buf = ratatui::buffer::Buffer::empty(Rect::new(0, 0, 20, 10));
|
|
278
|
-
let data = vec![(0.0, 0.0), (1.0, 1.0)];
|
|
279
|
-
let datasets = vec![Dataset::default().data(&data)];
|
|
280
|
-
let chart = Chart::new(datasets)
|
|
281
|
-
.x_axis(ratatui::widgets::Axis::default().bounds([0.0, 1.0]))
|
|
282
|
-
.y_axis(ratatui::widgets::Axis::default().bounds([0.0, 1.0]));
|
|
283
|
-
chart.render(Rect::new(0, 0, 20, 10), &mut buf);
|
|
284
|
-
assert!(buf.content().iter().any(|c| c.symbol() != " "));
|
|
285
|
-
}
|
|
286
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
-
|
|
4
|
-
use crate::style::{parse_color_value, parse_modifier_str, parse_style};
|
|
5
|
-
use crate::widgets;
|
|
6
|
-
use magnus::{prelude::*, Error, RArray, Value};
|
|
7
|
-
use ratatui::{buffer::Buffer, layout::Rect, style::Style, Frame};
|
|
8
|
-
|
|
9
|
-
pub fn render_node(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
|
|
10
|
-
if node.respond_to("render", true)? {
|
|
11
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
12
|
-
let ruby_area = {
|
|
13
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
14
|
-
let layout_mod = module.const_get::<_, magnus::RModule>("Layout")?;
|
|
15
|
-
let class = layout_mod.const_get::<_, magnus::RClass>("Rect")?;
|
|
16
|
-
class.funcall::<_, _, Value>("new", (area.x, area.y, area.width, area.height))?
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// Call render with just the area (no buffer!)
|
|
20
|
-
let commands: Value = node.funcall("render", (ruby_area,))?;
|
|
21
|
-
|
|
22
|
-
// Process returned draw commands
|
|
23
|
-
if let Some(arr) = RArray::from_value(commands) {
|
|
24
|
-
for i in 0..arr.len() {
|
|
25
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
26
|
-
let index = isize::try_from(i)
|
|
27
|
-
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
28
|
-
let cmd: Value = arr.entry(index)?;
|
|
29
|
-
process_draw_command(buffer, cmd)?;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return Ok(());
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
36
|
-
let class_name = unsafe { node.class().name() }.into_owned();
|
|
37
|
-
|
|
38
|
-
match class_name.as_str() {
|
|
39
|
-
"RatatuiRuby::Widgets::Paragraph" => widgets::paragraph::render(buffer, area, node)?,
|
|
40
|
-
"RatatuiRuby::Widgets::Clear" => widgets::clear::render(buffer, area, node)?,
|
|
41
|
-
"RatatuiRuby::Widgets::Cursor" => widgets::cursor::render(buffer, area, node)?,
|
|
42
|
-
"RatatuiRuby::Widgets::Overlay" => widgets::overlay::render(buffer, area, node)?,
|
|
43
|
-
"RatatuiRuby::Widgets::Center" => widgets::center::render(buffer, area, node)?,
|
|
44
|
-
"RatatuiRuby::Layout::Layout" => widgets::layout::render(buffer, area, node)?,
|
|
45
|
-
"RatatuiRuby::Widgets::List" => widgets::list::render(buffer, area, node)?,
|
|
46
|
-
"RatatuiRuby::Widgets::Gauge" => widgets::gauge::render(buffer, area, node)?,
|
|
47
|
-
"RatatuiRuby::Widgets::LineGauge" => widgets::line_gauge::render(buffer, area, node)?,
|
|
48
|
-
"RatatuiRuby::Widgets::Table" => widgets::table::render(buffer, area, node)?,
|
|
49
|
-
"RatatuiRuby::Widgets::Block" => widgets::block::render(buffer, area, node)?,
|
|
50
|
-
"RatatuiRuby::Widgets::Tabs" => widgets::tabs::render(buffer, area, node)?,
|
|
51
|
-
"RatatuiRuby::Widgets::Scrollbar" => widgets::scrollbar::render(buffer, area, node)?,
|
|
52
|
-
"RatatuiRuby::Widgets::BarChart" => widgets::barchart::render(buffer, area, node)?,
|
|
53
|
-
"RatatuiRuby::Widgets::Canvas" => widgets::canvas::render(buffer, area, node)?,
|
|
54
|
-
"RatatuiRuby::Widgets::Calendar" => widgets::calendar::render(buffer, area, node)?,
|
|
55
|
-
"RatatuiRuby::Widgets::Sparkline" => widgets::sparkline::render(buffer, area, node)?,
|
|
56
|
-
"RatatuiRuby::Widgets::Chart" => {
|
|
57
|
-
widgets::chart::render(buffer, area, node)?;
|
|
58
|
-
}
|
|
59
|
-
"RatatuiRuby::Widgets::RatatuiLogo" => widgets::ratatui_logo::render(buffer, area, node),
|
|
60
|
-
"RatatuiRuby::Widgets::RatatuiMascot" => {
|
|
61
|
-
widgets::ratatui_mascot::render_ratatui_mascot(buffer, area, node)?;
|
|
62
|
-
}
|
|
63
|
-
// Text primitives can also be rendered directly as widgets
|
|
64
|
-
"RatatuiRuby::Text::Line" => {
|
|
65
|
-
let line = crate::text::parse_line(node)?;
|
|
66
|
-
use ratatui::widgets::Widget;
|
|
67
|
-
line.render(area, buffer);
|
|
68
|
-
}
|
|
69
|
-
"RatatuiRuby::Text::Span" => {
|
|
70
|
-
let span = crate::text::parse_span(node)?;
|
|
71
|
-
use ratatui::widgets::Widget;
|
|
72
|
-
span.render(area, buffer);
|
|
73
|
-
}
|
|
74
|
-
_ => {}
|
|
75
|
-
}
|
|
76
|
-
Ok(())
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
pub fn render_widget_to_buffer(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
|
|
80
|
-
// Just delegate to render_node since it now works with Buffer
|
|
81
|
-
render_node(buffer, area, node)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
fn process_draw_command(buffer: &mut Buffer, cmd: Value) -> Result<(), Error> {
|
|
85
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
86
|
-
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
87
|
-
let class_name = unsafe { cmd.class().name() }.into_owned();
|
|
88
|
-
|
|
89
|
-
match class_name.as_str() {
|
|
90
|
-
"RatatuiRuby::Draw::StringCmd" => {
|
|
91
|
-
let x: u16 = cmd.funcall("x", ())?;
|
|
92
|
-
let y: u16 = cmd.funcall("y", ())?;
|
|
93
|
-
let string: String = cmd.funcall("string", ())?;
|
|
94
|
-
let style_val: Value = cmd.funcall("style", ())?;
|
|
95
|
-
let style = parse_style(style_val)?;
|
|
96
|
-
buffer.set_string(x, y, string, style);
|
|
97
|
-
}
|
|
98
|
-
"RatatuiRuby::Draw::CellCmd" => {
|
|
99
|
-
let x: u16 = cmd.funcall("x", ())?;
|
|
100
|
-
let y: u16 = cmd.funcall("y", ())?;
|
|
101
|
-
let cell_val: Value = cmd.funcall("cell", ())?;
|
|
102
|
-
|
|
103
|
-
let area = buffer.area;
|
|
104
|
-
if x >= area.x + area.width || y >= area.y + area.height {
|
|
105
|
-
return Ok(());
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let symbol: String = cell_val.funcall("char", ())?;
|
|
109
|
-
let fg_val: Value = cell_val.funcall("fg", ())?;
|
|
110
|
-
let bg_val: Value = cell_val.funcall("bg", ())?;
|
|
111
|
-
let modifiers_val: Value = cell_val.funcall("modifiers", ())?;
|
|
112
|
-
|
|
113
|
-
let mut style = Style::default();
|
|
114
|
-
|
|
115
|
-
if !fg_val.is_nil() {
|
|
116
|
-
if let Some(color) = parse_color_value(fg_val)? {
|
|
117
|
-
style = style.fg(color);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if !bg_val.is_nil() {
|
|
121
|
-
if let Some(color) = parse_color_value(bg_val)? {
|
|
122
|
-
style = style.bg(color);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if let Some(mods_array) = RArray::from_value(modifiers_val) {
|
|
127
|
-
for i in 0..mods_array.len() {
|
|
128
|
-
let index = isize::try_from(i)
|
|
129
|
-
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
130
|
-
let mod_val: Value = mods_array.entry(index)?;
|
|
131
|
-
// Accept both symbols and strings (DWIM)
|
|
132
|
-
let mod_str: String = mod_val.funcall("to_s", ())?;
|
|
133
|
-
if let Some(modifier) = parse_modifier_str(&mod_str) {
|
|
134
|
-
style = style.add_modifier(modifier);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if let Some(cell) = buffer.cell_mut((x, y)) {
|
|
140
|
-
cell.set_symbol(&symbol).set_style(style);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
_ => {
|
|
144
|
-
return Err(Error::new(
|
|
145
|
-
ruby.exception_type_error(),
|
|
146
|
-
format!("Unknown draw command: {class_name}"),
|
|
147
|
-
));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
Ok(())
|
|
152
|
-
}
|