nandi/jolt-nativepublic Fork 0
258bbc5161b93d580a4c84363acabe63b0624e88
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

keys.rs · 105 lines · 3.5 KBRust Blame HistoryRaw
Paint the same tree into a terminal, for the machines with no window a7f6202 nandi 17d ago1//! Key names, and the two word motions the entry needs.
2//!
3//! A key crosses this ABI as a string — `"ctrl+u"`, `"page-down"`, `"a"` —
4//! because that is the only shape jolt can match on, and because a name is
5//! something a test can type. The same names glimmer-tui's `k/match?` takes.
6
7/// Where the word before `at` starts.
8pub fn word_left(text: &[char], at: usize) -> usize {
9 let mut i = at.min(text.len());
10 while i > 0 && text[i - 1].is_whitespace() {
11 i -= 1;
12 }
13 while i > 0 && !text[i - 1].is_whitespace() {
14 i -= 1;
15 }
16 i
17}
18
19/// Where the word after `at` ends.
20pub fn word_right(text: &[char], at: usize) -> usize {
21 let mut i = at.min(text.len());
22 while i < text.len() && text[i].is_whitespace() {
23 i += 1;
24 }
25 while i < text.len() && !text[i].is_whitespace() {
26 i += 1;
27 }
28 i
29}
30
31#[cfg(feature = "terminal")]
32mod terminal {
33 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
34
35 /// The name for one key event, or `None` for a key with no name — a bare
36 /// modifier press, or a media key nothing here would do anything with.
37 pub fn name(event: KeyEvent) -> Option<String> {
38 let mods = event.modifiers;
39 let base = match event.code {
40 KeyCode::Char(' ') => "space".to_owned(),
41 KeyCode::Char(c) => {
42 // Shift is already in the character the terminal sent; saying
43 // it twice would make `A` into `shift+a`, which nothing wants
44 // to match on.
45 let mut name = c.to_string();
46 if mods.contains(KeyModifiers::CONTROL) || mods.contains(KeyModifiers::ALT) {
47 name = c.to_lowercase().to_string();
48 }
49 name
50 }
51 KeyCode::Enter => "enter".into(),
52 KeyCode::Tab => "tab".into(),
53 KeyCode::BackTab => return Some("shift+tab".into()),
54 KeyCode::Backspace => "backspace".into(),
55 KeyCode::Delete => "delete".into(),
56 KeyCode::Insert => "insert".into(),
57 KeyCode::Esc => "esc".into(),
58 KeyCode::Up => "up".into(),
59 KeyCode::Down => "down".into(),
60 KeyCode::Left => "left".into(),
61 KeyCode::Right => "right".into(),
62 KeyCode::Home => "home".into(),
63 KeyCode::End => "end".into(),
64 KeyCode::PageUp => "page-up".into(),
65 KeyCode::PageDown => "page-down".into(),
66 KeyCode::F(n) => format!("f{n}"),
67 _ => return None,
68 };
69 let mut out = String::new();
70 if mods.contains(KeyModifiers::CONTROL) {
71 out.push_str("ctrl+");
72 }
73 if mods.contains(KeyModifiers::ALT) {
74 out.push_str("alt+");
75 }
76 if mods.contains(KeyModifiers::SHIFT) && !matches!(event.code, KeyCode::Char(_)) {
77 out.push_str("shift+");
78 }
79 out.push_str(&base);
80 Some(out)
81 }
82}
83
84#[cfg(feature = "terminal")]
85pub use terminal::name;
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn chars(text: &str) -> Vec<char> {
92 text.chars().collect()
93 }
94
95 #[test]
96 fn word_motions_step_over_the_gap_and_then_the_word() {
97 let text = chars("one two three");
98 assert_eq!(word_left(&text, 13), 8);
99 assert_eq!(word_left(&text, 8), 4);
100 assert_eq!(word_left(&text, 0), 0);
101 assert_eq!(word_right(&text, 0), 3);
102 assert_eq!(word_right(&text, 3), 7);
103 assert_eq!(word_right(&text, 13), 13);
104 }
105}