diff options
author | gmorer <gmorer@users.noreply.github.com> | 2023-10-13 09:24:22 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-13 11:24:22 +0200 |
commit | ccc40a4a26b8e8a77288e55ab6777c64af816469 (patch) | |
tree | a67d9afbbd6bd6916161fea325a2512d42a5624a /zellij-client | |
parent | 3e31a0e3474c3ffe8ea8085303651798647ec4cb (diff) |
feat(client): terminal synchronized output (#2798)
Diffstat (limited to 'zellij-client')
-rw-r--r-- | zellij-client/src/input_handler.rs | 5 | ||||
-rw-r--r-- | zellij-client/src/lib.rs | 21 | ||||
-rw-r--r-- | zellij-client/src/stdin_ansi_parser.rs | 60 |
3 files changed, 83 insertions, 3 deletions
diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index cf5b3d326..158659a42 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -197,6 +197,11 @@ impl InputHandler { self.os_input .send_to_server(ClientToServerMsg::ColorRegisters(color_registers)); }, + AnsiStdinInstruction::SynchronizedOutput(enabled) => { + self.send_client_instructions + .send(ClientInstruction::SetSynchronizedOutput(enabled)) + .unwrap(); + }, } } fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) { diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 716592640..6cb855118 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -16,7 +16,7 @@ use std::sync::{Arc, Mutex}; use std::thread; use zellij_utils::errors::FatalError; -use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser}; +use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser, SyncOutput}; use crate::{ command_is_executing::CommandIsExecuting, input_handler::input_loop, os_input_output::ClientOsApi, stdin_handler::stdin_loop, @@ -47,6 +47,7 @@ pub(crate) enum ClientInstruction { DoneParsingStdinQuery, Log(Vec<String>), SwitchSession(ConnectToSession), + SetSynchronizedOutput(Option<SyncOutput>), } impl From<ServerToClientMsg> for ClientInstruction { @@ -82,6 +83,7 @@ impl From<&ClientInstruction> for ClientContext { ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery, ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery, ClientInstruction::SwitchSession(..) => ClientContext::SwitchSession, + ClientInstruction::SetSynchronizedOutput(..) => ClientContext::SetSynchronisedOutput, } } } @@ -381,6 +383,10 @@ pub fn start_client( let mut exit_msg = String::new(); let mut loading = true; let mut pending_instructions = vec![]; + let mut synchronised_output = match os_input.env_variable("TERM").as_deref() { + Some("alacritty") => Some(SyncOutput::DCS), + _ => None, + }; let mut stdout = os_input.get_stdout_writer(); stdout @@ -439,9 +445,19 @@ pub fn start_client( }, ClientInstruction::Render(output) => { let mut stdout = os_input.get_stdout_writer(); + if let Some(sync) = synchronised_output { + stdout + .write_all(sync.start_seq()) + .expect("cannot write to stdout"); + } stdout .write_all(output.as_bytes()) .expect("cannot write to stdout"); + if let Some(sync) = synchronised_output { + stdout + .write_all(sync.end_seq()) + .expect("cannot write to stdout"); + } stdout.flush().expect("could not flush"); }, ClientInstruction::UnblockInputThread => { @@ -462,6 +478,9 @@ pub fn start_client( os_input.send_to_server(ClientToServerMsg::ClientExited); break; }, + ClientInstruction::SetSynchronizedOutput(enabled) => { + synchronised_output = enabled; + }, _ => {}, } } diff --git a/zellij-client/src/stdin_ansi_parser.rs b/zellij-client/src/stdin_ansi_parser.rs index a34eac5be..6816803bb 100644 --- a/zellij-client/src/stdin_ansi_parser.rs +++ b/zellij-client/src/stdin_ansi_parser.rs @@ -11,6 +11,33 @@ use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; use zellij_utils::anyhow::Result; +/// Describe the terminal implementation of synchronised output +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum SyncOutput { + DCS, + CSI, +} + +impl SyncOutput { + pub fn start_seq(&self) -> &'static [u8] { + static CSI_BSU_SEQ: &'static [u8] = "\u{1b}[?2026h".as_bytes(); + static DCS_BSU_SEQ: &'static [u8] = "\u{1b}P=1s\u{1b}".as_bytes(); + match self { + SyncOutput::DCS => DCS_BSU_SEQ, + SyncOutput::CSI => CSI_BSU_SEQ, + } + } + + pub fn end_seq(&self) -> &'static [u8] { + static CSI_ESU_SEQ: &'static [u8] = "\u{1b}[?2026l".as_bytes(); + static DCS_ESU_SEQ: &'static [u8] = "\u{1b}P=2s\u{1b}".as_bytes(); + match self { + SyncOutput::DCS => DCS_ESU_SEQ, + SyncOutput::CSI => CSI_ESU_SEQ, + } + } +} + #[derive(Debug)] pub struct StdinAnsiParser { raw_buffer: Vec<u8>, @@ -36,8 +63,10 @@ impl StdinAnsiParser { // <ESC>[16t => get character cell size in pixels // <ESC>]11;?<ESC>\ => get background color // <ESC>]10;?<ESC>\ => get foreground color - let mut query_string = - String::from("\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}"); + // <ESC>[?2026$p => get synchronised output mode + let mut query_string = String::from( + "\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\u{5c}\u{1b}]10;?\u{1b}\u{5c}\u{1b}[?2026$p", + ); // query colors // eg. <ESC>]4;5;?<ESC>\ => query color register number 5 @@ -130,6 +159,14 @@ impl StdinAnsiParser { } else { self.raw_buffer.clear(); } + } else if byte == b'y' { + self.raw_buffer.push(byte); + if let Some(ansi_sequence) = + AnsiStdinInstruction::synchronized_output_from_bytes(&self.raw_buffer) + { + self.pending_events.push(ansi_sequence); + self.raw_buffer.clear(); + } } else { self.raw_buffer.push(byte); } @@ -142,6 +179,7 @@ pub enum AnsiStdinInstruction { BackgroundColor(String), ForegroundColor(String), ColorRegisters(Vec<(usize, String)>), + SynchronizedOutput(Option<SyncOutput>), } impl AnsiStdinInstruction { @@ -225,6 +263,24 @@ impl AnsiStdinInstruction { } Some(AnsiStdinInstruction::ColorRegisters(registers)) } + + pub fn synchronized_output_from_bytes(bytes: &[u8]) -> Option<Self> { + lazy_static! { + static ref RE: Regex = Regex::new(r"^\u{1b}\[\?2026;([0|1|2|3|4])\$y$").unwrap(); + } + let key_string = String::from_utf8_lossy(bytes); + if let Some(captures) = RE.captures_iter(&key_string).next() { + match captures[1].parse::<usize>().ok()? { + 1 | 2 => Some(AnsiStdinInstruction::SynchronizedOutput(Some( + SyncOutput::CSI, + ))), + 0 | 4 => Some(AnsiStdinInstruction::SynchronizedOutput(None)), + _ => None, + } + } else { + None + } + } } fn color_sequence_from_bytes(bytes: &[u8]) -> Result<(usize, String), &'static str> { |