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
This commit is contained in:
parent
44a3c1dae9
commit
48ecb0e34f
6 changed files with 102 additions and 33 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue