From c5487fb1b6d7836e9756a507b80e111bf15d60ab Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 2 Jul 2025 13:38:00 +0200 Subject: [PATCH] fix(web-client): various bugs (#4257) * fix: background color issues and server recovery bug * fix: display title in web client * style(fmt): rustfmt * docs(changelog): add PR --- CHANGELOG.md | 2 +- zellij-client/assets/websockets.js | 49 ++++++++++++------- .../src/web_client/server_listener.rs | 6 ++- zellij-server/src/panes/floating_panes/mod.rs | 8 +++ zellij-server/src/panes/terminal_pane.rs | 2 +- zellij-server/src/panes/tiled_panes/mod.rs | 4 ++ zellij-utils/src/shared.rs | 18 ++++--- 7 files changed, 60 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55b38f54..73f62dc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * feat: multiple select and bulk pane actions (https://github.com/zellij-org/zellij/pull/4169 and https://github.com/zellij-org/zellij/pull/4171 and https://github.com/zellij-org/zellij/pull/4221) * feat: add an optional key tooltip to show the current keybindings for the compact bar (https://github.com/zellij-org/zellij/pull/4225) -* feat: web-client, allowing users to share sessions in the browser (https://github.com/zellij-org/zellij/pull/4242) +* feat: web-client, allowing users to share sessions in the browser (https://github.com/zellij-org/zellij/pull/4242 and https://github.com/zellij-org/zellij/pull/4257) * performance: consolidate renders (https://github.com/zellij-org/zellij/pull/4245) * feat: add plugin API to replace a pane with another existing pane (https://github.com/zellij-org/zellij/pull/4246) * feat: add "stack" keybinding and CLI action to add a stacked pane to the current pane (https://github.com/zellij-org/zellij/pull/4255) diff --git a/zellij-client/assets/websockets.js b/zellij-client/assets/websockets.js index c45a9b52..6e5006c8 100644 --- a/zellij-client/assets/websockets.js +++ b/zellij-client/assets/websockets.js @@ -42,26 +42,37 @@ export function initWebSockets(webClientId, sessionName, term, fitAddon, sendAns } let data = event.data; - if (typeof data === 'string' && data.includes('\x1b[0 q')) { - const shouldBlink = term.options.cursorBlink; - const cursorStyle = term.options.cursorStyle; - let replacement; - switch (cursorStyle) { - case 'block': - replacement = shouldBlink ? '\x1b[1 q' : '\x1b[2 q'; - break; - case 'underline': - replacement = shouldBlink ? '\x1b[3 q' : '\x1b[4 q'; - break; - case 'bar': - replacement = shouldBlink ? '\x1b[5 q' : '\x1b[6 q'; - break; - default: - replacement = '\x1b[2 q'; - break; + + if (typeof data === 'string') { + // Handle ANSI title change sequences + const titleRegex = /\x1b\]0;([^\x07\x1b]*?)(?:\x07|\x1b\\)/g; + let match; + while ((match = titleRegex.exec(data)) !== null) { + document.title = match[1]; + } + + if (data.includes('\x1b[0 q')) { + const shouldBlink = term.options.cursorBlink; + const cursorStyle = term.options.cursorStyle; + let replacement; + switch (cursorStyle) { + case 'block': + replacement = shouldBlink ? '\x1b[1 q' : '\x1b[2 q'; + break; + case 'underline': + replacement = shouldBlink ? '\x1b[3 q' : '\x1b[4 q'; + break; + case 'bar': + replacement = shouldBlink ? '\x1b[5 q' : '\x1b[6 q'; + break; + default: + replacement = '\x1b[2 q'; + break; + } + data = data.replace(/\x1b\[0 q/g, replacement); } - data = data.replace(/\x1b\[0 q/g, replacement); } + term.write(data); }; @@ -145,7 +156,7 @@ function startWsControl(wsControl, term, fitAddon, ownWebClientId) { term.options.cursorInactiveStyle = cursor_inactive_style; } const body = document.querySelector("body"); - body.style.background = theme.background; + body.style.background = theme.background || "black"; const terminal = document.getElementById("terminal"); terminal.style.background = theme.background; diff --git a/zellij-client/src/web_client/server_listener.rs b/zellij-client/src/web_client/server_listener.rs index 3a489ec4..2ce91ea2 100644 --- a/zellij-client/src/web_client/server_listener.rs +++ b/zellij-client/src/web_client/server_listener.rs @@ -145,7 +145,11 @@ pub fn zellij_server_listener( WebServerToWebClientControlMessage::LogError { lines }, ); }, - _ => {}, + _ => { + // server disconnected, stop trying to listen otherwise we retry + // indefinitely and get 100% CPU + break; + }, } } if reconnect_to_session.is_none() { diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index 1e088a22..d8ce0c7c 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -49,6 +49,7 @@ pub struct FloatingPanes { show_panes: bool, pane_being_moved_with_mouse: Option<(PaneId, Position)>, senders: ThreadSenders, + window_title: Option, } #[allow(clippy::borrowed_box)] @@ -84,6 +85,7 @@ impl FloatingPanes { active_panes: ActivePanes::new(&os_input), pane_being_moved_with_mouse: None, senders, + window_title: None, } } pub fn stack(&self) -> Option { @@ -234,6 +236,7 @@ impl FloatingPanes { .or_else(|| self.panes.keys().next().copied()) } pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) { + self.window_title = None; // clear so that it will be re-rendered once we toggle back self.show_panes = should_show_floating_panes; if should_show_floating_panes { self.active_panes.focus_all_panes(&mut self.panes); @@ -438,6 +441,11 @@ impl FloatingPanes { .render_pane_contents_for_client(*client_id) .with_context(err_context)?; } + pane_contents_and_ui.render_terminal_title_if_needed( + *client_id, + client_mode, + &mut self.window_title, + ); // this is done for panes that don't have their own cursor (eg. panes of // another user) pane_contents_and_ui diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index c7aa931c..fe69556f 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -459,7 +459,7 @@ impl Pane for TerminalPane { let pane_title = if self.pane_name.is_empty() && input_mode == InputMode::RenamePane { "Enter name..." } else if self.pane_name.is_empty() { - self.grid.title.as_deref().unwrap_or(&self.pane_title) + self.grid.title.as_deref().unwrap_or("") } else { &self.pane_name }; diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 5ccafab9..e877026f 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -1143,6 +1143,10 @@ impl TiledPanes { .add_character_chunks_to_client(client_id, boundaries_to_render, None) .with_context(err_context)?; } + if floating_panes_are_visible { + // we do this here so that when they are toggled off, we will make sure to re-render the title + self.window_title = None; + } Ok(()) } pub fn get_panes(&self) -> impl Iterator)> { diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index 6a5a2c93..8c7c0806 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -54,13 +54,17 @@ pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String { } pub fn make_terminal_title(pane_title: &str) -> String { - format!( - "\u{1b}]0;Zellij {}- {}\u{07}", - get_session_name() - .map(|n| format!("({}) ", n)) - .unwrap_or_default(), - pane_title, - ) + // if we receive a title, we display it, otherwise we display the session name + if pane_title.is_empty() { + format!( + "\u{1b}]0;Zellij {}\u{07}", + get_session_name() + .map(|n| format!("({}) ", n)) + .unwrap_or_default() + ) + } else { + format!("\u{1b}]0;{}\u{07}", pane_title,) + } } // Colors