const DISAMBIGUATE: u16 = 1;
const SUPPORTED: u16 = DISAMBIGUATE;
const DEPTH: usize = 16;
#[derive(Debug, Default)]
pub(crate) struct Keyboard {
main: Vec<u16>,
alternate: Vec<u16>,
replies: Vec<u8>,
}
impl Keyboard {
pub(crate) fn disambiguates(&self, alternate: bool) -> bool {
self.flags(alternate) & DISAMBIGUATE != 0
}
pub(crate) fn take_replies(&mut self) -> Vec<u8> {
std::mem::take(&mut self.replies)
}
pub(crate) fn csi(&mut self, alternate: bool, marker: Option<u8>, params: &[&[u16]], end: char) {
if end != 'u' {
return;
}
let param = |index: usize| params.get(index).and_then(|param| param.first()).copied().unwrap_or(0);
match marker {
Some(b'?') => {
let flags = self.flags(alternate);
self.replies.extend_from_slice(format!("\x1b[?{flags}u").as_bytes());
}
Some(b'>') => {
let stack = self.stack(alternate);
if stack.len() == DEPTH {
stack.remove(0);
}
stack.push(param(0) & SUPPORTED);
}
Some(b'<') => {
let stack = self.stack(alternate);
let count = usize::from(param(0).max(1)).min(stack.len());
stack.truncate(stack.len() - count);
}
Some(b'=') => {
let flags = param(0) & SUPPORTED;
let mode = param(1).max(1);
let stack = self.stack(alternate);
if stack.is_empty() {
stack.push(0);
}
if let Some(top) = stack.last_mut() {
*top = match mode {
2 => *top | flags,
3 => *top & !flags,
_ => flags,
};
}
}
_ => {}
}
}
pub(crate) fn pushed(&self, alternate: bool) -> &[u16] {
if alternate { &self.alternate } else { &self.main }
}
fn flags(&self, alternate: bool) -> u16 {
let stack = if alternate { &self.alternate } else { &self.main };
stack.last().copied().unwrap_or(0)
}
fn stack(&mut self, alternate: bool) -> &mut Vec<u16> {
if alternate { &mut self.alternate } else { &mut self.main }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn after(bytes: &[u8]) -> (Keyboard, bool) {
let mut parser = vt100::Parser::new_with_callbacks(4, 40, 0, Probe::default());
parser.process(bytes);
let alternate = parser.screen().alternate_screen();
(std::mem::take(&mut parser.callbacks_mut().0), alternate)
}
#[derive(Default)]
struct Probe(Keyboard);
impl vt100::Callbacks for Probe {
fn unhandled_csi(
&mut self,
screen: &mut vt100::Screen,
i1: Option<u8>,
_: Option<u8>,
params: &[&[u16]],
end: char,
) {
self.0.csi(screen.alternate_screen(), i1, params, end);
}
}
#[test]
fn push_pop_and_query() {
let (mut keyboard, _) = after(b"\x1b[?u");
assert_eq!(keyboard.take_replies(), b"\x1b[?0u", "a terminal that knows the protocol answers");
assert!(!keyboard.disambiguates(false));
let (mut keyboard, _) = after(b"\x1b[>1u\x1b[?u");
assert!(keyboard.disambiguates(false));
assert_eq!(keyboard.take_replies(), b"\x1b[?1u");
assert!(keyboard.take_replies().is_empty(), "an answer is written once");
let (keyboard, _) = after(b"\x1b[>1u\x1b[<u");
assert!(!keyboard.disambiguates(false), "a pop without a count takes one entry");
let (keyboard, _) = after(b"\x1b[>1u\x1b[>0u\x1b[<u");
assert!(keyboard.disambiguates(false), "the entry below comes back");
let (keyboard, _) = after(b"\x1b[>1u\x1b[<9u");
assert!(!keyboard.disambiguates(false), "popping past the bottom empties the stack");
}
#[test]
fn only_the_flags_acted_on_are_kept_and_reported() {
let (mut keyboard, _) = after(b"\x1b[>31u\x1b[?u");
assert_eq!(keyboard.take_replies(), b"\x1b[?1u");
let (mut keyboard, _) = after(b"\x1b[>8u\x1b[?u");
assert_eq!(keyboard.take_replies(), b"\x1b[?0u");
}
#[test]
fn setting_changes_the_top_entry() {
let (keyboard, _) = after(b"\x1b[=1u");
assert!(keyboard.disambiguates(false), "mode 1 sets");
let (keyboard, _) = after(b"\x1b[>1u\x1b[=1;3u");
assert!(!keyboard.disambiguates(false), "mode 3 clears");
let (keyboard, _) = after(b"\x1b[>0u\x1b[=1;2u");
assert!(keyboard.disambiguates(false), "mode 2 adds");
}
#[test]
fn each_screen_has_its_own_stack() {
let (keyboard, alternate) = after(b"\x1b[?1049h\x1b[>1u");
assert!(alternate);
assert!(keyboard.disambiguates(true));
assert!(!keyboard.disambiguates(false), "the main screen's flags are untouched");
let (keyboard, alternate) = after(b"\x1b[?1049h\x1b[>1u\x1b[?1049l");
assert!(!alternate);
assert!(!keyboard.disambiguates(false), "back on the main screen its own flags apply");
}
#[test]
fn a_stack_pushed_without_end_stays_bounded() {
let (keyboard, _) = after(&b"\x1b[>1u".repeat(DEPTH * 4));
assert_eq!(keyboard.main.len(), DEPTH);
}
#[test]
fn other_sequences_are_left_alone() {
let (mut keyboard, _) = after(b"\x1b[?25l\x1b[>1c\x1b[u");
assert!(keyboard.take_replies().is_empty());
assert!(!keyboard.disambiguates(false));
}
}