aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-07-18 17:35:47 +0200
committerGitHub <noreply@github.com>2025-07-18 17:35:47 +0200
commit48ecb0e34ff9d6d04f574237dd3a8e18e2830e6c (patch)
treeb725379de8b0f8d1b08559cff18bbc74c2c87c2e
parentfix(grid): account for wide characters when double-triple mouse clicking (#4302) (diff)
downloadsl0ppij-48ecb0e34ff9d6d04f574237dd3a8e18e2830e6c.tar.gz
sl0ppij-48ecb0e34ff9d6d04f574237dd3a8e18e2830e6c.tar.xz
sl0ppij-48ecb0e34ff9d6d04f574237dd3a8e18e2830e6c.zip
fix(ux): allow pasting and inputing wide-chars in search + tab rename + pane rename (#4303)
* fix(ux): allow pasting to search/tab-rename/pane-rename * fix(ux): allow setting wide chars (eg. emoji) in tab/pane names * style(fmt): rustfmt * docs(changelog): add PR
-rw-r--r--CHANGELOG.md1
-rw-r--r--zellij-client/src/input_handler.rs3
-rw-r--r--zellij-server/src/panes/mod.rs2
-rw-r--r--zellij-server/src/screen.rs88
-rw-r--r--zellij-server/src/tab/mod.rs28
-rw-r--r--zellij-utils/src/shared.rs13
6 files changed, 102 insertions, 33 deletions
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<ClientInstruction>,
- 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<InputMode> {
+ 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<ClientId> {
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<u8>, 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<u8>, 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<InputMode> {
+ 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| {