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
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
-
|
|
4
|
-
use magnus::value::ReprValue;
|
|
5
|
-
use magnus::{Error, Module};
|
|
6
|
-
use ratatui::{
|
|
7
|
-
backend::{CrosstermBackend, TestBackend},
|
|
8
|
-
Terminal, TerminalOptions, Viewport,
|
|
9
|
-
};
|
|
10
|
-
use std::io;
|
|
11
|
-
use std::sync::Mutex;
|
|
12
|
-
|
|
13
|
-
pub enum TerminalWrapper {
|
|
14
|
-
Crossterm(Terminal<CrosstermBackend<io::Stdout>>),
|
|
15
|
-
Test(Terminal<TestBackend>),
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
pub static TERMINAL: Mutex<Option<TerminalWrapper>> = Mutex::new(None);
|
|
19
|
-
// Track whether we're using fullscreen viewport (for restore_terminal)
|
|
20
|
-
static IS_FULLSCREEN: Mutex<bool> = Mutex::new(false);
|
|
21
|
-
|
|
22
|
-
#[allow(clippy::needless_pass_by_value)] // Magnus FFI requires owned String, not &str
|
|
23
|
-
pub fn init_terminal(
|
|
24
|
-
focus_events: bool,
|
|
25
|
-
bracketed_paste: bool,
|
|
26
|
-
viewport_type: String,
|
|
27
|
-
viewport_height: Option<u16>,
|
|
28
|
-
) -> Result<(), Error> {
|
|
29
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
30
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
31
|
-
if term_lock.is_none() {
|
|
32
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
33
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
34
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
35
|
-
|
|
36
|
-
// Parse viewport type
|
|
37
|
-
let viewport = match viewport_type.as_ref() {
|
|
38
|
-
"inline" => {
|
|
39
|
-
let height = viewport_height.unwrap_or(8);
|
|
40
|
-
Viewport::Inline(height)
|
|
41
|
-
}
|
|
42
|
-
_ => Viewport::Fullscreen,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
ratatui::crossterm::terminal::enable_raw_mode()
|
|
46
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
47
|
-
let mut stdout = io::stdout();
|
|
48
|
-
|
|
49
|
-
// Only enter alternate screen for fullscreen viewports
|
|
50
|
-
if matches!(viewport, Viewport::Fullscreen) {
|
|
51
|
-
ratatui::crossterm::execute!(
|
|
52
|
-
stdout,
|
|
53
|
-
ratatui::crossterm::terminal::EnterAlternateScreen
|
|
54
|
-
)
|
|
55
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
ratatui::crossterm::execute!(stdout, ratatui::crossterm::event::EnableMouseCapture)
|
|
59
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
60
|
-
|
|
61
|
-
if focus_events {
|
|
62
|
-
ratatui::crossterm::execute!(stdout, ratatui::crossterm::event::EnableFocusChange)
|
|
63
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
64
|
-
}
|
|
65
|
-
if bracketed_paste {
|
|
66
|
-
ratatui::crossterm::execute!(stdout, ratatui::crossterm::event::EnableBracketedPaste)
|
|
67
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let backend = CrosstermBackend::new(stdout);
|
|
71
|
-
|
|
72
|
-
// Store whether we're using fullscreen for restore_terminal (before moving viewport)
|
|
73
|
-
let is_fullscreen = matches!(viewport, Viewport::Fullscreen);
|
|
74
|
-
*IS_FULLSCREEN.lock().unwrap() = is_fullscreen;
|
|
75
|
-
|
|
76
|
-
let options = TerminalOptions { viewport };
|
|
77
|
-
let terminal = Terminal::with_options(backend, options)
|
|
78
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
79
|
-
|
|
80
|
-
*term_lock = Some(TerminalWrapper::Crossterm(terminal));
|
|
81
|
-
}
|
|
82
|
-
Ok(())
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
#[allow(clippy::needless_pass_by_value)] // Magnus FFI requires owned String, not &str
|
|
86
|
-
pub fn init_test_terminal(
|
|
87
|
-
width: u16,
|
|
88
|
-
height: u16,
|
|
89
|
-
viewport_type: String,
|
|
90
|
-
viewport_height: Option<u16>,
|
|
91
|
-
) -> Result<(), Error> {
|
|
92
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
93
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
94
|
-
let backend = TestBackend::new(width, height);
|
|
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
|
-
|
|
99
|
-
// Parse viewport type (same as init_terminal)
|
|
100
|
-
let viewport = match viewport_type.as_ref() {
|
|
101
|
-
"inline" => {
|
|
102
|
-
let vp_height = viewport_height.unwrap_or(height);
|
|
103
|
-
Viewport::Inline(vp_height)
|
|
104
|
-
}
|
|
105
|
-
_ => Viewport::Fullscreen,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
let options = TerminalOptions { viewport };
|
|
109
|
-
let terminal = Terminal::with_options(backend, options)
|
|
110
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
111
|
-
|
|
112
|
-
*term_lock = Some(TerminalWrapper::Test(terminal));
|
|
113
|
-
Ok(())
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
pub fn restore_terminal() {
|
|
117
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
118
|
-
if let Some(wrapper) = term_lock.take() {
|
|
119
|
-
match wrapper {
|
|
120
|
-
TerminalWrapper::Crossterm(mut t) => {
|
|
121
|
-
let _ = ratatui::crossterm::terminal::disable_raw_mode();
|
|
122
|
-
|
|
123
|
-
// Only leave alternate screen if we were in fullscreen mode
|
|
124
|
-
let is_fullscreen = *IS_FULLSCREEN.lock().unwrap();
|
|
125
|
-
if is_fullscreen {
|
|
126
|
-
let _ = ratatui::crossterm::execute!(
|
|
127
|
-
t.backend_mut(),
|
|
128
|
-
ratatui::crossterm::terminal::LeaveAlternateScreen,
|
|
129
|
-
ratatui::crossterm::event::DisableMouseCapture,
|
|
130
|
-
ratatui::crossterm::event::DisableFocusChange,
|
|
131
|
-
ratatui::crossterm::event::DisableBracketedPaste
|
|
132
|
-
);
|
|
133
|
-
} else {
|
|
134
|
-
let _ = ratatui::crossterm::execute!(
|
|
135
|
-
t.backend_mut(),
|
|
136
|
-
ratatui::crossterm::event::DisableMouseCapture,
|
|
137
|
-
ratatui::crossterm::event::DisableFocusChange,
|
|
138
|
-
ratatui::crossterm::event::DisableBracketedPaste
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
TerminalWrapper::Test(_) => {}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
pub fn get_buffer_content() -> Result<String, Error> {
|
|
148
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
149
|
-
let term_lock = TERMINAL.lock().unwrap();
|
|
150
|
-
if let Some(TerminalWrapper::Test(terminal)) = term_lock.as_ref() {
|
|
151
|
-
let buffer = terminal.backend().buffer();
|
|
152
|
-
let area = buffer.area;
|
|
153
|
-
let mut result = String::new();
|
|
154
|
-
for y in 0..area.height {
|
|
155
|
-
for x in 0..area.width {
|
|
156
|
-
let cell = buffer.cell((x, y)).unwrap();
|
|
157
|
-
result.push_str(cell.symbol());
|
|
158
|
-
}
|
|
159
|
-
result.push('\n');
|
|
160
|
-
}
|
|
161
|
-
Ok(result)
|
|
162
|
-
} else {
|
|
163
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
164
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
165
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
166
|
-
Err(Error::new(
|
|
167
|
-
error_class,
|
|
168
|
-
"Terminal is not initialized as TestBackend",
|
|
169
|
-
))
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
pub fn insert_before(height: u16, widget: magnus::Value) -> Result<(), Error> {
|
|
174
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
175
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
176
|
-
|
|
177
|
-
if let Some(wrapper) = term_lock.as_mut() {
|
|
178
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
179
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
180
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
181
|
-
|
|
182
|
-
match wrapper {
|
|
183
|
-
TerminalWrapper::Crossterm(term) => {
|
|
184
|
-
// Capture rendering error since closure can't return Result
|
|
185
|
-
let mut render_error: Option<String> = None;
|
|
186
|
-
|
|
187
|
-
let result = term.insert_before(height, |buf| {
|
|
188
|
-
let area = buf.area();
|
|
189
|
-
let area_copy = *area; // Copy rect before closure capture
|
|
190
|
-
|
|
191
|
-
// Render widget to buffer using centralized dispatch
|
|
192
|
-
let render_result =
|
|
193
|
-
crate::rendering::render_widget_to_buffer(buf, area_copy, widget);
|
|
194
|
-
|
|
195
|
-
if let Err(e) = render_result {
|
|
196
|
-
render_error = Some(e.to_string());
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Handle insert_before error
|
|
201
|
-
result.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
202
|
-
|
|
203
|
-
// Handle rendering error
|
|
204
|
-
if let Some(err_msg) = render_error {
|
|
205
|
-
return Err(Error::new(error_class, err_msg));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
TerminalWrapper::Test(term) => {
|
|
209
|
-
// Capture rendering error since closure can't return Result
|
|
210
|
-
let mut render_error: Option<String> = None;
|
|
211
|
-
|
|
212
|
-
let result = term.insert_before(height, |buf| {
|
|
213
|
-
let area = buf.area();
|
|
214
|
-
let area_copy = *area; // Copy rect before closure capture
|
|
215
|
-
|
|
216
|
-
// Render widget to buffer using centralized dispatch
|
|
217
|
-
let render_result =
|
|
218
|
-
crate::rendering::render_widget_to_buffer(buf, area_copy, widget);
|
|
219
|
-
|
|
220
|
-
if let Err(e) = render_result {
|
|
221
|
-
render_error = Some(e.to_string());
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Handle insert_before error
|
|
226
|
-
result.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
227
|
-
|
|
228
|
-
// Handle rendering error
|
|
229
|
-
if let Some(err_msg) = render_error {
|
|
230
|
-
return Err(Error::new(error_class, err_msg));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
Ok(())
|
|
235
|
-
} else {
|
|
236
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
237
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
238
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
239
|
-
Err(Error::new(error_class, "Terminal not initialized"))
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
pub fn get_terminal_area() -> Result<magnus::RHash, Error> {
|
|
244
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
245
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
246
|
-
|
|
247
|
-
if let Some(wrapper) = term_lock.as_mut() {
|
|
248
|
-
// Get viewport area directly from the terminal
|
|
249
|
-
let area = match wrapper {
|
|
250
|
-
TerminalWrapper::Crossterm(term) => term.get_frame().area(),
|
|
251
|
-
TerminalWrapper::Test(term) => term.get_frame().area(),
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
let hash = ruby.hash_new();
|
|
255
|
-
hash.aset("x", area.x)?;
|
|
256
|
-
hash.aset("y", area.y)?;
|
|
257
|
-
hash.aset("width", area.width)?;
|
|
258
|
-
hash.aset("height", area.height)?;
|
|
259
|
-
Ok(hash)
|
|
260
|
-
} else {
|
|
261
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
262
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
263
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
264
|
-
Err(Error::new(error_class, "Terminal is not initialized"))
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/// Returns the full terminal backend size (not the viewport)
|
|
269
|
-
pub fn get_terminal_size() -> Result<magnus::RHash, Error> {
|
|
270
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
271
|
-
let term_lock = TERMINAL.lock().unwrap();
|
|
272
|
-
|
|
273
|
-
if let Some(wrapper) = term_lock.as_ref() {
|
|
274
|
-
let size = match wrapper {
|
|
275
|
-
TerminalWrapper::Crossterm(term) => term.size().map_err(|e| {
|
|
276
|
-
let module = ruby.define_module("RatatuiRuby").unwrap();
|
|
277
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error").unwrap();
|
|
278
|
-
let error_class = error_base.const_get("Terminal").unwrap();
|
|
279
|
-
Error::new(error_class, e.to_string())
|
|
280
|
-
})?,
|
|
281
|
-
TerminalWrapper::Test(term) => term.size().unwrap_or_default(),
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
let hash = ruby.hash_new();
|
|
285
|
-
hash.aset("x", 0)?;
|
|
286
|
-
hash.aset("y", 0)?;
|
|
287
|
-
hash.aset("width", size.width)?;
|
|
288
|
-
hash.aset("height", size.height)?;
|
|
289
|
-
Ok(hash)
|
|
290
|
-
} else {
|
|
291
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
292
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
293
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
294
|
-
Err(Error::new(error_class, "Terminal is not initialized"))
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
pub fn get_viewport_type() -> Result<String, Error> {
|
|
299
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
300
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
301
|
-
|
|
302
|
-
if let Some(wrapper) = term_lock.as_mut() {
|
|
303
|
-
// Get viewport area directly from the terminal
|
|
304
|
-
let vp_area = match wrapper {
|
|
305
|
-
TerminalWrapper::Crossterm(term) => term.get_frame().area(),
|
|
306
|
-
TerminalWrapper::Test(term) => term.get_frame().area(),
|
|
307
|
-
};
|
|
308
|
-
let backend_size = match wrapper {
|
|
309
|
-
TerminalWrapper::Crossterm(term) => term.size().unwrap_or_default(),
|
|
310
|
-
TerminalWrapper::Test(term) => term.size().unwrap_or_default(),
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
if vp_area.height < backend_size.height {
|
|
314
|
-
Ok("inline".to_string())
|
|
315
|
-
} else {
|
|
316
|
-
Ok("fullscreen".to_string())
|
|
317
|
-
}
|
|
318
|
-
} else {
|
|
319
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
320
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
321
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
322
|
-
Err(Error::new(error_class, "Terminal not initialized"))
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
pub fn get_cursor_position() -> Result<Option<(u16, u16)>, Error> {
|
|
327
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
328
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
329
|
-
if let Some(TerminalWrapper::Test(terminal)) = term_lock.as_mut() {
|
|
330
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
331
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
332
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
333
|
-
let pos = terminal
|
|
334
|
-
.get_cursor_position()
|
|
335
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
336
|
-
Ok(Some(pos.into()))
|
|
337
|
-
} else {
|
|
338
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
339
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
340
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
341
|
-
Err(Error::new(
|
|
342
|
-
error_class,
|
|
343
|
-
"Terminal is not initialized as TestBackend",
|
|
344
|
-
))
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
pub fn set_cursor_position(x: u16, y: u16) -> Result<(), Error> {
|
|
349
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
350
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
351
|
-
|
|
352
|
-
if let Some(wrapper) = term_lock.as_mut() {
|
|
353
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
354
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
355
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
356
|
-
|
|
357
|
-
match wrapper {
|
|
358
|
-
TerminalWrapper::Crossterm(term) => {
|
|
359
|
-
term.set_cursor_position((x, y))
|
|
360
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
361
|
-
}
|
|
362
|
-
TerminalWrapper::Test(term) => {
|
|
363
|
-
term.set_cursor_position((x, y))
|
|
364
|
-
.map_err(|e| Error::new(error_class, e.to_string()))?;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
Ok(())
|
|
368
|
-
} else {
|
|
369
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
370
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
371
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
372
|
-
Err(Error::new(error_class, "Terminal is not initialized"))
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
pub fn resize_terminal(width: u16, height: u16) -> Result<(), Error> {
|
|
377
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
378
|
-
let mut term_lock = TERMINAL.lock().unwrap();
|
|
379
|
-
if let Some(wrapper) = term_lock.as_mut() {
|
|
380
|
-
match wrapper {
|
|
381
|
-
TerminalWrapper::Crossterm(_) => {}
|
|
382
|
-
TerminalWrapper::Test(terminal) => {
|
|
383
|
-
terminal.backend_mut().resize(width, height);
|
|
384
|
-
if let Err(e) = terminal.resize(ratatui::layout::Rect::new(0, 0, width, height)) {
|
|
385
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
386
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
387
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
388
|
-
return Err(Error::new(error_class, e.to_string()));
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
Ok(())
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
use magnus::Value;
|
|
397
|
-
|
|
398
|
-
pub fn get_cell_at(x: u16, y: u16) -> Result<magnus::RHash, Error> {
|
|
399
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
400
|
-
let term_lock = TERMINAL.lock().unwrap();
|
|
401
|
-
if let Some(TerminalWrapper::Test(terminal)) = term_lock.as_ref() {
|
|
402
|
-
let buffer = terminal.backend().buffer();
|
|
403
|
-
if let Some(cell) = buffer.cell((x, y)) {
|
|
404
|
-
let hash = ruby.hash_new();
|
|
405
|
-
hash.aset("char", cell.symbol())?;
|
|
406
|
-
hash.aset("fg", color_to_value(cell.fg))?;
|
|
407
|
-
hash.aset("bg", color_to_value(cell.bg))?;
|
|
408
|
-
hash.aset("underline_color", color_to_value(cell.underline_color))?;
|
|
409
|
-
hash.aset("modifiers", modifiers_to_value(cell.modifier))?;
|
|
410
|
-
Ok(hash)
|
|
411
|
-
} else {
|
|
412
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
413
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
414
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
415
|
-
Err(Error::new(
|
|
416
|
-
error_class,
|
|
417
|
-
format!("Coordinates ({x}, {y}) out of bounds"),
|
|
418
|
-
))
|
|
419
|
-
}
|
|
420
|
-
} else {
|
|
421
|
-
let module = ruby.define_module("RatatuiRuby")?;
|
|
422
|
-
let error_base = module.const_get::<_, magnus::RClass>("Error")?;
|
|
423
|
-
let error_class = error_base.const_get("Terminal")?;
|
|
424
|
-
Err(Error::new(
|
|
425
|
-
error_class,
|
|
426
|
-
"Terminal is not initialized as TestBackend",
|
|
427
|
-
))
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
fn color_to_value(color: ratatui::style::Color) -> Value {
|
|
432
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
433
|
-
match color {
|
|
434
|
-
ratatui::style::Color::Reset => ruby.qnil().as_value(),
|
|
435
|
-
ratatui::style::Color::Black => ruby.to_symbol("black").as_value(),
|
|
436
|
-
ratatui::style::Color::Red => ruby.to_symbol("red").as_value(),
|
|
437
|
-
ratatui::style::Color::Green => ruby.to_symbol("green").as_value(),
|
|
438
|
-
ratatui::style::Color::Yellow => ruby.to_symbol("yellow").as_value(),
|
|
439
|
-
ratatui::style::Color::Blue => ruby.to_symbol("blue").as_value(),
|
|
440
|
-
ratatui::style::Color::Magenta => ruby.to_symbol("magenta").as_value(),
|
|
441
|
-
ratatui::style::Color::Cyan => ruby.to_symbol("cyan").as_value(),
|
|
442
|
-
ratatui::style::Color::Gray => ruby.to_symbol("gray").as_value(),
|
|
443
|
-
ratatui::style::Color::DarkGray => ruby.to_symbol("dark_gray").as_value(),
|
|
444
|
-
ratatui::style::Color::LightRed => ruby.to_symbol("light_red").as_value(),
|
|
445
|
-
ratatui::style::Color::LightGreen => ruby.to_symbol("light_green").as_value(),
|
|
446
|
-
ratatui::style::Color::LightYellow => ruby.to_symbol("light_yellow").as_value(),
|
|
447
|
-
ratatui::style::Color::LightBlue => ruby.to_symbol("light_blue").as_value(),
|
|
448
|
-
ratatui::style::Color::LightMagenta => ruby.to_symbol("light_magenta").as_value(),
|
|
449
|
-
ratatui::style::Color::LightCyan => ruby.to_symbol("light_cyan").as_value(),
|
|
450
|
-
ratatui::style::Color::White => ruby.to_symbol("white").as_value(),
|
|
451
|
-
ratatui::style::Color::Rgb(r, g, b) => ruby
|
|
452
|
-
.str_new(&(format!("#{r:02x}{g:02x}{b:02x}")))
|
|
453
|
-
.as_value(),
|
|
454
|
-
ratatui::style::Color::Indexed(i) => ruby.to_symbol(format!("indexed_{i}")).as_value(),
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
fn modifiers_to_value(modifier: ratatui::style::Modifier) -> Value {
|
|
459
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
460
|
-
let ary = ruby.ary_new();
|
|
461
|
-
|
|
462
|
-
if modifier.contains(ratatui::style::Modifier::BOLD) {
|
|
463
|
-
let _ = ary.push(ruby.to_symbol("bold"));
|
|
464
|
-
}
|
|
465
|
-
if modifier.contains(ratatui::style::Modifier::ITALIC) {
|
|
466
|
-
let _ = ary.push(ruby.to_symbol("italic"));
|
|
467
|
-
}
|
|
468
|
-
if modifier.contains(ratatui::style::Modifier::DIM) {
|
|
469
|
-
let _ = ary.push(ruby.to_symbol("dim"));
|
|
470
|
-
}
|
|
471
|
-
if modifier.contains(ratatui::style::Modifier::UNDERLINED) {
|
|
472
|
-
let _ = ary.push(ruby.to_symbol("underlined"));
|
|
473
|
-
}
|
|
474
|
-
if modifier.contains(ratatui::style::Modifier::REVERSED) {
|
|
475
|
-
let _ = ary.push(ruby.to_symbol("reversed"));
|
|
476
|
-
}
|
|
477
|
-
if modifier.contains(ratatui::style::Modifier::HIDDEN) {
|
|
478
|
-
let _ = ary.push(ruby.to_symbol("hidden"));
|
|
479
|
-
}
|
|
480
|
-
if modifier.contains(ratatui::style::Modifier::CROSSED_OUT) {
|
|
481
|
-
let _ = ary.push(ruby.to_symbol("crossed_out"));
|
|
482
|
-
}
|
|
483
|
-
if modifier.contains(ratatui::style::Modifier::SLOW_BLINK) {
|
|
484
|
-
let _ = ary.push(ruby.to_symbol("slow_blink"));
|
|
485
|
-
}
|
|
486
|
-
if modifier.contains(ratatui::style::Modifier::RAPID_BLINK) {
|
|
487
|
-
let _ = ary.push(ruby.to_symbol("rapid_blink"));
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
ary.as_value()
|
|
491
|
-
}
|