diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c477958..90e97afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix propagation of plugin ui request (https://github.com/zellij-org/zellij/pull/495) * Handle pasted text properly (https://github.com/zellij-org/zellij/pull/494) * Fix default keybinds for tab -> resize mode (https://github.com/zellij-org/zellij/pull/497) +* Terminal compatibility: device reports (https://github.com/zellij-org/zellij/pull/500) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs index c59e279b..e51b1ed5 100644 --- a/src/client/panes/grid.rs +++ b/src/client/panes/grid.rs @@ -9,7 +9,9 @@ use vte::{Params, Perform}; const TABSTOP_WIDTH: usize = 8; // TODO: is this always right? const SCROLL_BACK: usize = 10_000; +use crate::utils::consts::VERSION; use crate::utils::logging::debug_log_to_file; +use crate::utils::shared::version_number; use crate::panes::terminal_character::{ CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter, @@ -186,6 +188,7 @@ pub struct Grid { pub clear_viewport_before_rendering: bool, pub width: usize, pub height: usize, + pub pending_messages_to_pty: Vec>, } impl Debug for Grid { @@ -222,6 +225,7 @@ impl Grid { alternative_lines_above_viewport_and_cursor: None, clear_viewport_before_rendering: false, active_charset: Default::default(), + pending_messages_to_pty: vec![], } } pub fn contains_widechar(&self) -> bool { @@ -1283,6 +1287,64 @@ impl Perform for Grid { for _ in 0..next_param_or(1) { self.move_to_previous_tabstop(); } + } else if c == 'c' { + // identify terminal + // https://vt100.net/docs/vt510-rm/DA1.html + match intermediates.get(0) { + None | Some(0) => { + // primary device attributes + let terminal_capabilities = "\u{1b}[?6c"; + self.pending_messages_to_pty + .push(terminal_capabilities.as_bytes().to_vec()); + } + Some(b'>') => { + // secondary device attributes + let version = version_number(VERSION); + let text = format!("\u{1b}[>0;{};1c", version); + self.pending_messages_to_pty.push(text.as_bytes().to_vec()); + } + _ => {} + } + } else if c == 'n' { + // DSR - device status report + // https://vt100.net/docs/vt510-rm/DSR.html + match next_param_or(0) { + 5 => { + // report terminal status + let all_good = "\u{1b}[0n"; + self.pending_messages_to_pty + .push(all_good.as_bytes().to_vec()); + } + 6 => { + // CPR - cursor position report + let position_report = + format!("\x1b[{};{}R", self.cursor.y + 1, self.cursor.x + 1); + self.pending_messages_to_pty + .push(position_report.as_bytes().to_vec()); + } + _ => {} + } + } else if c == 't' { + match next_param_or(1) as usize { + 14 => { + // TODO: report text area size in pixels, currently unimplemented + // to solve this we probably need to query the user's terminal for the cursor + // size and then use it as a multiplier + } + 18 => { + // report text area + let text_area_report = format!("\x1b[8;{};{}t", self.height, self.width); + self.pending_messages_to_pty + .push(text_area_report.as_bytes().to_vec()); + } + 22 => { + // TODO: push title + } + 23 => { + // TODO: pop title + } + _ => {} + } } else { let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); #[cfg(not(test))] @@ -1341,6 +1403,11 @@ impl Perform for Grid { (b'7', None) => { self.save_cursor_position(); } + (b'Z', None) => { + let terminal_capabilities = "\u{1b}[?6c"; + self.pending_messages_to_pty + .push(terminal_capabilities.as_bytes().to_vec()); + } (b'8', None) => { self.restore_cursor_position(); } diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index 98021110..5c4f55c8 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -301,6 +301,9 @@ impl Pane for TerminalPane { CursorShape::BlinkingBeam => "\u{1b}[5 q".to_string(), } } + fn drain_messages_to_pty(&mut self) -> Vec> { + self.grid.pending_messages_to_pty.drain(..).collect() + } } impl TerminalPane { diff --git a/src/client/panes/unit/grid_tests.rs b/src/client/panes/unit/grid_tests.rs index b6a2ec6c..8517a975 100644 --- a/src/client/panes/unit/grid_tests.rs +++ b/src/client/panes/unit/grid_tests.rs @@ -383,3 +383,15 @@ fn csi_capital_z() { } assert_snapshot!(format!("{:?}", grid)); } + +#[test] +fn terminal_reports() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "terminal_reports"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid.pending_messages_to_pty)); +} diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap new file mode 100644 index 00000000..b2dca273 --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap @@ -0,0 +1,6 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid . pending_messages_to_pty)" + +--- +[[27, 91, 63, 54, 99], [27, 91, 56, 59, 53, 49, 59, 57, 55, 116], [27, 91, 63, 54, 99], [27, 91, 48, 110], [27, 91, 49, 59, 49, 82]] diff --git a/src/client/tab.rs b/src/client/tab.rs index 785a650e..29a41530 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -218,6 +218,11 @@ pub trait Pane { fn invisible_borders(&self) -> bool { false } + fn drain_messages_to_pty(&mut self) -> Vec> { + // TODO: this is only relevant to terminal panes + // we should probably refactor away from this trait at some point + vec![] + } } impl Tab { @@ -601,6 +606,10 @@ impl Tab { // the reason if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) { terminal_output.handle_pty_bytes(bytes); + let messages_to_pty = terminal_output.drain_messages_to_pty(); + for message in messages_to_pty { + self.write_to_pane_id(message, PaneId::Terminal(pid)); + } } } pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec) { diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs index ace5674b..26a2f90a 100644 --- a/src/common/utils/shared.rs +++ b/src/common/utils/shared.rs @@ -85,3 +85,24 @@ pub fn _detect_theme(bg: PaletteColor) -> Theme { _ => Theme::Dark, } } + +// (this was shamelessly copied from alacritty) +// +// This returns the current terminal version as a unique number based on the +// semver version. The different versions are padded to ensure that a higher semver version will +// always report a higher version number. +pub fn version_number(mut version: &str) -> usize { + if let Some(separator) = version.rfind('-') { + version = &version[..separator]; + } + + let mut version_number = 0; + + let semver_versions = version.split('.'); + for (i, semver_version) in semver_versions.rev().enumerate() { + let semver_number = semver_version.parse::().unwrap_or(0); + version_number += usize::pow(100, i as u32) * semver_number; + } + + version_number +} diff --git a/src/tests/fixtures/terminal_reports b/src/tests/fixtures/terminal_reports new file mode 100644 index 00000000..9ec87eba --- /dev/null +++ b/src/tests/fixtures/terminal_reports @@ -0,0 +1 @@ +Z