use crate::{
emulator::Emulator,
protocol::{Key, Mods, MouseKind},
};
const PASTE_END: &[u8] = b"\x1b[201~";
pub fn paste_bytes(bracketed: bool, content: &[u8]) -> Vec<u8> {
if bracketed {
let mut out = Vec::with_capacity(content.len() + 2 * PASTE_END.len() + 6);
out.extend_from_slice(b"\x1b[200~");
let mut rest = content;
while let Some(pos) = rest.windows(PASTE_END.len()).position(|w| w == PASTE_END) {
out.extend_from_slice(&rest[..pos]);
rest = &rest[pos + PASTE_END.len()..];
}
out.extend_from_slice(rest);
out.extend_from_slice(PASTE_END);
out
} else {
let mut out = Vec::with_capacity(content.len());
let mut i = 0;
while i < content.len() {
if content[i] == b'\r' && content.get(i + 1) == Some(&b'\n') {
out.push(b'\r');
i += 2;
} else if content[i] == b'\n' {
out.push(b'\r');
i += 1;
} else {
out.push(content[i]);
i += 1;
}
}
out
}
}
pub fn mouse_bytes(emu: &Emulator, kind: MouseKind, col: u16, row: u16) -> Option<Vec<u8>> {
use crate::emulator::{MouseProtocolEncoding, MouseProtocolMode};
let mode = emu.mouse_protocol_mode();
if mode != MouseProtocolMode::None {
if matches!(kind, MouseKind::Drag(_))
&& !matches!(
mode,
MouseProtocolMode::ButtonMotion | MouseProtocolMode::AnyMotion
)
{
return None;
}
let code: u16 = match kind {
MouseKind::WheelUp => 64,
MouseKind::WheelDown => 65,
MouseKind::Press(b) | MouseKind::Release(b) => b as u16,
MouseKind::Drag(b) => 32 + b as u16,
};
let release = matches!(kind, MouseKind::Release(_));
return Some(match emu.mouse_protocol_encoding() {
MouseProtocolEncoding::Sgr => {
let suffix = if release { 'm' } else { 'M' };
format!("\x1b[<{};{};{}{}", code, col + 1, row + 1, suffix).into_bytes()
}
MouseProtocolEncoding::Utf8 => {
let code = if release { 3 } else { code };
let mut out = b"\x1b[M".to_vec();
for v in [32 + code, 33 + col.min(2014), 33 + row.min(2014)] {
let mut buf = [0u8; 4];
let c = char::from_u32(u32::from(v)).unwrap_or(' ');
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
out
}
MouseProtocolEncoding::Default => {
let code = if release { 3 } else { code };
vec![
0x1b,
b'[',
b'M',
32 + code as u8,
(33 + col.min(222)) as u8,
(33 + row.min(222)) as u8,
]
}
});
}
if emu.alternate_scroll() {
let up = match kind {
MouseKind::WheelUp => true,
MouseKind::WheelDown => false,
_ => return None,
};
let arrow: &[u8] = match (emu.application_cursor(), up) {
(true, true) => b"\x1bOA",
(true, false) => b"\x1bOB",
(false, true) => b"\x1b[A",
(false, false) => b"\x1b[B",
};
return Some(arrow.repeat(3));
}
None
}
fn ctrl_byte(c: char) -> Option<u8> {
if c.is_ascii_alphabetic() {
return Some((c.to_ascii_uppercase() as u8) & 0x1f);
}
Some(match c {
' ' | '@' | '2' => 0x00,
'[' | '3' => 0x1b,
'\\' | '4' => 0x1c,
']' | '5' => 0x1d,
'^' | '6' => 0x1e,
'_' | '7' | '/' => 0x1f,
'?' | '8' => 0x7f,
_ => return None,
})
}
fn char_bytes(c: char, mods: Mods) -> Option<Vec<u8>> {
let mut out = if mods.ctrl {
vec![ctrl_byte(c)?]
} else {
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf).as_bytes().to_vec()
};
if mods.alt {
out.insert(0, 0x1b);
}
Some(out)
}
fn f_bytes(n: u8, m: Option<u8>) -> Option<Vec<u8>> {
if let Some(letter) = match n {
1 => Some('P'),
2 => Some('Q'),
3 => Some('R'),
4 => Some('S'),
_ => None,
} {
return Some(match m {
None => format!("\x1bO{letter}").into_bytes(),
Some(m) => format!("\x1b[1;{m}{letter}").into_bytes(),
});
}
let code = match n {
5 => 15,
6 => 17,
7 => 18,
8 => 19,
9 => 20,
10 => 21,
11 => 23,
12 => 24,
_ => return None,
};
Some(match m {
None => format!("\x1b[{code}~").into_bytes(),
Some(m) => format!("\x1b[{code};{m}~").into_bytes(),
})
}
pub fn key_bytes(app_cursor: bool, code: Key, mods: Mods) -> Option<Vec<u8>> {
let m = mods.param();
match code {
Key::Char(c) => char_bytes(c, mods),
Key::F(n) => f_bytes(n, m),
Key::Up | Key::Down | Key::Left | Key::Right | Key::Home | Key::End => {
let letter = match code {
Key::Up => 'A',
Key::Down => 'B',
Key::Right => 'C',
Key::Left => 'D',
Key::Home => 'H',
Key::End => 'F',
_ => unreachable!(),
};
Some(match m {
None if app_cursor => format!("\x1bO{letter}").into_bytes(),
None => format!("\x1b[{letter}").into_bytes(),
Some(m) => format!("\x1b[1;{m}{letter}").into_bytes(),
})
}
Key::Insert | Key::Delete | Key::PageUp | Key::PageDown => {
let n = match code {
Key::Insert => 2,
Key::Delete => 3,
Key::PageUp => 5,
Key::PageDown => 6,
_ => unreachable!(),
};
Some(match m {
None => format!("\x1b[{n}~").into_bytes(),
Some(m) => format!("\x1b[{n};{m}~").into_bytes(),
})
}
Key::Enter => Some(if mods.shift || mods.alt {
vec![0x1b, 0x0d]
} else {
vec![0x0d]
}),
Key::Tab => Some(if mods.alt {
vec![0x1b, 0x09]
} else {
vec![0x09]
}),
Key::BackTab => Some(if mods.alt {
b"\x1b\x1b[Z".to_vec()
} else {
b"\x1b[Z".to_vec()
}),
Key::Backspace => Some(if mods.alt {
vec![0x1b, 0x7f]
} else {
vec![0x7f]
}),
Key::Esc => Some(if mods.alt {
vec![0x1b, 0x1b]
} else {
vec![0x1b]
}),
}
}
#[cfg(test)]
#[path = "input_tests.rs"]
mod tests;