diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d17826..b21f466c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * fix: reap processes when using an external clipboard tool (https://github.com/zellij-org/zellij/pull/4298) * fix: out of bounds mouse release events (https://github.com/zellij-org/zellij/pull/4300) * fix: account for emoji/widechars when double/triple-clicking to mark words (https://github.com/zellij-org/zellij/pull/4302) +* fix: allow pasting and emojis in tab/pane names and pasting in search (https://github.com/zellij-org/zellij/pull/4303) ## [0.42.2] - 2025-04-15 * refactor(terminal): track scroll_region as tuple rather than Option (https://github.com/zellij-org/zellij/pull/4082) diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 083d6640..91ed1ac5 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -125,7 +125,8 @@ impl InputHandler { config: Config, options: Options, send_client_instructions: SenderWithContext, - mode: InputMode, + mode: InputMode, // TODO: we can probably get rid of this now that we're tracking it on the + // server instead receive_input_instructions: Receiver<(InputInstruction, ErrorContext)>, ) -> Self { InputHandler { diff --git a/zellij-server/src/panes/mod.rs b/zellij-server/src/panes/mod.rs index f85e3681..bbb74faa 100644 --- a/zellij-server/src/panes/mod.rs +++ b/zellij-server/src/panes/mod.rs @@ -10,7 +10,7 @@ mod active_panes; pub mod floating_panes; mod plugin_pane; mod search; -mod terminal_pane; +pub mod terminal_pane; mod tiled_panes; pub use active_panes::*; diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 44089237..e0125915 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -20,6 +20,7 @@ use zellij_utils::input::keybinds::Keybinds; use zellij_utils::input::mouse::MouseEvent; use zellij_utils::input::options::Clipboard; use zellij_utils::pane_size::{Size, SizeInPixels}; +use zellij_utils::shared::clean_string_from_control_and_linebreak; use zellij_utils::{ consts::{session_info_folder_for_session, ZELLIJ_SOCK_DIR}, envs::set_session_name, @@ -36,6 +37,7 @@ use crate::os_input_output::ResizeCache; use crate::pane_groups::PaneGroups; use crate::panes::alacritty_functions::xparse_color; use crate::panes::terminal_character::AnsiCode; +use crate::panes::terminal_pane::{BRACKETED_PASTE_BEGIN, BRACKETED_PASTE_END}; use crate::session_layout_metadata::{PaneLayoutMetadata, SessionLayoutMetadata}; use crate::{ @@ -1355,6 +1357,12 @@ impl Screen { } } + pub fn get_client_input_mode(&self, client_id: ClientId) -> Option { + self.get_active_tab(client_id) + .ok() + .and_then(|tab| tab.get_client_input_mode(client_id)) + } + pub fn get_first_client_id(&self) -> Option { self.active_tab_indices.keys().next().copied() } @@ -1849,10 +1857,9 @@ impl Screen { active_tab.name.pop(); }, c => { - // It only allows printable unicode - if buf.iter().all(|u| matches!(u, 0x20..=0x7E | 0xA0..=0xFF)) { - active_tab.name.push_str(c); - } + active_tab + .name + .push_str(&clean_string_from_control_and_linebreak(c)); }, } self.log_and_report_session_state() @@ -3547,24 +3554,73 @@ pub(crate) fn screen_thread_main( } } let mut state_changed = false; - active_tab_and_connected_client_id!( - screen, - client_id, - |tab: &mut Tab, client_id: ClientId| { - let write_result = match tab.is_sync_panes_active() { - true => tab.write_to_terminals_on_current_tab(&key_with_modifier, raw_bytes, is_kitty_keyboard_protocol, client_id), - false => tab.write_to_active_terminal(&key_with_modifier, raw_bytes, is_kitty_keyboard_protocol, client_id), - }; - if let Ok(true) = write_result { + let client_input_mode = screen.get_client_input_mode(client_id); + match client_input_mode { + Some(InputMode::RenameTab) => { + if !(raw_bytes == BRACKETED_PASTE_BEGIN || raw_bytes == BRACKETED_PASTE_END) + { + screen.update_active_tab_name(raw_bytes, client_id)?; state_changed = true; } - write_result }, - ? - ); + _ => { + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, client_id: ClientId| { + match client_input_mode { + Some(InputMode::EnterSearch) => { + if !(raw_bytes == BRACKETED_PASTE_BEGIN + || raw_bytes == BRACKETED_PASTE_END) + { + if let Err(e) = + tab.update_search_term(raw_bytes, client_id) + { + log::error!("{}", e); + } + } + state_changed = true; + }, + Some(InputMode::RenamePane) => { + if !(raw_bytes == BRACKETED_PASTE_BEGIN + || raw_bytes == BRACKETED_PASTE_END) + { + if let Err(e) = + tab.update_active_pane_name(raw_bytes, client_id) + { + log::error!("{}", e); + } + state_changed = true; + } + }, + _ => { + let write_result = match tab.is_sync_panes_active() { + true => tab.write_to_terminals_on_current_tab( + &key_with_modifier, + raw_bytes, + is_kitty_keyboard_protocol, + client_id, + ), + false => tab.write_to_active_terminal( + &key_with_modifier, + raw_bytes, + is_kitty_keyboard_protocol, + client_id, + ), + }; + if let Ok(true) = write_result { + state_changed = true; + } + }, + } + } + ); + }, + }; if state_changed { screen.log_and_report_session_state()?; } + screen.unblock_input()?; }, ScreenInstruction::Resize(client_id, strategy) => { active_tab_and_connected_client_id!( diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index bf578567..e8c61a69 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -21,6 +21,7 @@ use zellij_utils::input::command::RunCommand; use zellij_utils::input::mouse::{MouseEvent, MouseEventType}; use zellij_utils::position::Position; use zellij_utils::position::{Column, Line}; +use zellij_utils::shared::clean_string_from_control_and_linebreak; use crate::background_jobs::BackgroundJob; use crate::pane_groups::PaneGroups; @@ -4618,21 +4619,12 @@ impl Tab { pub fn update_active_pane_name(&mut self, buf: Vec, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to update name of active pane to '{buf:?}' for client {client_id}"); - - // Only allow printable unicode, delete and backspace keys. - let is_updatable = buf - .iter() - .all(|u| matches!(u, 0x20..=0x7E | 0xA0..=0xFF | 0x08 | 0x7F)); - if is_updatable { - let s = str::from_utf8(&buf).with_context(err_context)?; - self.get_active_pane_mut(client_id) - .with_context(|| format!("no active pane found for client {client_id}")) - .map(|active_pane| { - active_pane.update_name(s); - })?; - } else { - log::error!("Failed to update pane name due to unprintable characters"); - } + let s = str::from_utf8(&buf).with_context(err_context)?; + self.get_active_pane_mut(client_id) + .with_context(|| format!("no active pane found for client {client_id}")) + .map(|active_pane| { + active_pane.update_name(&clean_string_from_control_and_linebreak(s)); + })?; Ok(()) } @@ -4704,6 +4696,9 @@ impl Tab { pub fn update_search_term(&mut self, buf: Vec, client_id: ClientId) -> Result<()> { if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { // It only allows terminating char(\0), printable unicode, delete and backspace keys. + // TODO: we should really remove this limitation to allow searching for emojis and + // other wide chars - currently the search mechanism itself ignores wide chars, so we + // should first fix that before removing this condition let is_updatable = buf .iter() .all(|u| matches!(u, 0x00 | 0x20..=0x7E | 0x08 | 0x7F)); @@ -5360,6 +5355,9 @@ impl Tab { pub fn get_display_area(&self) -> Size { self.display_area.borrow().clone() } + pub fn get_client_input_mode(&self, client_id: ClientId) -> Option { + self.mode_info.borrow().get(&client_id).map(|m| m.mode) + } fn new_scrollback_editor_pane(&self, pid: u32) -> TerminalPane { let next_terminal_position = self.get_next_terminal_position(); let mut new_pane = TerminalPane::new( diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index 8c7c0806..37b413c1 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -35,6 +35,19 @@ pub fn ansi_len(s: &str) -> usize { from_utf8(&strip(s).unwrap()).unwrap().width() } +pub fn clean_string_from_control_and_linebreak(input: &str) -> String { + input + .chars() + .filter(|c| { + !c.is_control() && + *c != '\n' && // line feed + *c != '\r' && // carriage return + *c != '\u{2028}' && // line separator + *c != '\u{2029}' // paragraph separator + }) + .collect() +} + pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String { s.lines() .map(|l| {