summaryrefslogtreecommitdiffstats
path: root/zellij-client
diff options
context:
space:
mode:
authorgmorer <gmorer@users.noreply.github.com>2023-10-13 09:24:22 +0000
committerGitHub <noreply@github.com>2023-10-13 11:24:22 +0200
commitccc40a4a26b8e8a77288e55ab6777c64af816469 (patch)
treea67d9afbbd6bd6916161fea325a2512d42a5624a /zellij-client
parent3e31a0e3474c3ffe8ea8085303651798647ec4cb (diff)
feat(client): terminal synchronized output (#2798)
Diffstat (limited to 'zellij-client')
-rw-r--r--zellij-client/src/input_handler.rs5
-rw-r--r--zellij-client/src/lib.rs21
-rw-r--r--zellij-client/src/stdin_ansi_parser.rs60
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> {