input: kitty keyboard protocol and hex codepoint entry

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I0f58c82752b9d7a8df35fe78f034c0be6a6a6964
This commit is contained in:
raf 2026-06-25 15:00:39 +03:00
commit e04ffc6649
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
8 changed files with 544 additions and 15 deletions

View file

@ -249,6 +249,10 @@ pub struct Grid {
last_base: Option<(usize, usize)>,
/// OSC 8 hyperlink URIs; a cell's `link` is a 1-based index into this.
links: Vec<Box<str>>,
/// Active kitty-keyboard progressive-enhancement flags (0 = legacy mode).
kitty_current: u8,
/// Saved flag values for the kitty push/pop stack.
kitty_stack: Vec<u8>,
}
fn default_tabs(cols: usize) -> Vec<bool> {
@ -261,6 +265,10 @@ fn default_tabs(cols: usize) -> Vec<bool> {
/// one unit.
const WORD_DELIMITERS: &str = " \t`!@#$%^&*()+=[]{}\\|;'\",<>?";
/// Mask of the kitty-keyboard flags we implement (disambiguate, report events,
/// alternate keys, all-keys-as-escapes, associated text).
const KITTY_ALL: u8 = 0b1_1111;
/// Whether `c` is part of a word (not whitespace, not in `delims`).
fn is_word(c: char, delims: &str) -> bool {
!c.is_whitespace() && !delims.contains(c)
@ -304,6 +312,38 @@ impl Grid {
scrollback_cap: SCROLLBACK_CAP,
last_base: None,
links: Vec::new(),
kitty_current: 0,
kitty_stack: Vec::new(),
}
}
/// The active kitty-keyboard flags (0 means legacy encoding).
pub fn kitty_flags(&self) -> u8 {
self.kitty_current
}
/// Apply `CSI = flags ; mode u`: mode 1 replaces, 2 sets bits, 3 clears bits.
pub fn kitty_set(&mut self, flags: u8, mode: u8) {
self.kitty_current = match mode {
2 => self.kitty_current | flags,
3 => self.kitty_current & !flags,
_ => flags,
} & KITTY_ALL;
}
/// Apply `CSI > flags u`: push the current flags and switch to `flags`.
pub fn kitty_push(&mut self, flags: u8) {
if self.kitty_stack.len() >= 32 {
self.kitty_stack.remove(0);
}
self.kitty_stack.push(self.kitty_current);
self.kitty_current = flags & KITTY_ALL;
}
/// Apply `CSI < n u`: pop `n` saved flag values, restoring the last one.
pub fn kitty_pop(&mut self, n: usize) {
for _ in 0..n.max(1) {
self.kitty_current = self.kitty_stack.pop().unwrap_or(0);
}
}
@ -1170,6 +1210,24 @@ impl Grid {
mod tests {
use super::*;
#[test]
fn kitty_flag_stack() {
let mut g = Grid::new(4, 2);
assert_eq!(g.kitty_flags(), 0);
g.kitty_set(0b1, 1); // replace
assert_eq!(g.kitty_flags(), 0b1);
g.kitty_set(0b100, 2); // set bits
assert_eq!(g.kitty_flags(), 0b101);
g.kitty_set(0b1, 3); // clear bits
assert_eq!(g.kitty_flags(), 0b100);
g.kitty_push(0b11); // push current, switch
assert_eq!(g.kitty_flags(), 0b11);
g.kitty_pop(1); // restore
assert_eq!(g.kitty_flags(), 0b100);
g.kitty_pop(5); // underflow is harmless
assert_eq!(g.kitty_flags(), 0);
}
#[test]
fn prints_and_wraps() {
let mut g = Grid::new(4, 2);