ratatui_ruby 1.2.1 → 1.3.2
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/ext/ratatui_ruby/Cargo.lock +2 -1
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +157 -18
- data/lib/ratatui_ruby/event/focus_gained.rb +50 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +51 -0
- data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +2 -0
- data/lib/ratatui_ruby/event/key.rb +9 -0
- data/lib/ratatui_ruby/event/mouse.rb +33 -0
- data/lib/ratatui_ruby/event/paste.rb +25 -0
- data/lib/ratatui_ruby/event/resize.rb +65 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/sig/ratatui_ruby/event.rbs +97 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 859d136b449ee3babaa09dd80f9bfa1507f08146a632564369c6ab2aa3b937c8
|
|
4
|
+
data.tar.gz: dd7e5cf58b04c81e2b5a0c2eb75e796dbfdcbcdc925d4ee6d6f5eda3fdb15df9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75ccfaca2381095492f89c37abfc04530391e6ff31691253cdcd7feb93f12c81bb71bf011dbe00ef34f7cf9a580cf601d5939e592e22949390ebde29721bf05f
|
|
7
|
+
data.tar.gz: d498466df9bb6af3f4e2c76ce6db58c379a684a8670fa01df1912c621306dc35bdb30c4d1481196b6b493751546d0a03f88bf3be9ac1a888cd80c4c478ae8adc
|
data/ext/ratatui_ruby/Cargo.lock
CHANGED
|
@@ -1059,12 +1059,13 @@ dependencies = [
|
|
|
1059
1059
|
|
|
1060
1060
|
[[package]]
|
|
1061
1061
|
name = "ratatui_ruby"
|
|
1062
|
-
version = "1.2
|
|
1062
|
+
version = "1.3.2"
|
|
1063
1063
|
dependencies = [
|
|
1064
1064
|
"bumpalo",
|
|
1065
1065
|
"lazy_static",
|
|
1066
1066
|
"magnus",
|
|
1067
1067
|
"ratatui",
|
|
1068
|
+
"rb-sys",
|
|
1068
1069
|
"time",
|
|
1069
1070
|
"unicode-width 0.1.14",
|
|
1070
1071
|
]
|
data/ext/ratatui_ruby/Cargo.toml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[package]
|
|
5
5
|
name = "ratatui_ruby"
|
|
6
|
-
version = "1.2
|
|
6
|
+
version = "1.3.2"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
|
|
9
9
|
[lib]
|
|
@@ -11,6 +11,7 @@ crate-type = ["cdylib", "staticlib"]
|
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
13
|
magnus = "0.8.2"
|
|
14
|
+
rb-sys = "*"
|
|
14
15
|
ratatui = { version = "0.30", features = ["widget-calendar", "layout-cache", "unstable-rendered-line-info", "palette"] }
|
|
15
16
|
unicode-width = "0.1"
|
|
16
17
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
use magnus::{Error, IntoValue, TryConvert, Value};
|
|
5
|
+
use rb_sys::rb_thread_call_without_gvl;
|
|
5
6
|
use std::cell::RefCell;
|
|
7
|
+
use std::os::raw::c_void;
|
|
6
8
|
|
|
7
9
|
/// Wrapper enum for test events - includes crossterm events and our Sync event.
|
|
8
10
|
#[derive(Debug, Clone)]
|
|
@@ -314,6 +316,92 @@ pub fn clear_events() {
|
|
|
314
316
|
EVENT_QUEUE.with(|q| q.borrow_mut().clear());
|
|
315
317
|
}
|
|
316
318
|
|
|
319
|
+
/// Result of polling crossterm from outside the GVL.
|
|
320
|
+
///
|
|
321
|
+
/// The blocking I/O (poll + read) happens without the Ruby GVL held,
|
|
322
|
+
/// so other Ruby threads can run concurrently. This enum carries the
|
|
323
|
+
/// result back across the GVL boundary for Ruby object construction.
|
|
324
|
+
#[derive(Debug)]
|
|
325
|
+
enum PollResult {
|
|
326
|
+
/// A crossterm event was read successfully.
|
|
327
|
+
Event(ratatui::crossterm::event::Event),
|
|
328
|
+
/// The timeout expired with no event available.
|
|
329
|
+
NoEvent,
|
|
330
|
+
/// An I/O error occurred during poll or read.
|
|
331
|
+
Error(String),
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// Data passed to the GVL-free polling callback.
|
|
335
|
+
///
|
|
336
|
+
/// The `timeout` field controls the poll behavior:
|
|
337
|
+
/// - `Some(duration)`: poll with a timeout, return `NoEvent` if it expires.
|
|
338
|
+
/// - `None`: block indefinitely until an event arrives.
|
|
339
|
+
struct PollData {
|
|
340
|
+
timeout: Option<std::time::Duration>,
|
|
341
|
+
result: PollResult,
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// Polls crossterm for events without the Ruby GVL held.
|
|
345
|
+
///
|
|
346
|
+
/// This is the work function passed to `rb_thread_call_without_gvl`.
|
|
347
|
+
/// It performs blocking I/O that would otherwise starve Ruby threads.
|
|
348
|
+
extern "C" fn poll_without_gvl(data: *mut c_void) -> *mut c_void {
|
|
349
|
+
// SAFETY: `data` is a valid pointer to `PollData`, passed by
|
|
350
|
+
// `poll_crossterm_without_gvl` via `rb_thread_call_without_gvl`. The
|
|
351
|
+
// pointer remains valid for the duration of this callback because the
|
|
352
|
+
// caller owns the `PollData` on the stack and waits for us to return.
|
|
353
|
+
let data = unsafe { &mut *data.cast::<PollData>() };
|
|
354
|
+
|
|
355
|
+
data.result = match data.timeout {
|
|
356
|
+
Some(duration) => match ratatui::crossterm::event::poll(duration) {
|
|
357
|
+
Ok(true) => match ratatui::crossterm::event::read() {
|
|
358
|
+
Ok(event) => PollResult::Event(event),
|
|
359
|
+
Err(e) => PollResult::Error(e.to_string()),
|
|
360
|
+
},
|
|
361
|
+
Ok(false) => PollResult::NoEvent,
|
|
362
|
+
Err(e) => PollResult::Error(e.to_string()),
|
|
363
|
+
},
|
|
364
|
+
None => match ratatui::crossterm::event::read() {
|
|
365
|
+
Ok(event) => PollResult::Event(event),
|
|
366
|
+
Err(e) => PollResult::Error(e.to_string()),
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
std::ptr::null_mut()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/// Polls crossterm with the GVL released, then converts the result
|
|
374
|
+
/// to a Ruby value after reacquiring the GVL.
|
|
375
|
+
fn poll_crossterm_without_gvl(
|
|
376
|
+
ruby: &magnus::Ruby,
|
|
377
|
+
timeout: Option<std::time::Duration>,
|
|
378
|
+
) -> Result<Value, Error> {
|
|
379
|
+
let mut data = PollData {
|
|
380
|
+
timeout,
|
|
381
|
+
result: PollResult::NoEvent,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// SAFETY: `data` is a valid stack-local `PollData` whose lifetime
|
|
385
|
+
// spans this entire call. `poll_without_gvl` only reads/writes
|
|
386
|
+
// through the pointer while `rb_thread_call_without_gvl` blocks,
|
|
387
|
+
// and we don't access `data` again until that function returns.
|
|
388
|
+
unsafe {
|
|
389
|
+
rb_thread_call_without_gvl(
|
|
390
|
+
Some(poll_without_gvl),
|
|
391
|
+
(&raw mut data).cast::<c_void>(),
|
|
392
|
+
None,
|
|
393
|
+
std::ptr::null_mut(),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// GVL is now re-held — safe to create Ruby objects.
|
|
398
|
+
match data.result {
|
|
399
|
+
PollResult::Event(event) => handle_crossterm_event(event),
|
|
400
|
+
PollResult::NoEvent => Ok(ruby.qnil().into_value_with(ruby)),
|
|
401
|
+
PollResult::Error(msg) => Err(Error::new(ruby.exception_runtime_error(), msg)),
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
317
405
|
pub fn poll_event(ruby: &magnus::Ruby, timeout_val: Option<f64>) -> Result<Value, Error> {
|
|
318
406
|
let event = EVENT_QUEUE.with(|q| {
|
|
319
407
|
let mut queue = q.borrow_mut();
|
|
@@ -334,24 +422,8 @@ pub fn poll_event(ruby: &magnus::Ruby, timeout_val: Option<f64>) -> Result<Value
|
|
|
334
422
|
return Ok(ruby.qnil().into_value_with(ruby));
|
|
335
423
|
}
|
|
336
424
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
let duration = std::time::Duration::from_secs_f64(secs);
|
|
340
|
-
if ratatui::crossterm::event::poll(duration)
|
|
341
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?
|
|
342
|
-
{
|
|
343
|
-
let event = ratatui::crossterm::event::read()
|
|
344
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
345
|
-
handle_crossterm_event(event)
|
|
346
|
-
} else {
|
|
347
|
-
Ok(ruby.qnil().into_value_with(ruby))
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
// Blocking: wait indefinitely for an event
|
|
351
|
-
let event = ratatui::crossterm::event::read()
|
|
352
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
353
|
-
handle_crossterm_event(event)
|
|
354
|
-
}
|
|
425
|
+
let timeout = timeout_val.map(std::time::Duration::from_secs_f64);
|
|
426
|
+
poll_crossterm_without_gvl(ruby, timeout)
|
|
355
427
|
}
|
|
356
428
|
|
|
357
429
|
fn handle_test_event(event: TestEvent) -> Result<Value, Error> {
|
|
@@ -559,3 +631,70 @@ fn handle_focus_event(event_type: &str) -> Result<Value, Error> {
|
|
|
559
631
|
hash.aset(ruby.to_symbol("type"), ruby.to_symbol(event_type))?;
|
|
560
632
|
Ok(hash.into_value_with(&ruby))
|
|
561
633
|
}
|
|
634
|
+
|
|
635
|
+
#[cfg(test)]
|
|
636
|
+
mod tests {
|
|
637
|
+
use super::*;
|
|
638
|
+
|
|
639
|
+
#[test]
|
|
640
|
+
fn poll_data_defaults_to_no_event() {
|
|
641
|
+
let data = PollData {
|
|
642
|
+
timeout: Some(std::time::Duration::from_millis(0)),
|
|
643
|
+
result: PollResult::NoEvent,
|
|
644
|
+
};
|
|
645
|
+
assert!(matches!(data.result, PollResult::NoEvent));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
#[test]
|
|
649
|
+
fn poll_data_accepts_none_timeout_for_indefinite_blocking() {
|
|
650
|
+
let data = PollData {
|
|
651
|
+
timeout: None,
|
|
652
|
+
result: PollResult::NoEvent,
|
|
653
|
+
};
|
|
654
|
+
assert!(data.timeout.is_none());
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
#[test]
|
|
658
|
+
fn poll_result_error_preserves_message() {
|
|
659
|
+
let result = PollResult::Error("connection reset".to_string());
|
|
660
|
+
match result {
|
|
661
|
+
PollResult::Error(msg) => assert_eq!(msg, "connection reset"),
|
|
662
|
+
_ => panic!("Expected PollResult::Error"),
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
#[test]
|
|
667
|
+
fn poll_result_event_wraps_crossterm_event() {
|
|
668
|
+
let key_event =
|
|
669
|
+
ratatui::crossterm::event::Event::Key(ratatui::crossterm::event::KeyEvent::new(
|
|
670
|
+
ratatui::crossterm::event::KeyCode::Char('q'),
|
|
671
|
+
ratatui::crossterm::event::KeyModifiers::empty(),
|
|
672
|
+
));
|
|
673
|
+
let result = PollResult::Event(key_event.clone());
|
|
674
|
+
match result {
|
|
675
|
+
PollResult::Event(e) => assert_eq!(format!("{e:?}"), format!("{key_event:?}")),
|
|
676
|
+
_ => panic!("Expected PollResult::Event"),
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
#[test]
|
|
681
|
+
fn poll_without_gvl_returns_no_event_on_zero_timeout() {
|
|
682
|
+
// With a zero-duration timeout, poll returns immediately with no event.
|
|
683
|
+
// In headless environments (CI), crossterm may return an Error because
|
|
684
|
+
// there is no terminal to read from — that's also a valid outcome.
|
|
685
|
+
let mut data = PollData {
|
|
686
|
+
timeout: Some(std::time::Duration::from_millis(0)),
|
|
687
|
+
result: PollResult::Error("sentinel — should be overwritten".to_string()),
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
poll_without_gvl(&mut data as *mut PollData as *mut c_void);
|
|
691
|
+
|
|
692
|
+
// The callback must have overwritten the sentinel value.
|
|
693
|
+
match &data.result {
|
|
694
|
+
PollResult::Error(msg) if msg == "sentinel — should be overwritten" => {
|
|
695
|
+
panic!("poll_without_gvl did not write to data.result")
|
|
696
|
+
}
|
|
697
|
+
_ => {} // NoEvent, Event, or a *different* Error are all fine.
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
@@ -70,6 +70,56 @@ module RatatuiRuby
|
|
|
70
70
|
def ==(other)
|
|
71
71
|
other.is_a?(FocusGained)
|
|
72
72
|
end
|
|
73
|
+
|
|
74
|
+
# =========================================================================
|
|
75
|
+
# DWIM Predicates
|
|
76
|
+
# =========================================================================
|
|
77
|
+
|
|
78
|
+
# Returns true. The terminal window is now in focus.
|
|
79
|
+
#
|
|
80
|
+
# event.focus? # => true
|
|
81
|
+
def focus?
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
alias focused? focus?
|
|
85
|
+
|
|
86
|
+
# Returns true. The application gained focus.
|
|
87
|
+
#
|
|
88
|
+
# event.gained? # => true
|
|
89
|
+
def gained?
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns false. This is not a focus lost event.
|
|
94
|
+
#
|
|
95
|
+
# event.lost? # => false
|
|
96
|
+
def lost?
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns false. Blur is the opposite of focus gained.
|
|
101
|
+
#
|
|
102
|
+
# event.blur? # => false
|
|
103
|
+
def blur?
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
alias blurred? blur?
|
|
107
|
+
|
|
108
|
+
# Returns true. The application is active.
|
|
109
|
+
#
|
|
110
|
+
# event.active? # => true
|
|
111
|
+
def active?
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
alias foreground? active?
|
|
115
|
+
|
|
116
|
+
# Returns false. The application is not inactive.
|
|
117
|
+
#
|
|
118
|
+
# event.inactive? # => false
|
|
119
|
+
def inactive?
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
alias background? inactive?
|
|
73
123
|
end
|
|
74
124
|
end
|
|
75
125
|
end
|
|
@@ -71,6 +71,57 @@ module RatatuiRuby
|
|
|
71
71
|
def ==(other)
|
|
72
72
|
other.is_a?(FocusLost)
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
# =========================================================================
|
|
76
|
+
# DWIM Predicates
|
|
77
|
+
# =========================================================================
|
|
78
|
+
|
|
79
|
+
# Returns true. The terminal has lost focus (blur).
|
|
80
|
+
#
|
|
81
|
+
# event.blur? # => true
|
|
82
|
+
def blur?
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
alias blurred? blur?
|
|
86
|
+
|
|
87
|
+
# Returns true. The application lost focus.
|
|
88
|
+
#
|
|
89
|
+
# event.lost? # => true
|
|
90
|
+
def lost?
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
alias unfocused? lost?
|
|
94
|
+
|
|
95
|
+
# Returns false. This is not a focus gained event.
|
|
96
|
+
#
|
|
97
|
+
# event.focus? # => false
|
|
98
|
+
def focus?
|
|
99
|
+
false
|
|
100
|
+
end
|
|
101
|
+
alias focused? focus?
|
|
102
|
+
|
|
103
|
+
# Returns false. This is not a gained event.
|
|
104
|
+
#
|
|
105
|
+
# event.gained? # => false
|
|
106
|
+
def gained?
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns true. The application is inactive.
|
|
111
|
+
#
|
|
112
|
+
# event.inactive? # => true
|
|
113
|
+
def inactive?
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
alias background? inactive?
|
|
117
|
+
|
|
118
|
+
# Returns false. The application is not active.
|
|
119
|
+
#
|
|
120
|
+
# event.active? # => false
|
|
121
|
+
def active?
|
|
122
|
+
false
|
|
123
|
+
end
|
|
124
|
+
alias foreground? active?
|
|
74
125
|
end
|
|
75
126
|
end
|
|
76
127
|
end
|
|
@@ -0,0 +1,301 @@
|
|
|
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
|
+
class Event
|
|
10
|
+
class Key < Event
|
|
11
|
+
# DWIM predicates for common key patterns.
|
|
12
|
+
#
|
|
13
|
+
# These predicates anticipate what developers intuitively try. Space bars,
|
|
14
|
+
# character categories, Unix signals, and Vim-style navigation.
|
|
15
|
+
module Dwim
|
|
16
|
+
# Returns true if the key is a space character.
|
|
17
|
+
#
|
|
18
|
+
# event.space? # => true for " "
|
|
19
|
+
def space?
|
|
20
|
+
@code == " " && @modifiers.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias spacebar? space?
|
|
24
|
+
|
|
25
|
+
# Returns true if the key is Enter. Alias for carriage return.
|
|
26
|
+
#
|
|
27
|
+
# event.cr? # => true for enter
|
|
28
|
+
def cr?
|
|
29
|
+
@code == "enter" && @modifiers.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
alias carriagereturn? cr?
|
|
33
|
+
alias linefeed? cr?
|
|
34
|
+
alias newline? cr?
|
|
35
|
+
alias lf? cr?
|
|
36
|
+
|
|
37
|
+
# Returns true if the key is a single letter (a-z, A-Z).
|
|
38
|
+
#
|
|
39
|
+
# Event::Key.new(code: "a").letter? # => true
|
|
40
|
+
def letter?
|
|
41
|
+
@code.length == 1 && @code.match?(/\A[A-Za-z]\z/)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns true if the key is a digit (0-9).
|
|
45
|
+
#
|
|
46
|
+
# Event::Key.new(code: "5").digit? # => true
|
|
47
|
+
def digit?
|
|
48
|
+
@code.length == 1 && @code.match?(/\A[0-9]\z/)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns true if the key is alphanumeric.
|
|
52
|
+
#
|
|
53
|
+
# Event::Key.new(code: "a").alphanumeric? # => true
|
|
54
|
+
def alphanumeric?
|
|
55
|
+
letter? || digit?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns true if the key is punctuation.
|
|
59
|
+
#
|
|
60
|
+
# Event::Key.new(code: "@", modifiers: ["shift"]).punctuation? # => true
|
|
61
|
+
def punctuation?
|
|
62
|
+
return false unless @code.length == 1
|
|
63
|
+
!letter? && !digit? && !whitespace?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns true if the key is whitespace (space, enter, tab).
|
|
67
|
+
#
|
|
68
|
+
# Event::Key.new(code: " ").whitespace? # => true
|
|
69
|
+
def whitespace?
|
|
70
|
+
@code == " " || @code == "enter" || @code == "tab"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns true for interrupt (Ctrl+C).
|
|
74
|
+
#
|
|
75
|
+
# event.interrupt? # => true for Ctrl+C
|
|
76
|
+
def interrupt?
|
|
77
|
+
@code == "c" && @modifiers == ["ctrl"]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns true for end-of-file (Ctrl+D).
|
|
81
|
+
#
|
|
82
|
+
# event.eof? # => true for Ctrl+D
|
|
83
|
+
def eof?
|
|
84
|
+
@code == "d" && @modifiers == ["ctrl"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns true for cancel (Esc or Ctrl+C).
|
|
88
|
+
#
|
|
89
|
+
# event.cancel? # => true for Esc or Ctrl+C
|
|
90
|
+
def cancel?
|
|
91
|
+
(@code == "esc" && @modifiers.empty?) || interrupt?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns true for SIGINT (Ctrl+C).
|
|
95
|
+
#
|
|
96
|
+
# event.sigint? # => true for Ctrl+C
|
|
97
|
+
def sigint?
|
|
98
|
+
interrupt?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
alias int? sigint?
|
|
102
|
+
|
|
103
|
+
# Returns true for SIGTSTP (Ctrl+Z) - suspend/stop.
|
|
104
|
+
#
|
|
105
|
+
# event.suspend? # => true for Ctrl+Z
|
|
106
|
+
def suspend?
|
|
107
|
+
@code == "z" && @modifiers == ["ctrl"]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias sigtstp? suspend?
|
|
111
|
+
alias tstp? suspend?
|
|
112
|
+
|
|
113
|
+
# Returns true for SIGQUIT (Ctrl+\).
|
|
114
|
+
#
|
|
115
|
+
# event.quit? # => true for Ctrl+\
|
|
116
|
+
def quit?
|
|
117
|
+
@code == "\\" && @modifiers == ["ctrl"]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
alias sigquit? quit?
|
|
121
|
+
|
|
122
|
+
NAVIGATION_KEYS = %w[up down left right home end page_up page_down].freeze # :nodoc:
|
|
123
|
+
|
|
124
|
+
# Returns true if key is a navigation key.
|
|
125
|
+
#
|
|
126
|
+
# Event::Key.new(code: "up").navigation? # => true
|
|
127
|
+
def navigation?
|
|
128
|
+
NAVIGATION_KEYS.include?(@code) && @modifiers.empty?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
ARROW_KEYS = %w[up down left right].freeze # :nodoc:
|
|
132
|
+
|
|
133
|
+
# Returns true if key is an arrow key.
|
|
134
|
+
#
|
|
135
|
+
# Event::Key.new(code: "up").arrow? # => true
|
|
136
|
+
def arrow?
|
|
137
|
+
ARROW_KEYS.include?(@code) && @modifiers.empty?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
VIM_MOVEMENT_KEYS = %w[h j k l w b g G].freeze # :nodoc:
|
|
141
|
+
|
|
142
|
+
# Returns true if key is a Vim movement key.
|
|
143
|
+
#
|
|
144
|
+
# Event::Key.new(code: "j").vim? # => true
|
|
145
|
+
def vim?
|
|
146
|
+
return true if VIM_MOVEMENT_KEYS.include?(@code) && @modifiers.empty?
|
|
147
|
+
@code == "G" && @modifiers == ["shift"]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Returns true for Vim left (h).
|
|
151
|
+
def vim_left?
|
|
152
|
+
@code == "h" && @modifiers.empty?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns true for Vim down (j).
|
|
156
|
+
def vim_down?
|
|
157
|
+
@code == "j" && @modifiers.empty?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns true for Vim up (k).
|
|
161
|
+
def vim_up?
|
|
162
|
+
@code == "k" && @modifiers.empty?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Returns true for Vim right (l).
|
|
166
|
+
def vim_right?
|
|
167
|
+
@code == "l" && @modifiers.empty?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns true for Vim word forward (w).
|
|
171
|
+
def vim_word_forward?
|
|
172
|
+
@code == "w" && @modifiers.empty?
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Returns true for Vim word backward (b).
|
|
176
|
+
def vim_word_backward?
|
|
177
|
+
@code == "b" && @modifiers.empty?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Returns true for Vim go to top (gg pattern, here just g).
|
|
181
|
+
def vim_top?
|
|
182
|
+
@code == "g" && @modifiers.empty?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Returns true for Vim go to bottom (G).
|
|
186
|
+
def vim_bottom?
|
|
187
|
+
@code == "G" && @modifiers == ["shift"]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Punctuation name predicates - generated at load time for performance.
|
|
191
|
+
# Maps intuitive names to their symbol characters.
|
|
192
|
+
# :nodoc:
|
|
193
|
+
PUNCTUATION_NAMES = {
|
|
194
|
+
# Navigation shortcuts (the original use case!)
|
|
195
|
+
tilde: "~",
|
|
196
|
+
slash: "/",
|
|
197
|
+
forwardslash: "/",
|
|
198
|
+
backslash: "\\",
|
|
199
|
+
|
|
200
|
+
# Common punctuation
|
|
201
|
+
comma: ",",
|
|
202
|
+
period: ".",
|
|
203
|
+
dot: ".",
|
|
204
|
+
colon: ":",
|
|
205
|
+
semicolon: ";",
|
|
206
|
+
|
|
207
|
+
# Question and exclamation
|
|
208
|
+
question: "?",
|
|
209
|
+
questionmark: "?",
|
|
210
|
+
exclamation: "!",
|
|
211
|
+
exclamationmark: "!",
|
|
212
|
+
exclamationpoint: "!",
|
|
213
|
+
bang: "!",
|
|
214
|
+
|
|
215
|
+
# Programming symbols
|
|
216
|
+
at: "@",
|
|
217
|
+
atsign: "@",
|
|
218
|
+
hash: "#",
|
|
219
|
+
pound: "#",
|
|
220
|
+
numbersign: "#",
|
|
221
|
+
dollar: "$",
|
|
222
|
+
dollarsign: "$",
|
|
223
|
+
percent: "%",
|
|
224
|
+
caret: "^",
|
|
225
|
+
circumflex: "^",
|
|
226
|
+
ampersand: "&",
|
|
227
|
+
asterisk: "*",
|
|
228
|
+
star: "*",
|
|
229
|
+
|
|
230
|
+
# Arithmetic and comparison
|
|
231
|
+
underscore: "_",
|
|
232
|
+
hyphen: "-",
|
|
233
|
+
dash: "-",
|
|
234
|
+
minus: "-",
|
|
235
|
+
plus: "+",
|
|
236
|
+
equals: "=",
|
|
237
|
+
equalsign: "=",
|
|
238
|
+
pipe: "|",
|
|
239
|
+
bar: "|",
|
|
240
|
+
lessthan: "<",
|
|
241
|
+
lt: "<",
|
|
242
|
+
greaterthan: ">",
|
|
243
|
+
gt: ">",
|
|
244
|
+
|
|
245
|
+
# Brackets and parens
|
|
246
|
+
lparen: "(",
|
|
247
|
+
leftparen: "(",
|
|
248
|
+
openparen: "(",
|
|
249
|
+
leftparenthesis: "(",
|
|
250
|
+
openparenthesis: "(",
|
|
251
|
+
rparen: ")",
|
|
252
|
+
rightparen: ")",
|
|
253
|
+
closeparen: ")",
|
|
254
|
+
rightparenthesis: ")",
|
|
255
|
+
closeparenthesis: ")",
|
|
256
|
+
lbracket: "[",
|
|
257
|
+
leftbracket: "[",
|
|
258
|
+
openbracket: "[",
|
|
259
|
+
leftsquarebracket: "[",
|
|
260
|
+
opensquarebracket: "[",
|
|
261
|
+
rbracket: "]",
|
|
262
|
+
rightbracket: "]",
|
|
263
|
+
closebracket: "]",
|
|
264
|
+
rightsquarebracket: "]",
|
|
265
|
+
closesquarebracket: "]",
|
|
266
|
+
lbrace: "{",
|
|
267
|
+
leftbrace: "{",
|
|
268
|
+
openbrace: "{",
|
|
269
|
+
leftcurlybrace: "{",
|
|
270
|
+
opencurlybrace: "{",
|
|
271
|
+
rbrace: "}",
|
|
272
|
+
rightbrace: "}",
|
|
273
|
+
closebrace: "}",
|
|
274
|
+
rightcurlybrace: "}",
|
|
275
|
+
closecurlybrace: "}",
|
|
276
|
+
|
|
277
|
+
# Quotes
|
|
278
|
+
backtick: "`",
|
|
279
|
+
grave: "`",
|
|
280
|
+
singlequote: "'",
|
|
281
|
+
apostrophe: "'",
|
|
282
|
+
doublequote: "\"",
|
|
283
|
+
}.freeze
|
|
284
|
+
|
|
285
|
+
# Generate predicate methods at load time (faster than method_missing)
|
|
286
|
+
PUNCTUATION_NAMES.each do |name, char|
|
|
287
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
288
|
+
def #{name}?
|
|
289
|
+
@code == #{char.inspect}
|
|
290
|
+
end
|
|
291
|
+
RUBY
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# quote? matches both single and double quotes
|
|
295
|
+
def quote?
|
|
296
|
+
@code == "'" || @code == "\""
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
require_relative "key/character"
|
|
9
|
+
require_relative "key/dwim"
|
|
9
10
|
require_relative "key/media"
|
|
10
11
|
require_relative "key/modifier"
|
|
11
12
|
require_relative "key/navigation"
|
|
@@ -88,6 +89,7 @@ module RatatuiRuby
|
|
|
88
89
|
# These keys will not work in Terminal.app, iTerm2, or GNOME Terminal.
|
|
89
90
|
class Key < Event
|
|
90
91
|
include Character
|
|
92
|
+
include Dwim
|
|
91
93
|
include Media
|
|
92
94
|
include Modifier
|
|
93
95
|
include Navigation
|
|
@@ -435,6 +437,13 @@ module RatatuiRuby
|
|
|
435
437
|
normalized_code = @code.delete("_")
|
|
436
438
|
return true if normalized_predicate == normalized_code && @modifiers.empty?
|
|
437
439
|
|
|
440
|
+
# DWIM: Underscore variants delegate to existing methods
|
|
441
|
+
# space_bar? → spacebar? → space?, sig_int? → sigint?
|
|
442
|
+
normalized_method = :"#{normalized_predicate}?"
|
|
443
|
+
if normalized_method != name && respond_to?(normalized_method)
|
|
444
|
+
return public_send(normalized_method)
|
|
445
|
+
end
|
|
446
|
+
|
|
438
447
|
false
|
|
439
448
|
else
|
|
440
449
|
super
|
|
@@ -253,6 +253,39 @@ module RatatuiRuby
|
|
|
253
253
|
else false
|
|
254
254
|
end
|
|
255
255
|
end
|
|
256
|
+
|
|
257
|
+
alias wheel_up? scroll_up?
|
|
258
|
+
alias wheel_down? scroll_down?
|
|
259
|
+
|
|
260
|
+
# Returns true for any scroll event.
|
|
261
|
+
#
|
|
262
|
+
# event.scroll? # => true for scroll_up or scroll_down
|
|
263
|
+
def scroll?
|
|
264
|
+
scroll_up? || scroll_down?
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
alias primary? left?
|
|
268
|
+
alias secondary? right?
|
|
269
|
+
alias context_menu? right?
|
|
270
|
+
alias aux? middle?
|
|
271
|
+
alias auxiliary? aux?
|
|
272
|
+
|
|
273
|
+
# Returns true for mouse movement without button press.
|
|
274
|
+
#
|
|
275
|
+
# event.moved? # => true for moved (no button)
|
|
276
|
+
def moved?
|
|
277
|
+
@kind == "moved"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
alias hover? moved?
|
|
281
|
+
alias hovering? moved?
|
|
282
|
+
alias move? moved?
|
|
283
|
+
alias dragging? drag?
|
|
284
|
+
|
|
285
|
+
alias release? up?
|
|
286
|
+
alias released? up?
|
|
287
|
+
alias press? down?
|
|
288
|
+
alias pressed? down?
|
|
256
289
|
end
|
|
257
290
|
end
|
|
258
291
|
end
|
|
@@ -67,6 +67,9 @@ module RatatuiRuby
|
|
|
67
67
|
def paste?
|
|
68
68
|
true
|
|
69
69
|
end
|
|
70
|
+
alias clipboard? paste?
|
|
71
|
+
alias pasteboard? paste?
|
|
72
|
+
alias pasted? paste?
|
|
70
73
|
|
|
71
74
|
# Creates a new Paste event.
|
|
72
75
|
#
|
|
@@ -100,6 +103,28 @@ module RatatuiRuby
|
|
|
100
103
|
return false unless other.is_a?(Paste)
|
|
101
104
|
content == other.content
|
|
102
105
|
end
|
|
106
|
+
|
|
107
|
+
# Returns true if the pasted content is empty.
|
|
108
|
+
def empty?
|
|
109
|
+
@content.empty?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns true if the pasted content is empty or whitespace-only.
|
|
113
|
+
def blank?
|
|
114
|
+
@content.strip.empty?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns true if the pasted content spans multiple lines.
|
|
118
|
+
def multiline?
|
|
119
|
+
@content.include?("\n")
|
|
120
|
+
end
|
|
121
|
+
alias multi_line? multiline?
|
|
122
|
+
|
|
123
|
+
# Returns true if the pasted content is a single line.
|
|
124
|
+
def single_line?
|
|
125
|
+
!multiline?
|
|
126
|
+
end
|
|
127
|
+
alias singleline? single_line?
|
|
103
128
|
end
|
|
104
129
|
end
|
|
105
130
|
end
|
|
@@ -151,6 +151,71 @@ module RatatuiRuby
|
|
|
151
151
|
else false
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
|
+
|
|
155
|
+
# Returns true. Unix <tt>SIGWINCH</tt> triggers terminal resize.
|
|
156
|
+
#
|
|
157
|
+
# event.sigwinch? # => true
|
|
158
|
+
def sigwinch?
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
alias winch? sigwinch?
|
|
163
|
+
alias sig_winch? sigwinch?
|
|
164
|
+
|
|
165
|
+
alias terminal_resize? resize?
|
|
166
|
+
alias window_resize? resize?
|
|
167
|
+
alias window_change? resize?
|
|
168
|
+
alias viewport_resize? resize?
|
|
169
|
+
alias viewport_change? resize?
|
|
170
|
+
alias size_change? resize?
|
|
171
|
+
alias resized? resize?
|
|
172
|
+
|
|
173
|
+
VT100_WIDTH = 80 # :nodoc:
|
|
174
|
+
VT100_HEIGHT = 24 # :nodoc:
|
|
175
|
+
|
|
176
|
+
# Returns true if width exceeds height.
|
|
177
|
+
#
|
|
178
|
+
# Event::Resize.new(width: 120, height: 24).landscape? # => true
|
|
179
|
+
def landscape?
|
|
180
|
+
@width > @height
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Returns true if height exceeds or equals width.
|
|
184
|
+
#
|
|
185
|
+
# Event::Resize.new(width: 40, height: 80).portrait? # => true
|
|
186
|
+
def portrait?
|
|
187
|
+
@height >= @width
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Returns true if dimensions are exactly 80x24.
|
|
191
|
+
#
|
|
192
|
+
# Event::Resize.new(width: 80, height: 24).vt100? # => true
|
|
193
|
+
def vt100?
|
|
194
|
+
@width == VT100_WIDTH && @height == VT100_HEIGHT
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Returns true if both dimensions meet or exceed 80x24.
|
|
198
|
+
#
|
|
199
|
+
# Event::Resize.new(width: 120, height: 40).at_least_vt100? # => true
|
|
200
|
+
def at_least_vt100?
|
|
201
|
+
@width >= VT100_WIDTH && @height >= VT100_HEIGHT
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Returns true if both dimensions exceed 80x24.
|
|
205
|
+
#
|
|
206
|
+
# Event::Resize.new(width: 81, height: 25).over_vt100? # => true
|
|
207
|
+
def over_vt100?
|
|
208
|
+
@width > VT100_WIDTH && @height > VT100_HEIGHT
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Returns true if either dimension falls below VT100 standard.
|
|
212
|
+
#
|
|
213
|
+
# Event::Resize.new(width: 60, height: 24).cramped? # => true
|
|
214
|
+
def cramped?
|
|
215
|
+
@width < VT100_WIDTH || @height < VT100_HEIGHT
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
alias constrained? cramped?
|
|
154
219
|
end
|
|
155
220
|
end
|
|
156
221
|
end
|
data/lib/ratatui_ruby/version.rb
CHANGED
data/sig/ratatui_ruby/event.rbs
CHANGED
|
@@ -67,11 +67,46 @@ module RatatuiRuby
|
|
|
67
67
|
private def match_system_dwim?: (String key_name, Symbol key_sym) -> bool
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# DWIM predicates for common key patterns
|
|
71
|
+
module Dwim
|
|
72
|
+
NAVIGATION_KEYS: Array[String]
|
|
73
|
+
ARROW_KEYS: Array[String]
|
|
74
|
+
VIM_MOVEMENT_KEYS: Array[String]
|
|
75
|
+
PUNCTUATION_NAMES: Hash[Symbol, String]
|
|
76
|
+
|
|
77
|
+
def space?: () -> bool
|
|
78
|
+
def cr?: () -> bool
|
|
79
|
+
def letter?: () -> bool
|
|
80
|
+
def digit?: () -> bool
|
|
81
|
+
def alphanumeric?: () -> bool
|
|
82
|
+
def punctuation?: () -> bool
|
|
83
|
+
def whitespace?: () -> bool
|
|
84
|
+
def interrupt?: () -> bool
|
|
85
|
+
def eof?: () -> bool
|
|
86
|
+
def cancel?: () -> bool
|
|
87
|
+
def sigint?: () -> bool
|
|
88
|
+
def suspend?: () -> bool
|
|
89
|
+
def quit?: () -> bool
|
|
90
|
+
def navigation?: () -> bool
|
|
91
|
+
def arrow?: () -> bool
|
|
92
|
+
def vim?: () -> bool
|
|
93
|
+
def vim_left?: () -> bool
|
|
94
|
+
def vim_down?: () -> bool
|
|
95
|
+
def vim_up?: () -> bool
|
|
96
|
+
def vim_right?: () -> bool
|
|
97
|
+
def vim_word_forward?: () -> bool
|
|
98
|
+
def vim_word_backward?: () -> bool
|
|
99
|
+
def vim_top?: () -> bool
|
|
100
|
+
def vim_bottom?: () -> bool
|
|
101
|
+
def quote?: () -> bool
|
|
102
|
+
end
|
|
103
|
+
|
|
70
104
|
include Character
|
|
71
105
|
include Modifier
|
|
72
106
|
include Media
|
|
73
107
|
include Navigation
|
|
74
108
|
include System
|
|
109
|
+
include Dwim
|
|
75
110
|
|
|
76
111
|
attr_reader code: String
|
|
77
112
|
attr_reader modifiers: Array[String]
|
|
@@ -114,6 +149,23 @@ module RatatuiRuby
|
|
|
114
149
|
def left?: () -> bool
|
|
115
150
|
def right?: () -> bool
|
|
116
151
|
def middle?: () -> bool
|
|
152
|
+
def scroll?: () -> bool
|
|
153
|
+
def moved?: () -> bool
|
|
154
|
+
def hover?: () -> bool
|
|
155
|
+
def hovering?: () -> bool
|
|
156
|
+
def move?: () -> bool
|
|
157
|
+
def primary?: () -> bool
|
|
158
|
+
def secondary?: () -> bool
|
|
159
|
+
def context_menu?: () -> bool
|
|
160
|
+
def aux?: () -> bool
|
|
161
|
+
def auxiliary?: () -> bool
|
|
162
|
+
def wheel_up?: () -> bool
|
|
163
|
+
def wheel_down?: () -> bool
|
|
164
|
+
def release?: () -> bool
|
|
165
|
+
def released?: () -> bool
|
|
166
|
+
def press?: () -> bool
|
|
167
|
+
def pressed?: () -> bool
|
|
168
|
+
def dragging?: () -> bool
|
|
117
169
|
def to_sym: () -> Symbol
|
|
118
170
|
def ==: (top other) -> bool
|
|
119
171
|
def deconstruct_keys: (Array[Symbol]?) -> { type: :mouse, kind: String, x: Integer, y: Integer, button: String, modifiers: Array[String] }
|
|
@@ -127,6 +179,20 @@ module RatatuiRuby
|
|
|
127
179
|
def to_sym: () -> Symbol
|
|
128
180
|
def ==: (top other) -> bool
|
|
129
181
|
def deconstruct_keys: (Array[Symbol]?) -> { type: :resize, width: Integer, height: Integer }
|
|
182
|
+
|
|
183
|
+
# DWIM predicates
|
|
184
|
+
VT100_WIDTH: Integer
|
|
185
|
+
VT100_HEIGHT: Integer
|
|
186
|
+
def sigwinch?: () -> bool
|
|
187
|
+
def winch?: () -> bool
|
|
188
|
+
def sig_winch?: () -> bool
|
|
189
|
+
def landscape?: () -> bool
|
|
190
|
+
def portrait?: () -> bool
|
|
191
|
+
def vt100?: () -> bool
|
|
192
|
+
def at_least_vt100?: () -> bool
|
|
193
|
+
def over_vt100?: () -> bool
|
|
194
|
+
def cramped?: () -> bool
|
|
195
|
+
def constrained?: () -> bool
|
|
130
196
|
end
|
|
131
197
|
|
|
132
198
|
class Paste < Event
|
|
@@ -134,14 +200,45 @@ module RatatuiRuby
|
|
|
134
200
|
|
|
135
201
|
def initialize: (content: String) -> void
|
|
136
202
|
def deconstruct_keys: (Array[Symbol]?) -> { type: :paste, content: String }
|
|
203
|
+
|
|
204
|
+
# DWIM predicates
|
|
205
|
+
def clipboard?: () -> bool
|
|
206
|
+
def pasteboard?: () -> bool
|
|
207
|
+
def pasted?: () -> bool
|
|
208
|
+
def empty?: () -> bool
|
|
209
|
+
def blank?: () -> bool
|
|
210
|
+
def multiline?: () -> bool
|
|
211
|
+
def multi_line?: () -> bool
|
|
212
|
+
def single_line?: () -> bool
|
|
213
|
+
def singleline?: () -> bool
|
|
137
214
|
end
|
|
138
215
|
|
|
139
216
|
class FocusGained < Event
|
|
140
217
|
def deconstruct_keys: (Array[Symbol]?) -> { type: :focus_gained }
|
|
218
|
+
|
|
219
|
+
# DWIM predicates
|
|
220
|
+
def focus?: () -> bool
|
|
221
|
+
def gained?: () -> bool
|
|
222
|
+
def lost?: () -> bool
|
|
223
|
+
def blur?: () -> bool
|
|
224
|
+
def active?: () -> bool
|
|
225
|
+
def inactive?: () -> bool
|
|
226
|
+
def foreground?: () -> bool
|
|
227
|
+
def background?: () -> bool
|
|
141
228
|
end
|
|
142
229
|
|
|
143
230
|
class FocusLost < Event
|
|
144
231
|
def deconstruct_keys: (Array[Symbol]?) -> { type: :focus_lost }
|
|
232
|
+
|
|
233
|
+
# DWIM predicates
|
|
234
|
+
def focus?: () -> bool
|
|
235
|
+
def gained?: () -> bool
|
|
236
|
+
def lost?: () -> bool
|
|
237
|
+
def blur?: () -> bool
|
|
238
|
+
def active?: () -> bool
|
|
239
|
+
def inactive?: () -> bool
|
|
240
|
+
def foreground?: () -> bool
|
|
241
|
+
def background?: () -> bool
|
|
145
242
|
end
|
|
146
243
|
|
|
147
244
|
class None < Event
|
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.2
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kerrick Long
|
|
@@ -474,6 +474,7 @@ files:
|
|
|
474
474
|
- lib/ratatui_ruby/event/focus_lost.rb
|
|
475
475
|
- lib/ratatui_ruby/event/key.rb
|
|
476
476
|
- lib/ratatui_ruby/event/key/character.rb
|
|
477
|
+
- lib/ratatui_ruby/event/key/dwim.rb
|
|
477
478
|
- lib/ratatui_ruby/event/key/media.rb
|
|
478
479
|
- lib/ratatui_ruby/event/key/modifier.rb
|
|
479
480
|
- lib/ratatui_ruby/event/key/navigation.rb
|