From 0a85a56b5453f58d7d413e9b32d4e82e5b0cc68c Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Mon, 13 May 2024 17:45:39 +0200 Subject: [PATCH 01/12] feat: add arrows support for search field --- wezterm-gui/src/overlay/copy.rs | 69 ++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index c06b0018c14..39399637f0b 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -36,6 +36,7 @@ lazy_static::lazy_static! { } const SEARCH_CHUNK_SIZE: StableRowIndex = 1000; +const SEARCH_CURSOR_PADDING: usize = 8; pub struct CopyOverlay { delegate: Arc, @@ -66,6 +67,7 @@ struct CopyRenderable { /// The text that the user entered pattern: Pattern, + search_cursor: StableCursorPosition, /// The most recently queried set of matches results: Vec, by_line: HashMap>, @@ -125,6 +127,22 @@ impl CopyOverlay { .clone() .ok_or_else(|| anyhow::anyhow!("failed to clone window handle"))?; let dims = pane.get_dimensions(); + let pattern = if params.pattern.is_empty() { + SAVED_PATTERN + .lock() + .get(&tab_id) + .map(|p| p.clone()) + .unwrap_or(params.pattern) + } else { + params.pattern + }; + + let search_cursor = StableCursorPosition { + x: SEARCH_CURSOR_PADDING + pattern.len(), + y: 0, + shape: termwiz::surface::CursorShape::SteadyBlock, + visibility: termwiz::surface::CursorVisibility::Visible, + }; let mut render = CopyRenderable { cursor, window, @@ -139,15 +157,8 @@ impl CopyOverlay { last_result_seqno: SEQ_ZERO, last_bar_pos: None, tab_id, - pattern: if params.pattern.is_empty() { - SAVED_PATTERN - .lock() - .get(&tab_id) - .map(|p| p.clone()) - .unwrap_or(params.pattern) - } else { - params.pattern - }, + pattern, + search_cursor, editing_search: params.editing_search, result_pos: None, selection_mode: SelectionMode::Cell, @@ -159,6 +170,7 @@ impl CopyOverlay { let search_row = render.compute_search_row(); render.dirty_results.add(search_row); + render.search_cursor.y = search_row; render.update_search(); Ok(Arc::new(CopyOverlay { @@ -629,6 +641,7 @@ impl CopyRenderable { fn clear_pattern(&mut self) { self.pattern.clear(); + self.search_cursor.x = SEARCH_CURSOR_PADDING; self.update_search(); } @@ -676,6 +689,7 @@ impl CopyRenderable { Pattern::Regex(s) => Pattern::CaseSensitiveString(s.clone()), }; self.pattern = pattern; + self.search_cursor.x = SEARCH_CURSOR_PADDING + self.pattern.len(); self.schedule_update_search(); } @@ -1141,13 +1155,37 @@ impl Pane for CopyOverlay { (KeyCode::Char(c), KeyModifiers::NONE) | (KeyCode::Char(c), KeyModifiers::SHIFT) => { // Type to add to the pattern - render.pattern.push(c); + if render.pattern.capacity() - render.pattern.len() == 0 { + render.pattern.reserve(10); + } + let position = render.search_cursor.x - SEARCH_CURSOR_PADDING; + render.pattern.insert(position, c); + render.search_cursor.x += 1; + render.schedule_update_search(); } (KeyCode::Backspace, KeyModifiers::NONE) => { // Backspace to edit the pattern - render.pattern.pop(); - render.schedule_update_search(); + if render.pattern.len() > 0 + && render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 + { + let position = render.search_cursor.x - SEARCH_CURSOR_PADDING; + render.pattern.remove(position - 1); + if render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 { + render.search_cursor.x -= 1; + } + render.schedule_update_search(); + } + } + (KeyCode::LeftArrow, KeyModifiers::NONE) => { + if render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 { + render.search_cursor.x -= 1; + } + } + (KeyCode::RightArrow, KeyModifiers::NONE) => { + if render.search_cursor.x - SEARCH_CURSOR_PADDING < render.pattern.len() { + render.search_cursor.x += 1; + } } _ => {} } @@ -1259,12 +1297,7 @@ impl Pane for CopyOverlay { let renderer = self.render.lock(); if renderer.editing_search { // place in the search box - StableCursorPosition { - x: 8 + wezterm_term::unicode_column_width(&renderer.pattern, None), - y: renderer.compute_search_row(), - shape: termwiz::surface::CursorShape::SteadyBlock, - visibility: termwiz::surface::CursorVisibility::Visible, - } + renderer.search_cursor } else { renderer.cursor } From 4e66fbe7c2ca133be4643507f88a428a33654b1e Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Wed, 12 Jun 2024 15:58:48 +0200 Subject: [PATCH 02/12] fix: wrong indexing for unicode --- wezterm-gui/src/overlay/copy.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 39399637f0b..c269bbc191c 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -689,7 +689,6 @@ impl CopyRenderable { Pattern::Regex(s) => Pattern::CaseSensitiveString(s.clone()), }; self.pattern = pattern; - self.search_cursor.x = SEARCH_CURSOR_PADDING + self.pattern.len(); self.schedule_update_search(); } @@ -1158,8 +1157,15 @@ impl Pane for CopyOverlay { if render.pattern.capacity() - render.pattern.len() == 0 { render.pattern.reserve(10); } + let position = render.search_cursor.x - SEARCH_CURSOR_PADDING; - render.pattern.insert(position, c); + if position == render.pattern.graphemes(true).count() { + render.pattern.push(c); + } else { + let (offset, _) = + render.pattern.grapheme_indices(true).nth(position).unwrap(); + render.pattern.insert(offset, c); + } render.search_cursor.x += 1; render.schedule_update_search(); @@ -1170,7 +1176,12 @@ impl Pane for CopyOverlay { && render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 { let position = render.search_cursor.x - SEARCH_CURSOR_PADDING; - render.pattern.remove(position - 1); + let (offset, _) = render + .pattern + .grapheme_indices(true) + .nth(position - 1) + .unwrap(); + render.pattern.remove(offset); if render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 { render.search_cursor.x -= 1; } @@ -1183,7 +1194,9 @@ impl Pane for CopyOverlay { } } (KeyCode::RightArrow, KeyModifiers::NONE) => { - if render.search_cursor.x - SEARCH_CURSOR_PADDING < render.pattern.len() { + if render.search_cursor.x - SEARCH_CURSOR_PADDING + < render.pattern.graphemes(true).count() + { render.search_cursor.x += 1; } } From fba20840259c793e2a8c26a8b2ae41fba42042b0 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Wed, 12 Jun 2024 16:37:54 +0200 Subject: [PATCH 03/12] feat: simlify code --- wezterm-gui/src/overlay/copy.rs | 58 ++++++++++++++------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index c269bbc191c..aaa0839d7b0 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -36,7 +36,6 @@ lazy_static::lazy_static! { } const SEARCH_CHUNK_SIZE: StableRowIndex = 1000; -const SEARCH_CURSOR_PADDING: usize = 8; pub struct CopyOverlay { delegate: Arc, @@ -67,7 +66,7 @@ struct CopyRenderable { /// The text that the user entered pattern: Pattern, - search_cursor: StableCursorPosition, + search_cursor_x: usize, /// The most recently queried set of matches results: Vec, by_line: HashMap>, @@ -137,12 +136,7 @@ impl CopyOverlay { params.pattern }; - let search_cursor = StableCursorPosition { - x: SEARCH_CURSOR_PADDING + pattern.len(), - y: 0, - shape: termwiz::surface::CursorShape::SteadyBlock, - visibility: termwiz::surface::CursorVisibility::Visible, - }; + let search_cursor_x = unicode_column_width(&pattern, None); let mut render = CopyRenderable { cursor, window, @@ -158,7 +152,7 @@ impl CopyOverlay { last_bar_pos: None, tab_id, pattern, - search_cursor, + search_cursor_x, editing_search: params.editing_search, result_pos: None, selection_mode: SelectionMode::Cell, @@ -170,7 +164,6 @@ impl CopyOverlay { let search_row = render.compute_search_row(); render.dirty_results.add(search_row); - render.search_cursor.y = search_row; render.update_search(); Ok(Arc::new(CopyOverlay { @@ -641,7 +634,7 @@ impl CopyRenderable { fn clear_pattern(&mut self) { self.pattern.clear(); - self.search_cursor.x = SEARCH_CURSOR_PADDING; + self.search_cursor_x = 0; self.update_search(); } @@ -1154,50 +1147,44 @@ impl Pane for CopyOverlay { (KeyCode::Char(c), KeyModifiers::NONE) | (KeyCode::Char(c), KeyModifiers::SHIFT) => { // Type to add to the pattern - if render.pattern.capacity() - render.pattern.len() == 0 { - render.pattern.reserve(10); - } - - let position = render.search_cursor.x - SEARCH_CURSOR_PADDING; - if position == render.pattern.graphemes(true).count() { + if render.search_cursor_x == unicode_column_width(&render.pattern, None) { render.pattern.push(c); } else { - let (offset, _) = - render.pattern.grapheme_indices(true).nth(position).unwrap(); + let (offset, _) = render + .pattern + .grapheme_indices(true) + .nth(render.search_cursor_x) + .unwrap(); render.pattern.insert(offset, c); } - render.search_cursor.x += 1; + render.search_cursor_x += 1; render.schedule_update_search(); } (KeyCode::Backspace, KeyModifiers::NONE) => { // Backspace to edit the pattern - if render.pattern.len() > 0 - && render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 - { - let position = render.search_cursor.x - SEARCH_CURSOR_PADDING; + if render.pattern.len() > 0 && render.search_cursor_x > 0 { + let position = render.search_cursor_x; let (offset, _) = render .pattern .grapheme_indices(true) .nth(position - 1) .unwrap(); render.pattern.remove(offset); - if render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 { - render.search_cursor.x -= 1; + if render.search_cursor_x > 0 { + render.search_cursor_x -= 1; } render.schedule_update_search(); } } (KeyCode::LeftArrow, KeyModifiers::NONE) => { - if render.search_cursor.x - SEARCH_CURSOR_PADDING > 0 { - render.search_cursor.x -= 1; + if render.search_cursor_x > 0 { + render.search_cursor_x -= 1; } } (KeyCode::RightArrow, KeyModifiers::NONE) => { - if render.search_cursor.x - SEARCH_CURSOR_PADDING - < render.pattern.graphemes(true).count() - { - render.search_cursor.x += 1; + if render.search_cursor_x < unicode_column_width(&render.pattern, None) { + render.search_cursor_x += 1; } } _ => {} @@ -1310,7 +1297,12 @@ impl Pane for CopyOverlay { let renderer = self.render.lock(); if renderer.editing_search { // place in the search box - renderer.search_cursor + StableCursorPosition { + x: 8 + renderer.search_cursor_x, + y: renderer.compute_search_row(), + shape: termwiz::surface::CursorShape::SteadyBlock, + visibility: termwiz::surface::CursorVisibility::Visible, + } } else { renderer.cursor } From 45f67618c9a53b16a325ef3f6fd48901ce6a6817 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Wed, 12 Jun 2024 18:59:45 +0200 Subject: [PATCH 04/12] refactor: extract LineEditBuffer from LineEditor --- termwiz/src/lineedit/buffer.rs | 178 ++++++++++++++++++++++++++++ termwiz/src/lineedit/mod.rs | 205 ++++++--------------------------- 2 files changed, 211 insertions(+), 172 deletions(-) create mode 100644 termwiz/src/lineedit/buffer.rs diff --git a/termwiz/src/lineedit/buffer.rs b/termwiz/src/lineedit/buffer.rs new file mode 100644 index 00000000000..719ab8fb259 --- /dev/null +++ b/termwiz/src/lineedit/buffer.rs @@ -0,0 +1,178 @@ +use unicode_segmentation::GraphemeCursor; + +use super::actions::Movement; + +pub struct LineEditBuffer { + line: String, + /// byte index into the UTF-8 string data of the insertion + /// point. This is NOT the number of graphemes! + cursor: usize, +} + +impl LineEditBuffer { + pub fn new() -> Self { + Self { + line: String::new(), + cursor: 0, + } + } + + pub fn get_line(&self) -> &String { + return &self.line; + } + + pub fn get_cursor(&self) -> usize { + return self.cursor; + } + + pub fn insert_char(&mut self, c: char) { + self.line.insert(self.cursor, c); + let mut cursor = GraphemeCursor::new(self.cursor, self.line.len(), false); + if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { + self.cursor = pos; + } + } + + pub fn insert_text(&mut self, text: &str) { + self.line.insert_str(self.cursor, text); + self.cursor += text.len(); + } + + pub fn set_line_and_cursor(&mut self, line: &str, cursor: usize) { + assert!( + cursor < line.len(), + "cursor {} is outside the byte length of the new line of length {}", + cursor, + line.len() + ); + self.line = line.to_string(); + self.cursor = cursor; + } + + pub fn kill_text(&mut self, kill_movement: Movement, move_movement: Movement) { + let kill_pos = self.eval_movement(kill_movement); + let new_cursor = self.eval_movement(move_movement); + + let (lower, upper) = if kill_pos < self.cursor { + (kill_pos, self.cursor) + } else { + (self.cursor, kill_pos) + }; + + self.line.replace_range(lower..upper, ""); + + // Clamp to the line length, otherwise a kill to end of line + // command will leave the cursor way off beyond the end of + // the line. + self.cursor = new_cursor.min(self.line.len()); + } + + pub fn clear(&mut self) { + self.line.clear(); + self.cursor = 0; + } + + pub fn exec_movement(&mut self, movement: Movement) { + self.cursor = self.eval_movement(movement); + } + + /// Compute the cursor position after applying movement + fn eval_movement(&self, movement: Movement) -> usize { + match movement { + Movement::BackwardChar(rep) => { + let mut position = self.cursor; + for _ in 0..rep { + let mut cursor = GraphemeCursor::new(position, self.line.len(), false); + if let Ok(Some(pos)) = cursor.prev_boundary(&self.line, 0) { + position = pos; + } else { + break; + } + } + position + } + Movement::BackwardWord(rep) => { + let char_indices: Vec<(usize, char)> = self.line.char_indices().collect(); + if char_indices.is_empty() { + return self.cursor; + } + let mut char_position = char_indices + .iter() + .position(|(idx, _)| *idx == self.cursor) + .unwrap_or(char_indices.len() - 1); + + for _ in 0..rep { + if char_position == 0 { + break; + } + + let mut found = None; + for prev in (0..char_position - 1).rev() { + if char_indices[prev].1.is_whitespace() { + found = Some(prev + 1); + break; + } + } + + char_position = found.unwrap_or(0); + } + char_indices[char_position].0 + } + Movement::ForwardWord(rep) => { + let char_indices: Vec<(usize, char)> = self.line.char_indices().collect(); + if char_indices.is_empty() { + return self.cursor; + } + let mut char_position = char_indices + .iter() + .position(|(idx, _)| *idx == self.cursor) + .unwrap_or_else(|| char_indices.len()); + + for _ in 0..rep { + // Skip any non-whitespace characters + while char_position < char_indices.len() + && !char_indices[char_position].1.is_whitespace() + { + char_position += 1; + } + + // Skip any whitespace characters + while char_position < char_indices.len() + && char_indices[char_position].1.is_whitespace() + { + char_position += 1; + } + + // We are now on the start of the next word + } + char_indices + .get(char_position) + .map(|(i, _)| *i) + .unwrap_or_else(|| self.line.len()) + } + Movement::ForwardChar(rep) => { + let mut position = self.cursor; + for _ in 0..rep { + let mut cursor = GraphemeCursor::new(position, self.line.len(), false); + if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { + position = pos; + } else { + break; + } + } + position + } + Movement::StartOfLine => 0, + Movement::EndOfLine => { + let mut cursor = + GraphemeCursor::new(self.line.len().saturating_sub(1), self.line.len(), false); + if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { + pos + } else { + self.cursor + } + } + Movement::None => self.cursor, + } + } +} diff --git a/termwiz/src/lineedit/mod.rs b/termwiz/src/lineedit/mod.rs index 88a376db6e5..c65f0f2f526 100644 --- a/termwiz/src/lineedit/mod.rs +++ b/termwiz/src/lineedit/mod.rs @@ -43,12 +43,13 @@ use crate::surface::change::ChangeSequence; use crate::surface::{Change, Position}; use crate::terminal::{new_terminal, Terminal}; use crate::{bail, ensure, Result}; -use unicode_segmentation::GraphemeCursor; mod actions; +mod buffer; mod history; mod host; pub use actions::{Action, Movement, RepeatCount}; +pub use buffer::LineEditBuffer; pub use history::*; pub use host::*; @@ -71,10 +72,7 @@ pub use host::*; pub struct LineEditor<'term> { terminal: &'term mut dyn Terminal, prompt: String, - line: String, - /// byte index into the UTF-8 string data of the insertion - /// point. This is NOT the number of graphemes! - cursor: usize, + line: LineEditBuffer, history_pos: Option, bottom_line: Option, @@ -155,8 +153,7 @@ impl<'term> LineEditor<'term> { Self { terminal, prompt: "> ".to_owned(), - line: String::new(), - cursor: 0, + line: LineEditBuffer::new(), history_pos: None, bottom_line: None, completion: None, @@ -186,7 +183,7 @@ impl<'term> LineEditor<'term> { cursor, .. } => (matching_line, *cursor), - _ => (&self.line, self.cursor), + _ => (self.line.get_line(), self.line.get_cursor()), }; let cursor_position_after_printing_prompt = changes.current_cursor_position(); @@ -258,7 +255,7 @@ impl<'term> LineEditor<'term> { // the text in the line editing area, but since the input // is drawn here, we render an `_` to indicate where the input // position really is. - changes.add(format!("\r\n{}: {}_", label, self.line)); + changes.add(format!("\r\n{}: {}_", label, self.line.get_line())); } // Add some debugging status at the bottom @@ -512,123 +509,9 @@ impl<'term> LineEditor<'term> { } } - /// Compute the cursor position after applying movement - fn eval_movement(&self, movement: Movement) -> usize { - match movement { - Movement::BackwardChar(rep) => { - let mut position = self.cursor; - for _ in 0..rep { - let mut cursor = GraphemeCursor::new(position, self.line.len(), false); - if let Ok(Some(pos)) = cursor.prev_boundary(&self.line, 0) { - position = pos; - } else { - break; - } - } - position - } - Movement::BackwardWord(rep) => { - let char_indices: Vec<(usize, char)> = self.line.char_indices().collect(); - if char_indices.is_empty() { - return self.cursor; - } - let mut char_position = char_indices - .iter() - .position(|(idx, _)| *idx == self.cursor) - .unwrap_or(char_indices.len() - 1); - - for _ in 0..rep { - if char_position == 0 { - break; - } - - let mut found = None; - for prev in (0..char_position - 1).rev() { - if char_indices[prev].1.is_whitespace() { - found = Some(prev + 1); - break; - } - } - - char_position = found.unwrap_or(0); - } - char_indices[char_position].0 - } - Movement::ForwardWord(rep) => { - let char_indices: Vec<(usize, char)> = self.line.char_indices().collect(); - if char_indices.is_empty() { - return self.cursor; - } - let mut char_position = char_indices - .iter() - .position(|(idx, _)| *idx == self.cursor) - .unwrap_or_else(|| char_indices.len()); - - for _ in 0..rep { - // Skip any non-whitespace characters - while char_position < char_indices.len() - && !char_indices[char_position].1.is_whitespace() - { - char_position += 1; - } - - // Skip any whitespace characters - while char_position < char_indices.len() - && char_indices[char_position].1.is_whitespace() - { - char_position += 1; - } - - // We are now on the start of the next word - } - char_indices - .get(char_position) - .map(|(i, _)| *i) - .unwrap_or_else(|| self.line.len()) - } - Movement::ForwardChar(rep) => { - let mut position = self.cursor; - for _ in 0..rep { - let mut cursor = GraphemeCursor::new(position, self.line.len(), false); - if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { - position = pos; - } else { - break; - } - } - position - } - Movement::StartOfLine => 0, - Movement::EndOfLine => { - let mut cursor = - GraphemeCursor::new(self.line.len().saturating_sub(1), self.line.len(), false); - if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { - pos - } else { - self.cursor - } - } - Movement::None => self.cursor, - } - } - fn kill_text(&mut self, kill_movement: Movement, move_movement: Movement) { self.clear_completion(); - let kill_pos = self.eval_movement(kill_movement); - let new_cursor = self.eval_movement(move_movement); - - let (lower, upper) = if kill_pos < self.cursor { - (kill_pos, self.cursor) - } else { - (self.cursor, kill_pos) - }; - - self.line.replace_range(lower..upper, ""); - - // Clamp to the line length, otherwise a kill to end of line - // command will leave the cursor way off beyond the end of - // the line. - self.cursor = new_cursor.min(self.line.len()); + self.line.kill_text(kill_movement, move_movement); } fn clear_completion(&mut self) { @@ -642,8 +525,7 @@ impl<'term> LineEditor<'term> { .. } = &self.state { - self.line = matching_line.to_string(); - self.cursor = *cursor; + self.line.set_line_and_cursor(matching_line, *cursor); self.state = EditorState::Editing; } } @@ -653,7 +535,7 @@ impl<'term> LineEditor<'term> { /// a custom editor operation on the line buffer contents. /// The cursor position is the byte index into the line UTF-8 bytes. pub fn get_line_and_cursor(&mut self) -> (&str, usize) { - (&self.line, self.cursor) + (self.line.get_line(), self.line.get_cursor()) } /// Sets the current line and cursor position. @@ -662,14 +544,7 @@ impl<'term> LineEditor<'term> { /// The cursor position is the byte index into the line UTF-8 bytes. /// Panics: the cursor must be within the bounds of the provided line. pub fn set_line_and_cursor(&mut self, line: &str, cursor: usize) { - assert!( - cursor < line.len(), - "cursor {} is outside the byte length of the new line of length {}", - cursor, - line.len() - ); - self.line = line.to_string(); - self.cursor = cursor; + self.line.set_line_and_cursor(line, cursor); } /// Call this after changing modifying the line buffer. @@ -698,9 +573,9 @@ impl<'term> LineEditor<'term> { let last_matching_line; let last_cursor; - if let Some(result) = host - .history() - .search(history_pos, *style, *direction, &self.line) + if let Some(result) = + host.history() + .search(history_pos, *style, *direction, self.line.get_line()) { self.history_pos.replace(result.idx); last_matching_line = result.line.to_string(); @@ -733,7 +608,6 @@ impl<'term> LineEditor<'term> { // Not yet searching, so we start a new search // with an empty pattern self.line.clear(); - self.cursor = 0; self.history_pos.take(); } @@ -752,9 +626,9 @@ impl<'term> LineEditor<'term> { }, }; - let search_result = host - .history() - .search(history_pos, style, direction, &self.line); + let search_result = + host.history() + .search(history_pos, style, direction, self.line.get_line()); let last_matching_line; let last_cursor; @@ -836,25 +710,20 @@ impl<'term> LineEditor<'term> { Action::Move(movement) => { self.clear_completion(); self.cancel_search_state(); - self.cursor = self.eval_movement(movement); + self.line.exec_movement(movement); } Action::InsertChar(rep, c) => { self.clear_completion(); for _ in 0..rep { - self.line.insert(self.cursor, c); - let mut cursor = GraphemeCursor::new(self.cursor, self.line.len(), false); - if let Ok(Some(pos)) = cursor.next_boundary(&self.line, 0) { - self.cursor = pos; - } + self.line.insert_char(c); } self.reapply_search_pattern(host); } Action::InsertText(rep, text) => { self.clear_completion(); for _ in 0..rep { - self.line.insert_str(self.cursor, &text); - self.cursor += text.len(); + self.line.insert_text(&text); } self.reapply_search_pattern(host); } @@ -870,18 +739,16 @@ impl<'term> LineEditor<'term> { let prior_idx = cur_pos.saturating_sub(1); if let Some(prior) = host.history().get(prior_idx) { self.history_pos = Some(prior_idx); - self.line = prior.to_string(); - self.cursor = self.line.len(); + self.line.set_line_and_cursor(&prior, prior.len()); } } else if let Some(last) = host.history().last() { - self.bottom_line = Some(self.line.clone()); + self.bottom_line = Some(self.line.get_line().clone()); self.history_pos = Some(last); - self.line = host + let line = host .history() .get(last) - .expect("History::last and History::get to be consistent") - .to_string(); - self.cursor = self.line.len(); + .expect("History::last and History::get to be consistent"); + self.line.set_line_and_cursor(&line, line.len()) } } Action::HistoryNext => { @@ -892,14 +759,11 @@ impl<'term> LineEditor<'term> { let next_idx = cur_pos.saturating_add(1); if let Some(next) = host.history().get(next_idx) { self.history_pos = Some(next_idx); - self.line = next.to_string(); - self.cursor = self.line.len(); + self.line.set_line_and_cursor(&next, next.len()); } else if let Some(bottom) = self.bottom_line.take() { - self.line = bottom; - self.cursor = self.line.len(); + self.line.set_line_and_cursor(&bottom, bottom.len()); } else { self.line.clear(); - self.cursor = 0; } } } @@ -915,18 +779,17 @@ impl<'term> LineEditor<'term> { self.cancel_search_state(); if self.completion.is_none() { - let candidates = host.complete(&self.line, self.cursor); + let candidates = host.complete(self.line.get_line(), self.line.get_cursor()); if !candidates.is_empty() { let state = CompletionState { candidates, index: 0, - original_line: self.line.clone(), - original_cursor: self.cursor, + original_line: self.line.get_line().clone(), + original_cursor: self.line.get_cursor(), }; let (cursor, line) = state.current(); - self.cursor = cursor; - self.line = line; + self.line.set_line_and_cursor(&line, cursor); // If there is only a single completion then don't // leave us in a state where we just cycle on the @@ -938,8 +801,7 @@ impl<'term> LineEditor<'term> { } else if let Some(state) = self.completion.as_mut() { state.next(); let (cursor, line) = state.current(); - self.cursor = cursor; - self.line = line; + self.line.set_line_and_cursor(&line, cursor); } } } @@ -949,7 +811,6 @@ impl<'term> LineEditor<'term> { fn read_line_impl(&mut self, host: &mut dyn LineEditorHost) -> Result> { self.line.clear(); - self.cursor = 0; self.history_pos = None; self.bottom_line = None; self.clear_completion(); @@ -964,14 +825,14 @@ impl<'term> LineEditor<'term> { match self.state { EditorState::Searching { .. } | EditorState::Editing => {} EditorState::Cancelled => return Ok(None), - EditorState::Accepted => return Ok(Some(self.line.clone())), + EditorState::Accepted => return Ok(Some(self.line.get_line().clone())), EditorState::Inactive => bail!("editor is inactive during read line!?"), } } else { self.render(host)?; } } - Ok(Some(self.line.clone())) + Ok(Some(self.line.get_line().clone())) } } From e0c46e359c4a18562c00372fe25d387ca36d636f Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Wed, 12 Jun 2024 21:40:34 +0200 Subject: [PATCH 05/12] refactor: migrate search field to LineEditBuffer --- mux/src/pane.rs | 17 +++++ termwiz/src/lineedit/buffer.rs | 14 +++- termwiz/src/lineedit/mod.rs | 2 +- wezterm-gui/src/overlay/copy.rs | 120 ++++++++++++++++---------------- 4 files changed, 88 insertions(+), 65 deletions(-) diff --git a/mux/src/pane.rs b/mux/src/pane.rs index dcea9ca250b..85d08144635 100644 --- a/mux/src/pane.rs +++ b/mux/src/pane.rs @@ -87,6 +87,23 @@ impl std::ops::DerefMut for Pattern { } } +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub enum PatternType { + CaseSensitiveString, + CaseInSensitiveString, + Regex, +} + +impl From<&Pattern> for PatternType { + fn from(value: &Pattern) -> Self { + match value { + Pattern::CaseSensitiveString(_) => PatternType::CaseSensitiveString, + Pattern::CaseInSensitiveString(_) => PatternType::CaseInSensitiveString, + Pattern::Regex(_) => PatternType::Regex, + } + } +} + /// Why a close request is being made #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum CloseReason { diff --git a/termwiz/src/lineedit/buffer.rs b/termwiz/src/lineedit/buffer.rs index 719ab8fb259..0d5453228d2 100644 --- a/termwiz/src/lineedit/buffer.rs +++ b/termwiz/src/lineedit/buffer.rs @@ -9,13 +9,21 @@ pub struct LineEditBuffer { cursor: usize, } -impl LineEditBuffer { - pub fn new() -> Self { +impl Default for LineEditBuffer { + fn default() -> Self { Self { line: String::new(), cursor: 0, } } +} + +impl LineEditBuffer { + pub fn new(line: &str, cursor: usize) -> Self { + let mut buffer = Self::default(); + buffer.set_line_and_cursor(line, cursor); + return buffer; + } pub fn get_line(&self) -> &String { return &self.line; @@ -40,7 +48,7 @@ impl LineEditBuffer { pub fn set_line_and_cursor(&mut self, line: &str, cursor: usize) { assert!( - cursor < line.len(), + cursor <= line.len(), "cursor {} is outside the byte length of the new line of length {}", cursor, line.len() diff --git a/termwiz/src/lineedit/mod.rs b/termwiz/src/lineedit/mod.rs index c65f0f2f526..1c5d7fe6d3e 100644 --- a/termwiz/src/lineedit/mod.rs +++ b/termwiz/src/lineedit/mod.rs @@ -153,7 +153,7 @@ impl<'term> LineEditor<'term> { Self { terminal, prompt: "> ".to_owned(), - line: LineEditBuffer::new(), + line: LineEditBuffer::default(), history_pos: None, bottom_line: None, completion: None, diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index aaa0839d7b0..8ee371ba1e3 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -7,7 +7,7 @@ use config::keyassignment::{ }; use mux::domain::DomainId; use mux::pane::{ - CachePolicy, ForEachPaneLogicalLine, LogicalLine, Pane, PaneId, Pattern, + CachePolicy, ForEachPaneLogicalLine, LogicalLine, Pane, PaneId, Pattern, PatternType, PerformAssignmentResult, SearchResult, WithPaneLines, }; use mux::renderable::*; @@ -21,6 +21,7 @@ use std::sync::Arc; use std::time::Duration; use termwiz::cell::{Cell, CellAttributes}; use termwiz::color::AnsiColor; +use termwiz::lineedit::{LineEditBuffer, Movement}; use termwiz::surface::{CursorVisibility, SequenceNo, SEQ_ZERO}; use unicode_segmentation::*; use url::Url; @@ -65,8 +66,8 @@ struct CopyRenderable { window: ::window::Window, /// The text that the user entered - pattern: Pattern, - search_cursor_x: usize, + pattern_type: PatternType, + search_line: LineEditBuffer, /// The most recently queried set of matches results: Vec, by_line: HashMap>, @@ -135,8 +136,8 @@ impl CopyOverlay { } else { params.pattern }; + let search_line = LineEditBuffer::new(&pattern, unicode_column_width(&pattern, None)); - let search_cursor_x = unicode_column_width(&pattern, None); let mut render = CopyRenderable { cursor, window, @@ -151,8 +152,8 @@ impl CopyOverlay { last_result_seqno: SEQ_ZERO, last_bar_pos: None, tab_id, - pattern, - search_cursor_x, + pattern_type: PatternType::from(&pattern), + search_line, editing_search: params.editing_search, result_pos: None, selection_mode: SelectionMode::Cell, @@ -175,7 +176,7 @@ impl CopyOverlay { pub fn get_params(&self) -> CopyModeParams { let render = self.render.lock(); CopyModeParams { - pattern: render.pattern.clone(), + pattern: render.get_pattern(), editing_search: render.editing_search, } } @@ -183,8 +184,11 @@ impl CopyOverlay { pub fn apply_params(&self, params: CopyModeParams) { let mut render = self.render.lock(); render.editing_search = params.editing_search; - if render.pattern != params.pattern { - render.pattern = params.pattern; + if render.get_pattern() != params.pattern { + render.pattern_type = PatternType::from(¶ms.pattern); + render + .search_line + .set_line_and_cursor(¶ms.pattern, params.pattern.len()); render.schedule_update_search(); } let search_row = render.compute_search_row(); @@ -298,18 +302,16 @@ impl CopyRenderable { self.by_line.clear(); self.result_pos.take(); - SAVED_PATTERN - .lock() - .insert(self.tab_id, self.pattern.clone()); + SAVED_PATTERN.lock().insert(self.tab_id, self.get_pattern()); let bar_pos = self.compute_search_row(); self.dirty_results.add(bar_pos); self.last_result_seqno = self.delegate.get_current_seqno(); - if !self.pattern.is_empty() { + let pattern = self.get_pattern(); + if !pattern.is_empty() { let pane: Arc = self.delegate.clone(); let window = self.window.clone(); - let pattern = self.pattern.clone(); let dims = pane.get_dimensions(); let end = dims.scrollback_top + dims.scrollback_rows as StableRowIndex; @@ -355,7 +357,7 @@ impl CopyRenderable { range: Range, ) { self.window.invalidate(); - if pattern != self.pattern { + if pattern != self.get_pattern() { return; } let is_first = self.results.is_empty(); @@ -632,9 +634,20 @@ impl CopyRenderable { } } + fn get_pattern(&self) -> Pattern { + return match self.pattern_type { + PatternType::CaseSensitiveString => { + Pattern::CaseSensitiveString(self.search_line.get_line().to_owned()) + } + PatternType::CaseInSensitiveString => { + Pattern::CaseInSensitiveString(self.search_line.get_line().to_owned()) + } + PatternType::Regex => Pattern::Regex(self.search_line.get_line().to_owned()), + }; + } + fn clear_pattern(&mut self) { - self.pattern.clear(); - self.search_cursor_x = 0; + self.search_line.clear(); self.update_search(); } @@ -676,12 +689,12 @@ impl CopyRenderable { } fn cycle_match_type(&mut self) { - let pattern = match &self.pattern { - Pattern::CaseSensitiveString(s) => Pattern::CaseInSensitiveString(s.clone()), - Pattern::CaseInSensitiveString(s) => Pattern::Regex(s.clone()), - Pattern::Regex(s) => Pattern::CaseSensitiveString(s.clone()), + let pattern_type = match &self.pattern_type { + PatternType::CaseSensitiveString => PatternType::CaseInSensitiveString, + PatternType::CaseInSensitiveString => PatternType::Regex, + PatternType::Regex => PatternType::CaseSensitiveString, }; - self.pattern = pattern; + self.pattern_type = pattern_type; self.schedule_update_search(); } @@ -1096,7 +1109,7 @@ impl Pane for CopyOverlay { fn send_paste(&self, text: &str) -> anyhow::Result<()> { // paste into the search bar let mut r = self.render.lock(); - r.pattern.push_str(text); + r.search_line.insert_text(text); r.schedule_update_search(); Ok(()) } @@ -1147,45 +1160,23 @@ impl Pane for CopyOverlay { (KeyCode::Char(c), KeyModifiers::NONE) | (KeyCode::Char(c), KeyModifiers::SHIFT) => { // Type to add to the pattern - if render.search_cursor_x == unicode_column_width(&render.pattern, None) { - render.pattern.push(c); - } else { - let (offset, _) = render - .pattern - .grapheme_indices(true) - .nth(render.search_cursor_x) - .unwrap(); - render.pattern.insert(offset, c); - } - render.search_cursor_x += 1; + render.search_line.insert_char(c); render.schedule_update_search(); } (KeyCode::Backspace, KeyModifiers::NONE) => { // Backspace to edit the pattern - if render.pattern.len() > 0 && render.search_cursor_x > 0 { - let position = render.search_cursor_x; - let (offset, _) = render - .pattern - .grapheme_indices(true) - .nth(position - 1) - .unwrap(); - render.pattern.remove(offset); - if render.search_cursor_x > 0 { - render.search_cursor_x -= 1; - } - render.schedule_update_search(); - } + render + .search_line + .kill_text(Movement::BackwardChar(1), Movement::BackwardChar(1)); + + render.schedule_update_search(); } (KeyCode::LeftArrow, KeyModifiers::NONE) => { - if render.search_cursor_x > 0 { - render.search_cursor_x -= 1; - } + render.search_line.exec_movement(Movement::BackwardChar(1)); } (KeyCode::RightArrow, KeyModifiers::NONE) => { - if render.search_cursor_x < unicode_column_width(&render.pattern, None) { - render.search_cursor_x += 1; - } + render.search_line.exec_movement(Movement::ForwardChar(1)); } _ => {} } @@ -1297,8 +1288,14 @@ impl Pane for CopyOverlay { let renderer = self.render.lock(); if renderer.editing_search { // place in the search box + // Padding between the start of the editable line and the left side of the terminal + const SEARCH_CURSOR_PADDING: usize = 8; + let cursor = unicode_column_width( + &renderer.search_line.get_line()[0..renderer.search_line.get_cursor()], + None, + ); StableCursorPosition { - x: 8 + renderer.search_cursor_x, + x: SEARCH_CURSOR_PADDING + cursor, y: renderer.compute_search_row(), shape: termwiz::surface::CursorShape::SteadyBlock, visibility: termwiz::surface::CursorVisibility::Visible, @@ -1373,13 +1370,14 @@ impl Pane for CopyOverlay { let stable_idx = idx as StableRowIndex + first_row; self.renderer.dirty_results.remove(stable_idx); + let pattern = self.renderer.get_pattern(); if stable_idx == self.search_row - && (self.renderer.editing_search || !self.renderer.pattern.is_empty()) + && (self.renderer.editing_search || !pattern.is_empty()) { // Replace with search UI let rev = CellAttributes::default().set_reverse(true).clone(); line.fill_range(0..self.dims.cols, &Cell::new(' ', rev.clone()), SEQ_ZERO); - let mode = &match self.renderer.pattern { + let mode = &match pattern { Pattern::CaseSensitiveString(_) => "case-sensitive", Pattern::CaseInSensitiveString(_) => "ignore-case", Pattern::Regex(_) => "regex", @@ -1396,7 +1394,7 @@ impl Pane for CopyOverlay { 0, &format!( "Search: {} ({}/{} matches. {}{remain})", - *self.renderer.pattern, + *pattern, self.renderer.result_pos.map(|x| x + 1).unwrap_or(0), self.renderer.results.len(), mode @@ -1475,12 +1473,12 @@ impl Pane for CopyOverlay { for (idx, line) in lines.iter_mut().enumerate() { let stable_idx = idx as StableRowIndex + top; renderer.dirty_results.remove(stable_idx); - if stable_idx == search_row && (renderer.editing_search || !renderer.pattern.is_empty()) - { + let pattern = renderer.get_pattern(); + if stable_idx == search_row && (renderer.editing_search || !pattern.is_empty()) { // Replace with search UI let rev = CellAttributes::default().set_reverse(true).clone(); line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()), SEQ_ZERO); - let mode = &match renderer.pattern { + let mode = &match pattern { Pattern::CaseSensitiveString(_) => "case-sensitive", Pattern::CaseInSensitiveString(_) => "ignore-case", Pattern::Regex(_) => "regex", @@ -1489,7 +1487,7 @@ impl Pane for CopyOverlay { 0, &format!( "Search: {} ({}/{} matches. {})", - *renderer.pattern, + *pattern, renderer.result_pos.map(|x| x + 1).unwrap_or(0), renderer.results.len(), mode From f9e4d8285e730c3c65f463fb7d90b52952895d75 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Wed, 12 Jun 2024 22:21:30 +0200 Subject: [PATCH 06/12] feat: add key bindings for search field --- wezterm-gui/src/overlay/copy.rs | 57 ++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 8ee371ba1e3..ad39c4a89f6 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -1164,20 +1164,69 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::Backspace, KeyModifiers::NONE) => { - // Backspace to edit the pattern + (KeyCode::Char('H'), KeyModifiers::CTRL) + | (KeyCode::Backspace, KeyModifiers::NONE) => { render .search_line .kill_text(Movement::BackwardChar(1), Movement::BackwardChar(1)); render.schedule_update_search(); } - (KeyCode::LeftArrow, KeyModifiers::NONE) => { + (KeyCode::Delete, KeyModifiers::NONE) => { + render + .search_line + .kill_text(Movement::ForwardChar(1), Movement::None); + + render.schedule_update_search(); + } + (KeyCode::Backspace, KeyModifiers::ALT) + | (KeyCode::Char('W'), KeyModifiers::CTRL) => { + render + .search_line + .kill_text(Movement::BackwardWord(1), Movement::BackwardWord(1)); + + render.schedule_update_search(); + } + (KeyCode::Backspace, KeyModifiers::SUPER) => { + render + .search_line + .kill_text(Movement::StartOfLine, Movement::StartOfLine); + + render.schedule_update_search(); + } + (KeyCode::Char('K'), KeyModifiers::CTRL) => { + render + .search_line + .kill_text(Movement::EndOfLine, Movement::EndOfLine); + + render.schedule_update_search(); + } + (KeyCode::Char('B'), KeyModifiers::CTRL) + | (KeyCode::ApplicationLeftArrow, KeyModifiers::NONE) + | (KeyCode::LeftArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::BackwardChar(1)); } - (KeyCode::RightArrow, KeyModifiers::NONE) => { + (KeyCode::Char('F'), KeyModifiers::CTRL) + | (KeyCode::ApplicationRightArrow, KeyModifiers::NONE) + | (KeyCode::RightArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::ForwardChar(1)); } + (KeyCode::Char('b'), KeyModifiers::CTRL) + | (KeyCode::ApplicationLeftArrow, KeyModifiers::ALT) + | (KeyCode::LeftArrow, KeyModifiers::ALT) => { + render.search_line.exec_movement(Movement::BackwardWord(1)); + } + (KeyCode::Char('f'), KeyModifiers::CTRL) + | (KeyCode::ApplicationRightArrow, KeyModifiers::ALT) + | (KeyCode::RightArrow, KeyModifiers::ALT) => { + render.search_line.exec_movement(Movement::ForwardWord(1)); + } + (KeyCode::Char('A'), KeyModifiers::CTRL) | (KeyCode::Home, KeyModifiers::NONE) => { + render.search_line.exec_movement(Movement::StartOfLine); + } + (KeyCode::Char('E'), KeyModifiers::CTRL) | (KeyCode::End, KeyModifiers::NONE) => { + render.search_line.exec_movement(Movement::EndOfLine); + } _ => {} } } From b5184df448343fd748f4bfeb0ad44d1dadb98d6e Mon Sep 17 00:00:00 2001 From: Ivan Moiseev <55442381+Mrreadiness@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:29:32 +0300 Subject: [PATCH 07/12] Fix crash on search reopening Co-authored-by: Ken Chou --- wezterm-gui/src/overlay/copy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index ad39c4a89f6..390c00959b0 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -136,7 +136,7 @@ impl CopyOverlay { } else { params.pattern }; - let search_line = LineEditBuffer::new(&pattern, unicode_column_width(&pattern, None)); + let search_line = LineEditBuffer::new(&pattern, pattern.len()); let mut render = CopyRenderable { cursor, From a90e5a2c941a5585b5c62446f5898bd72b040c33 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Thu, 13 Jun 2024 10:48:23 +0200 Subject: [PATCH 08/12] feat: improve cursor bounds checks --- termwiz/src/lineedit/buffer.rs | 9 ++++++--- termwiz/src/lineedit/mod.rs | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/termwiz/src/lineedit/buffer.rs b/termwiz/src/lineedit/buffer.rs index 0d5453228d2..375a2b59df2 100644 --- a/termwiz/src/lineedit/buffer.rs +++ b/termwiz/src/lineedit/buffer.rs @@ -46,12 +46,15 @@ impl LineEditBuffer { self.cursor += text.len(); } + /// The cursor position is the byte index into the line UTF-8 bytes. + /// Panics: the cursor must be the first byte in a UTF-8 code point + /// sequence or the end of the provided line. pub fn set_line_and_cursor(&mut self, line: &str, cursor: usize) { assert!( - cursor <= line.len(), - "cursor {} is outside the byte length of the new line of length {}", + line.is_char_boundary(cursor), + "cursor {} is not a char boundary of the new line {}", cursor, - line.len() + line ); self.line = line.to_string(); self.cursor = cursor; diff --git a/termwiz/src/lineedit/mod.rs b/termwiz/src/lineedit/mod.rs index 1c5d7fe6d3e..7a0cb350c3a 100644 --- a/termwiz/src/lineedit/mod.rs +++ b/termwiz/src/lineedit/mod.rs @@ -542,7 +542,8 @@ impl<'term> LineEditor<'term> { /// You don't normally need to call this unless you are defining /// a custom editor operation on the line buffer contents. /// The cursor position is the byte index into the line UTF-8 bytes. - /// Panics: the cursor must be within the bounds of the provided line. + /// Panics: the cursor must be the first byte in a UTF-8 code point + /// sequence or the end of the provided line. pub fn set_line_and_cursor(&mut self, line: &str, cursor: usize) { self.line.set_line_and_cursor(line, cursor); } From 96186b84dada62b37fbd1c5234a4f66659085411 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Fri, 14 Jun 2024 14:15:38 +0200 Subject: [PATCH 09/12] fix: refactor after review --- termwiz/src/lineedit/buffer.rs | 2 +- termwiz/src/lineedit/mod.rs | 10 +++++----- wezterm-gui/src/overlay/copy.rs | 15 ++++++--------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/termwiz/src/lineedit/buffer.rs b/termwiz/src/lineedit/buffer.rs index 375a2b59df2..1d922d1a227 100644 --- a/termwiz/src/lineedit/buffer.rs +++ b/termwiz/src/lineedit/buffer.rs @@ -25,7 +25,7 @@ impl LineEditBuffer { return buffer; } - pub fn get_line(&self) -> &String { + pub fn get_line(&self) -> &str { return &self.line; } diff --git a/termwiz/src/lineedit/mod.rs b/termwiz/src/lineedit/mod.rs index 7a0cb350c3a..8130a051b61 100644 --- a/termwiz/src/lineedit/mod.rs +++ b/termwiz/src/lineedit/mod.rs @@ -182,7 +182,7 @@ impl<'term> LineEditor<'term> { matching_line, cursor, .. - } => (matching_line, *cursor), + } => (matching_line.as_str(), *cursor), _ => (self.line.get_line(), self.line.get_cursor()), }; @@ -743,7 +743,7 @@ impl<'term> LineEditor<'term> { self.line.set_line_and_cursor(&prior, prior.len()); } } else if let Some(last) = host.history().last() { - self.bottom_line = Some(self.line.get_line().clone()); + self.bottom_line = Some(self.line.get_line().to_string()); self.history_pos = Some(last); let line = host .history() @@ -785,7 +785,7 @@ impl<'term> LineEditor<'term> { let state = CompletionState { candidates, index: 0, - original_line: self.line.get_line().clone(), + original_line: self.line.get_line().to_string(), original_cursor: self.line.get_cursor(), }; @@ -826,14 +826,14 @@ impl<'term> LineEditor<'term> { match self.state { EditorState::Searching { .. } | EditorState::Editing => {} EditorState::Cancelled => return Ok(None), - EditorState::Accepted => return Ok(Some(self.line.get_line().clone())), + EditorState::Accepted => return Ok(Some(self.line.get_line().to_string())), EditorState::Inactive => bail!("editor is inactive during read line!?"), } } else { self.render(host)?; } } - Ok(Some(self.line.get_line().clone())) + Ok(Some(self.line.get_line().to_string())) } } diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 390c00959b0..408849c9f27 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -635,15 +635,12 @@ impl CopyRenderable { } fn get_pattern(&self) -> Pattern { - return match self.pattern_type { - PatternType::CaseSensitiveString => { - Pattern::CaseSensitiveString(self.search_line.get_line().to_owned()) - } - PatternType::CaseInSensitiveString => { - Pattern::CaseInSensitiveString(self.search_line.get_line().to_owned()) - } - PatternType::Regex => Pattern::Regex(self.search_line.get_line().to_owned()), - }; + let pattern = self.search_line.get_line().to_string(); + match self.pattern_type { + PatternType::CaseSensitiveString => Pattern::CaseSensitiveString(pattern), + PatternType::CaseInSensitiveString => Pattern::CaseInSensitiveString(pattern), + PatternType::Regex => Pattern::Regex(pattern), + } } fn clear_pattern(&mut self) { From 9a4613399a72d0c07058778645f739cca9be38d5 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Fri, 14 Jun 2024 14:40:12 +0200 Subject: [PATCH 10/12] fix: remove potentially ambiguous keys --- wezterm-gui/src/overlay/copy.rs | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 408849c9f27..3bc7935c107 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -1161,8 +1161,7 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::Char('H'), KeyModifiers::CTRL) - | (KeyCode::Backspace, KeyModifiers::NONE) => { + (KeyCode::Backspace, KeyModifiers::NONE) => { render .search_line .kill_text(Movement::BackwardChar(1), Movement::BackwardChar(1)); @@ -1176,8 +1175,7 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::Backspace, KeyModifiers::ALT) - | (KeyCode::Char('W'), KeyModifiers::CTRL) => { + (KeyCode::Backspace, KeyModifiers::ALT) => { render .search_line .kill_text(Movement::BackwardWord(1), Movement::BackwardWord(1)); @@ -1191,37 +1189,26 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::Char('K'), KeyModifiers::CTRL) => { - render - .search_line - .kill_text(Movement::EndOfLine, Movement::EndOfLine); - - render.schedule_update_search(); - } - (KeyCode::Char('B'), KeyModifiers::CTRL) - | (KeyCode::ApplicationLeftArrow, KeyModifiers::NONE) + (KeyCode::ApplicationLeftArrow, KeyModifiers::NONE) | (KeyCode::LeftArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::BackwardChar(1)); } - (KeyCode::Char('F'), KeyModifiers::CTRL) - | (KeyCode::ApplicationRightArrow, KeyModifiers::NONE) + (KeyCode::ApplicationRightArrow, KeyModifiers::NONE) | (KeyCode::RightArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::ForwardChar(1)); } - (KeyCode::Char('b'), KeyModifiers::CTRL) - | (KeyCode::ApplicationLeftArrow, KeyModifiers::ALT) - | (KeyCode::LeftArrow, KeyModifiers::ALT) => { + (KeyCode::ApplicationLeftArrow, KeyModifiers::CTRL) + | (KeyCode::LeftArrow, KeyModifiers::CTRL) => { render.search_line.exec_movement(Movement::BackwardWord(1)); } - (KeyCode::Char('f'), KeyModifiers::CTRL) - | (KeyCode::ApplicationRightArrow, KeyModifiers::ALT) - | (KeyCode::RightArrow, KeyModifiers::ALT) => { + (KeyCode::ApplicationRightArrow, KeyModifiers::CTRL) + | (KeyCode::RightArrow, KeyModifiers::CTRL) => { render.search_line.exec_movement(Movement::ForwardWord(1)); } - (KeyCode::Char('A'), KeyModifiers::CTRL) | (KeyCode::Home, KeyModifiers::NONE) => { + (KeyCode::Home, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::StartOfLine); } - (KeyCode::Char('E'), KeyModifiers::CTRL) | (KeyCode::End, KeyModifiers::NONE) => { + (KeyCode::End, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::EndOfLine); } _ => {} From abfdf6f627f7c16b569f04ea5eed558cda75fa48 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Fri, 14 Jun 2024 15:31:42 +0200 Subject: [PATCH 11/12] Revert "fix: remove potentially ambiguous keys" This reverts commit 9a4613399a72d0c07058778645f739cca9be38d5. --- wezterm-gui/src/overlay/copy.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 3bc7935c107..408849c9f27 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -1161,7 +1161,8 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::Backspace, KeyModifiers::NONE) => { + (KeyCode::Char('H'), KeyModifiers::CTRL) + | (KeyCode::Backspace, KeyModifiers::NONE) => { render .search_line .kill_text(Movement::BackwardChar(1), Movement::BackwardChar(1)); @@ -1175,7 +1176,8 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::Backspace, KeyModifiers::ALT) => { + (KeyCode::Backspace, KeyModifiers::ALT) + | (KeyCode::Char('W'), KeyModifiers::CTRL) => { render .search_line .kill_text(Movement::BackwardWord(1), Movement::BackwardWord(1)); @@ -1189,26 +1191,37 @@ impl Pane for CopyOverlay { render.schedule_update_search(); } - (KeyCode::ApplicationLeftArrow, KeyModifiers::NONE) + (KeyCode::Char('K'), KeyModifiers::CTRL) => { + render + .search_line + .kill_text(Movement::EndOfLine, Movement::EndOfLine); + + render.schedule_update_search(); + } + (KeyCode::Char('B'), KeyModifiers::CTRL) + | (KeyCode::ApplicationLeftArrow, KeyModifiers::NONE) | (KeyCode::LeftArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::BackwardChar(1)); } - (KeyCode::ApplicationRightArrow, KeyModifiers::NONE) + (KeyCode::Char('F'), KeyModifiers::CTRL) + | (KeyCode::ApplicationRightArrow, KeyModifiers::NONE) | (KeyCode::RightArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::ForwardChar(1)); } - (KeyCode::ApplicationLeftArrow, KeyModifiers::CTRL) - | (KeyCode::LeftArrow, KeyModifiers::CTRL) => { + (KeyCode::Char('b'), KeyModifiers::CTRL) + | (KeyCode::ApplicationLeftArrow, KeyModifiers::ALT) + | (KeyCode::LeftArrow, KeyModifiers::ALT) => { render.search_line.exec_movement(Movement::BackwardWord(1)); } - (KeyCode::ApplicationRightArrow, KeyModifiers::CTRL) - | (KeyCode::RightArrow, KeyModifiers::CTRL) => { + (KeyCode::Char('f'), KeyModifiers::CTRL) + | (KeyCode::ApplicationRightArrow, KeyModifiers::ALT) + | (KeyCode::RightArrow, KeyModifiers::ALT) => { render.search_line.exec_movement(Movement::ForwardWord(1)); } - (KeyCode::Home, KeyModifiers::NONE) => { + (KeyCode::Char('A'), KeyModifiers::CTRL) | (KeyCode::Home, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::StartOfLine); } - (KeyCode::End, KeyModifiers::NONE) => { + (KeyCode::Char('E'), KeyModifiers::CTRL) | (KeyCode::End, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::EndOfLine); } _ => {} From 8f3fdbe78b00ad33c9e4fa45c24a1223c1518339 Mon Sep 17 00:00:00 2001 From: Ivan Moiseev Date: Fri, 14 Jun 2024 15:35:31 +0200 Subject: [PATCH 12/12] fix: remove potentially ambiguous keys --- wezterm-gui/src/overlay/copy.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 408849c9f27..e4de21b8c8e 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -1208,14 +1208,12 @@ impl Pane for CopyOverlay { | (KeyCode::RightArrow, KeyModifiers::NONE) => { render.search_line.exec_movement(Movement::ForwardChar(1)); } - (KeyCode::Char('b'), KeyModifiers::CTRL) - | (KeyCode::ApplicationLeftArrow, KeyModifiers::ALT) - | (KeyCode::LeftArrow, KeyModifiers::ALT) => { + (KeyCode::ApplicationLeftArrow, KeyModifiers::CTRL) + | (KeyCode::LeftArrow, KeyModifiers::CTRL) => { render.search_line.exec_movement(Movement::BackwardWord(1)); } - (KeyCode::Char('f'), KeyModifiers::CTRL) - | (KeyCode::ApplicationRightArrow, KeyModifiers::ALT) - | (KeyCode::RightArrow, KeyModifiers::ALT) => { + (KeyCode::ApplicationRightArrow, KeyModifiers::CTRL) + | (KeyCode::RightArrow, KeyModifiers::CTRL) => { render.search_line.exec_movement(Movement::ForwardWord(1)); } (KeyCode::Char('A'), KeyModifiers::CTRL) | (KeyCode::Home, KeyModifiers::NONE) => {