nandi/jolt-nativepublic Fork 0
0fb017593acce485b16f9756141b599beb49dc76
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.

Paint the same tree into a terminal, for the machines with no window a7f6202 · on 0fb017593acce485b16f9756141b599beb49dc76 · nandi · 17d ago
keys.rs · 105 lines · 3.5 KBRust Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
//! Key names, and the two word motions the entry needs.
//!
//! A key crosses this ABI as a string — `"ctrl+u"`, `"page-down"`, `"a"` —
//! because that is the only shape jolt can match on, and because a name is
//! something a test can type. The same names glimmer-tui's `k/match?` takes.

/// Where the word before `at` starts.
pub fn word_left(text: &[char], at: usize) -> usize {
    let mut i = at.min(text.len());
    while i > 0 && text[i - 1].is_whitespace() {
        i -= 1;
    }
    while i > 0 && !text[i - 1].is_whitespace() {
        i -= 1;
    }
    i
}

/// Where the word after `at` ends.
pub fn word_right(text: &[char], at: usize) -> usize {
    let mut i = at.min(text.len());
    while i < text.len() && text[i].is_whitespace() {
        i += 1;
    }
    while i < text.len() && !text[i].is_whitespace() {
        i += 1;
    }
    i
}

#[cfg(feature = "terminal")]
mod terminal {
    use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

    /// The name for one key event, or `None` for a key with no name — a bare
    /// modifier press, or a media key nothing here would do anything with.
    pub fn name(event: KeyEvent) -> Option<String> {
        let mods = event.modifiers;
        let base = match event.code {
            KeyCode::Char(' ') => "space".to_owned(),
            KeyCode::Char(c) => {
                // Shift is already in the character the terminal sent; saying
                // it twice would make `A` into `shift+a`, which nothing wants
                // to match on.
                let mut name = c.to_string();
                if mods.contains(KeyModifiers::CONTROL) || mods.contains(KeyModifiers::ALT) {
                    name = c.to_lowercase().to_string();
                }
                name
            }
            KeyCode::Enter => "enter".into(),
            KeyCode::Tab => "tab".into(),
            KeyCode::BackTab => return Some("shift+tab".into()),
            KeyCode::Backspace => "backspace".into(),
            KeyCode::Delete => "delete".into(),
            KeyCode::Insert => "insert".into(),
            KeyCode::Esc => "esc".into(),
            KeyCode::Up => "up".into(),
            KeyCode::Down => "down".into(),
            KeyCode::Left => "left".into(),
            KeyCode::Right => "right".into(),
            KeyCode::Home => "home".into(),
            KeyCode::End => "end".into(),
            KeyCode::PageUp => "page-up".into(),
            KeyCode::PageDown => "page-down".into(),
            KeyCode::F(n) => format!("f{n}"),
            _ => return None,
        };
        let mut out = String::new();
        if mods.contains(KeyModifiers::CONTROL) {
            out.push_str("ctrl+");
        }
        if mods.contains(KeyModifiers::ALT) {
            out.push_str("alt+");
        }
        if mods.contains(KeyModifiers::SHIFT) && !matches!(event.code, KeyCode::Char(_)) {
            out.push_str("shift+");
        }
        out.push_str(&base);
        Some(out)
    }
}

#[cfg(feature = "terminal")]
pub use terminal::name;

#[cfg(test)]
mod tests {
    use super::*;

    fn chars(text: &str) -> Vec<char> {
        text.chars().collect()
    }

    #[test]
    fn word_motions_step_over_the_gap_and_then_the_word() {
        let text = chars("one two three");
        assert_eq!(word_left(&text, 13), 8);
        assert_eq!(word_left(&text, 8), 4);
        assert_eq!(word_left(&text, 0), 0);
        assert_eq!(word_right(&text, 0), 3);
        assert_eq!(word_right(&text, 3), 7);
        assert_eq!(word_right(&text, 13), 13);
    }
}