forked from NotAShelf/beer
vt: answer XTVERSION, DA3, DECRQM, and the title stack
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ib74e56cfd26b029064e5683ba20b70606a6a6964
This commit is contained in:
parent
56907b4115
commit
6b3c8dc059
2 changed files with 125 additions and 9 deletions
16
src/grid.rs
16
src/grid.rs
|
|
@ -177,6 +177,22 @@ impl Grid {
|
||||||
self.insert = on;
|
self.insert = on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn autowrap(&self) -> bool {
|
||||||
|
self.autowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin(&self) -> bool {
|
||||||
|
self.origin
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self) -> bool {
|
||||||
|
self.insert
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alt_active(&self) -> bool {
|
||||||
|
self.alt_saved.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
// --- printing ---
|
// --- printing ---
|
||||||
|
|
||||||
/// Place a printable character at the cursor, honouring width and autowrap.
|
/// Place a printable character at the cursor, honouring width and autowrap.
|
||||||
|
|
|
||||||
118
src/vt.rs
118
src/vt.rs
|
|
@ -13,11 +13,25 @@ enum Charset {
|
||||||
DecSpecial,
|
DecSpecial,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Which device-attributes query is being answered.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum DaLevel {
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
Tertiary,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DECRQM mode-state code: 1 = set, 2 = reset.
|
||||||
|
fn set_reset(on: bool) -> u8 {
|
||||||
|
if on { 1 } else { 2 }
|
||||||
|
}
|
||||||
|
|
||||||
/// The terminal model: a grid plus the escape-sequence state around it.
|
/// The terminal model: a grid plus the escape-sequence state around it.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Term {
|
pub struct Term {
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
|
title_stack: Vec<Option<String>>,
|
||||||
response: Vec<u8>,
|
response: Vec<u8>,
|
||||||
g0: Charset,
|
g0: Charset,
|
||||||
g1: Charset,
|
g1: Charset,
|
||||||
|
|
@ -29,6 +43,7 @@ impl Term {
|
||||||
Self {
|
Self {
|
||||||
grid: Grid::new(cols, rows),
|
grid: Grid::new(cols, rows),
|
||||||
title: None,
|
title: None,
|
||||||
|
title_stack: Vec::new(),
|
||||||
response: Vec::new(),
|
response: Vec::new(),
|
||||||
g0: Charset::Ascii,
|
g0: Charset::Ascii,
|
||||||
g1: Charset::Ascii,
|
g1: Charset::Ascii,
|
||||||
|
|
@ -141,16 +156,25 @@ impl Term {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_attrs(&mut self, secondary: bool) {
|
/// Device attributes. DA1 claims a VT220 with ANSI colour; DA2 a generic
|
||||||
// Claim a VT220 with ANSI colour (62;22) for DA1, and a generic
|
/// firmware level; DA3 a (zero) unit ID.
|
||||||
// firmware level for DA2.
|
fn device_attrs(&mut self, level: DaLevel) {
|
||||||
if secondary {
|
match level {
|
||||||
self.response.extend_from_slice(b"\x1b[>0;276;0c");
|
DaLevel::Primary => self.response.extend_from_slice(b"\x1b[?62;22c"),
|
||||||
} else {
|
DaLevel::Secondary => self.response.extend_from_slice(b"\x1b[>0;276;0c"),
|
||||||
self.response.extend_from_slice(b"\x1b[?62;22c");
|
DaLevel::Tertiary => self.response.extend_from_slice(b"\x1bP!|00000000\x1b\\"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// XTVERSION (`CSI > q`): report the terminal name and version.
|
||||||
|
fn report_version(&mut self) {
|
||||||
|
let _ = write!(
|
||||||
|
self.response,
|
||||||
|
"\x1bP>|beer({})\x1b\\",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn device_status(&mut self, params: &Params) {
|
fn device_status(&mut self, params: &Params) {
|
||||||
match params.iter().next().and_then(|p| p.first().copied()) {
|
match params.iter().next().and_then(|p| p.first().copied()) {
|
||||||
Some(5) => self.response.extend_from_slice(b"\x1b[0n"),
|
Some(5) => self.response.extend_from_slice(b"\x1b[0n"),
|
||||||
|
|
@ -161,6 +185,34 @@ impl Term {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DECRQM (`CSI [?] Ps $ p`): report whether a mode is set (1), reset (2),
|
||||||
|
/// or unrecognized (0). Only the modes we actually track are reported.
|
||||||
|
fn report_mode(&mut self, params: &Params, private: bool) {
|
||||||
|
let code = raw(params, 0);
|
||||||
|
let state = match (private, code) {
|
||||||
|
(true, 6) => set_reset(self.grid.origin()),
|
||||||
|
(true, 7) => set_reset(self.grid.autowrap()),
|
||||||
|
(true, 47 | 1047 | 1049) => set_reset(self.grid.alt_active()),
|
||||||
|
(false, 4) => set_reset(self.grid.insert()),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
let prefix = if private { "?" } else { "" };
|
||||||
|
let _ = write!(self.response, "\x1b[{prefix}{code};{state}$y");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Title stack (`CSI 22/23 ; Ps t`): push or pop the window title.
|
||||||
|
fn title_stack_op(&mut self, params: &Params) {
|
||||||
|
match raw(params, 0) {
|
||||||
|
22 => self.title_stack.push(self.title.clone()),
|
||||||
|
23 => {
|
||||||
|
if let Some(title) = self.title_stack.pop() {
|
||||||
|
self.title = title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// First param value, with 0/absent folded to `default` (xterm convention for
|
/// First param value, with 0/absent folded to `default` (xterm convention for
|
||||||
|
|
@ -291,7 +343,6 @@ impl Perform for Term {
|
||||||
|
|
||||||
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, action: char) {
|
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, action: char) {
|
||||||
let private = intermediates.first() == Some(&b'?');
|
let private = intermediates.first() == Some(&b'?');
|
||||||
let secondary = intermediates.first() == Some(&b'>');
|
|
||||||
match action {
|
match action {
|
||||||
'A' => self.grid.cursor_up(n(params, 0, 1)),
|
'A' => self.grid.cursor_up(n(params, 0, 1)),
|
||||||
'B' | 'e' => self.grid.cursor_down(n(params, 0, 1)),
|
'B' | 'e' => self.grid.cursor_down(n(params, 0, 1)),
|
||||||
|
|
@ -328,10 +379,17 @@ impl Perform for Term {
|
||||||
}
|
}
|
||||||
'h' => self.set_mode(params, private, true),
|
'h' => self.set_mode(params, private, true),
|
||||||
'l' => self.set_mode(params, private, false),
|
'l' => self.set_mode(params, private, false),
|
||||||
'c' => self.device_attrs(secondary),
|
'c' => self.device_attrs(match intermediates.first() {
|
||||||
|
Some(b'>') => DaLevel::Secondary,
|
||||||
|
Some(b'=') => DaLevel::Tertiary,
|
||||||
|
_ => DaLevel::Primary,
|
||||||
|
}),
|
||||||
|
'q' if intermediates.first() == Some(&b'>') => self.report_version(),
|
||||||
|
'p' if intermediates.contains(&b'$') => self.report_mode(params, private),
|
||||||
'n' => self.device_status(params),
|
'n' => self.device_status(params),
|
||||||
's' => self.grid.save_cursor(),
|
's' => self.grid.save_cursor(),
|
||||||
'u' => self.grid.restore_cursor(),
|
'u' => self.grid.restore_cursor(),
|
||||||
|
't' => self.title_stack_op(params),
|
||||||
'g' => match raw(params, 0) {
|
'g' => match raw(params, 0) {
|
||||||
3 => self.grid.clear_all_tabs(),
|
3 => self.grid.clear_all_tabs(),
|
||||||
_ => self.grid.clear_tab(),
|
_ => self.grid.clear_tab(),
|
||||||
|
|
@ -412,6 +470,48 @@ mod tests {
|
||||||
assert_eq!(t.grid().row_text(1), "two");
|
assert_eq!(t.grid().row_text(1), "two");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_attributes_levels() {
|
||||||
|
let mut t = Term::new(20, 4);
|
||||||
|
feed(&mut t, b"\x1b[c");
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[?62;22c");
|
||||||
|
feed(&mut t, b"\x1b[>c");
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[>0;276;0c");
|
||||||
|
feed(&mut t, b"\x1b[=c");
|
||||||
|
assert_eq!(t.take_response(), b"\x1bP!|00000000\x1b\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn xtversion_reports_name() {
|
||||||
|
let mut t = Term::new(20, 4);
|
||||||
|
feed(&mut t, b"\x1b[>q");
|
||||||
|
let resp = t.take_response();
|
||||||
|
assert!(resp.starts_with(b"\x1bP>|beer("));
|
||||||
|
assert!(resp.ends_with(b")\x1b\\"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decrqm_reports_known_modes() {
|
||||||
|
let mut t = Term::new(20, 4);
|
||||||
|
feed(&mut t, b"\x1b[?7$p"); // autowrap, on by default
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[?7;1$y");
|
||||||
|
feed(&mut t, b"\x1b[?7l\x1b[?7$p"); // turn it off, re-query
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[?7;2$y");
|
||||||
|
feed(&mut t, b"\x1b[?9999$p"); // unknown mode
|
||||||
|
assert_eq!(t.take_response(), b"\x1b[?9999;0$y");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn title_stack_push_pop() {
|
||||||
|
let mut t = Term::new(20, 4);
|
||||||
|
feed(&mut t, b"\x1b]0;first\x07");
|
||||||
|
feed(&mut t, b"\x1b[22t"); // push "first"
|
||||||
|
feed(&mut t, b"\x1b]0;second\x07");
|
||||||
|
assert_eq!(t.title(), Some("second"));
|
||||||
|
feed(&mut t, b"\x1b[23t"); // pop -> "first"
|
||||||
|
assert_eq!(t.title(), Some("first"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sgr_sets_pen_colours() {
|
fn sgr_sets_pen_colours() {
|
||||||
let mut t = Term::new(20, 1);
|
let mut t = Term::new(20, 1);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue