From 46f410bfdce0b64c3d94fdc0271886f30d64b151 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:36:53 -0400 Subject: [PATCH 1/7] add bashism `!term` to prefix search for last command beginning with `term` (#779) * add bashism `!term` to prefix search for last command beginning with `term` * missed doc comment * one more try with doc comments * add ability to search session first, then globally * unrelates types for list_menu test * missed on --- .typos.toml | 7 +++++++ src/engine.rs | 41 ++++++++++++++++++++++++++++++++++++++ src/menu/menu_functions.rs | 24 +++++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/.typos.toml b/.typos.toml index a1802fae..7a64bdaf 100644 --- a/.typos.toml +++ b/.typos.toml @@ -11,3 +11,10 @@ descriptio = "descriptio" ot = "ot" # for sqlite backed history wheres = "wheres" +# for list_menu tests +an1other = "an1other" +ver2y = "ver2y" +l3ine = "l3ine" +4should = "4should" +wr5ap = "wr5ap" +ine = "ine" diff --git a/src/engine.rs b/src/engine.rs index b26d3c1e..044ac27d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1495,6 +1495,8 @@ impl Reedline { fn parse_bang_command(&mut self) -> Option { let buffer = self.editor.get_buffer(); let parsed = parse_selection_char(buffer, '!'); + let parsed_prefix = parsed.prefix.unwrap_or_default().to_string(); + let parsed_marker = parsed.marker.unwrap_or_default().to_string(); if let Some(last) = parsed.remainder.chars().last() { if last != ' ' { @@ -1546,6 +1548,44 @@ impl Reedline { history.command_line.clone(), ) }), + ParseAction::BackwardPrefixSearch => { + let history_search_by_session = self + .history + .search(SearchQuery::last_with_prefix_and_cwd( + parsed.prefix.unwrap().to_string(), + self.get_history_session_id(), + )) + .unwrap_or_else(|_| Vec::new()) + .get(index.saturating_sub(1)) + .map(|history| { + ( + parsed.remainder.len(), + parsed_prefix.len() + parsed_marker.len(), + history.command_line.clone(), + ) + }); + // If we don't find any history searching by session id, then let's + // search everything, otherwise use the result from the session search + if history_search_by_session.is_none() { + eprintln!("Using global search"); + self.history + .search(SearchQuery::last_with_prefix( + parsed_prefix.clone(), + self.get_history_session_id(), + )) + .unwrap_or_else(|_| Vec::new()) + .get(index.saturating_sub(1)) + .map(|history| { + ( + parsed.remainder.len(), + parsed_prefix.len() + parsed_marker.len(), + history.command_line.clone(), + ) + }) + } else { + history_search_by_session + } + } ParseAction::ForwardSearch => self .history .search(SearchQuery { @@ -1573,6 +1613,7 @@ impl Reedline { ))) .unwrap_or_else(|_| Vec::new()) .first() + //BUGBUG: This returns the wrong results with paths with spaces in them .and_then(|history| history.command_line.split_whitespace().next_back()) .map(|token| (parsed.remainder.len(), indicator.len(), token.to_string())), }); diff --git a/src/menu/menu_functions.rs b/src/menu/menu_functions.rs index cc48d85a..bf9ded7b 100644 --- a/src/menu/menu_functions.rs +++ b/src/menu/menu_functions.rs @@ -17,6 +17,8 @@ pub struct ParseResult<'buffer> { pub marker: Option<&'buffer str>, /// Direction of the search based on the marker pub action: ParseAction, + /// Prefix to search for + pub prefix: Option<&'buffer str>, } /// Direction of the index found in the string @@ -30,6 +32,8 @@ pub enum ParseAction { LastToken, /// Last executed command. LastCommand, + /// Backward search for a prefix + BackwardPrefixSearch, } /// Splits a string that contains a marker character @@ -46,7 +50,8 @@ pub enum ParseAction { /// remainder: "this is an example", /// index: Some(10), /// marker: Some("!10"), -/// action: ParseAction::ForwardSearch +/// action: ParseAction::ForwardSearch, +/// prefix: None, /// } /// ) /// @@ -58,6 +63,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult { index: None, marker: None, action: ParseAction::ForwardSearch, + prefix: None, }; } @@ -75,6 +81,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult { index: Some(0), marker: Some(&buffer[index..index + 2 * marker.len_utf8()]), action: ParseAction::LastCommand, + prefix: None, } } #[cfg(feature = "bashisms")] @@ -84,6 +91,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult { index: Some(0), marker: Some(&buffer[index..index + 2]), action: ParseAction::LastToken, + prefix: None, } } Some(&x) if x.is_ascii_digit() || x == '-' => { @@ -106,6 +114,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult { index: Some(count), marker: Some(&buffer[index..index + size]), action, + prefix: None, }; } } @@ -114,14 +123,26 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult { index: Some(count), marker: Some(&buffer[index..index + size]), action, + prefix: None, }; } + #[cfg(feature = "bashisms")] + Some(&x) if x.is_ascii_alphabetic() => { + return ParseResult { + remainder: &buffer[0..index], + index: Some(0), + marker: Some(&buffer[index..index + marker.len_utf8()]), + action: ParseAction::BackwardPrefixSearch, + prefix: Some(&buffer[index + marker.len_utf8()..buffer.len()]), + } + } None => { return ParseResult { remainder: &buffer[0..index], index: Some(0), marker: Some(&buffer[index..buffer.len()]), action, + prefix: Some(&buffer[index..buffer.len()]), } } _ => {} @@ -135,6 +156,7 @@ pub fn parse_selection_char(buffer: &str, marker: char) -> ParseResult { index: None, marker: None, action, + prefix: None, } } From ced60e57cd7c213e8996748800ae10cf8515b93f Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Mon, 22 Apr 2024 15:04:37 +0200 Subject: [PATCH 2/7] Remove debug print (#784) --- src/engine.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine.rs b/src/engine.rs index 044ac27d..279545ec 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1567,7 +1567,6 @@ impl Reedline { // If we don't find any history searching by session id, then let's // search everything, otherwise use the result from the session search if history_search_by_session.is_none() { - eprintln!("Using global search"); self.history .search(SearchQuery::last_with_prefix( parsed_prefix.clone(), From cc9a957184800065bbc9741ef483a2dee19c2106 Mon Sep 17 00:00:00 2001 From: maxomatic458 <104733404+maxomatic458@users.noreply.github.com> Date: Mon, 22 Apr 2024 20:14:01 +0200 Subject: [PATCH 3/7] fix ide menu not reporting correct required_lines (#781) * fix ide menu not reporting correct required_lines * format --- examples/ide_completions.rs | 2 +- src/menu/ide_menu.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ide_completions.rs b/examples/ide_completions.rs index f6e69b7b..a209a167 100644 --- a/examples/ide_completions.rs +++ b/examples/ide_completions.rs @@ -33,7 +33,7 @@ fn main() -> io::Result<()> { // Max width of the completion box, including the border let max_completion_width: u16 = 50; // Max height of the completion box, including the border - let max_completion_height = u16::MAX; + let max_completion_height: u16 = u16::MAX; // Padding inside of the completion box (on the left and right side) let padding: u16 = 0; // Whether to draw the default border around the completion box diff --git a/src/menu/ide_menu.rs b/src/menu/ide_menu.rs index 2ce711f6..5dc325f7 100644 --- a/src/menu/ide_menu.rs +++ b/src/menu/ide_menu.rs @@ -819,6 +819,7 @@ impl Menu for IdeMenu { fn menu_required_lines(&self, _terminal_columns: u16) -> u16 { self.get_rows() + .min(self.default_details.max_completion_height) } fn menu_string(&self, available_lines: u16, use_ansi_coloring: bool) -> String { From 455b9a3a22f3840719d993ea715b57790a77b509 Mon Sep 17 00:00:00 2001 From: Benoit de Chezelles Date: Tue, 23 Apr 2024 17:13:32 +0200 Subject: [PATCH 4/7] Fix (properly) the logic around prompt re-use & Host Command handling (#770) * Fix (properly) the logic around prompt re-use & Host Command handling * Move to dedicated selector function, add tests * cargo fmt --- src/engine.rs | 34 ++++++----- src/painting/mod.rs | 2 +- src/painting/painter.rs | 122 +++++++++++++++++++++++++++++++++------- 3 files changed, 122 insertions(+), 36 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 279545ec..6b323c1e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -27,7 +27,7 @@ use { FileBackedHistory, History, HistoryCursor, HistoryItem, HistoryItemId, HistoryNavigationQuery, HistorySessionId, SearchDirection, SearchQuery, }, - painting::{Painter, PromptLines}, + painting::{Painter, PainterSuspendedState, PromptLines}, prompt::{PromptEditMode, PromptHistorySearchStatus}, result::{ReedlineError, ReedlineErrorVariants}, terminal_extensions::{bracketed_paste::BracketedPasteGuard, kitty::KittyProtocolGuard}, @@ -109,8 +109,9 @@ pub struct Reedline { history_cursor_on_excluded: bool, input_mode: InputMode, - // Yielded to the host program after a `ReedlineEvent::ExecuteHostCommand`, thus redraw in-place - executing_host_command: bool, + // State of the painter after a `ReedlineEvent::ExecuteHostCommand` was requested, used after + // execution to decide if we can re-use the previous prompt or paint a new one. + suspended_state: Option, // Validator validator: Option>, @@ -210,7 +211,7 @@ impl Reedline { history_excluded_item: None, history_cursor_on_excluded: false, input_mode: InputMode::Regular, - executing_host_command: false, + suspended_state: None, painter, transient_prompt: None, edit_mode, @@ -671,12 +672,14 @@ impl Reedline { /// Helper implementing the logic for [`Reedline::read_line()`] to be wrapped /// in a `raw_mode` context. fn read_line_helper(&mut self, prompt: &dyn Prompt) -> Result { - if self.executing_host_command { - self.executing_host_command = false; - } else { - self.painter.initialize_prompt_position()?; - self.hide_hints = false; + self.painter + .initialize_prompt_position(self.suspended_state.as_ref())?; + if self.suspended_state.is_some() { + // Last editor was suspended to run a ExecuteHostCommand event, + // we are resuming operation now. + self.suspended_state = None; } + self.hide_hints = false; self.repaint(prompt)?; @@ -773,8 +776,11 @@ impl Reedline { for event in reedline_events.drain(..) { match self.handle_event(prompt, event)? { EventStatus::Exits(signal) => { - if !self.executing_host_command { - // Move the cursor below the input area, for external commands or new read_line call + // Check if we are merely suspended (to process an ExecuteHostCommand event) + // or if we're about to quit the editor. + if self.suspended_state.is_none() { + // We are about to quit the editor, move the cursor below the input + // area, for external commands or new read_line call self.painter.move_cursor_to_end()?; } return Ok(signal); @@ -851,8 +857,7 @@ impl Reedline { Ok(EventStatus::Handled) } ReedlineEvent::ExecuteHostCommand(host_command) => { - // TODO: Decide if we need to do something special to have a nicer painter state on the next go - self.executing_host_command = true; + self.suspended_state = Some(self.painter.state_before_suspension()); Ok(EventStatus::Exits(Signal::Success(host_command))) } ReedlineEvent::Edit(commands) => { @@ -1122,8 +1127,7 @@ impl Reedline { } } ReedlineEvent::ExecuteHostCommand(host_command) => { - // TODO: Decide if we need to do something special to have a nicer painter state on the next go - self.executing_host_command = true; + self.suspended_state = Some(self.painter.state_before_suspension()); Ok(EventStatus::Exits(Signal::Success(host_command))) } ReedlineEvent::Edit(commands) => { diff --git a/src/painting/mod.rs b/src/painting/mod.rs index 87c5472a..4c17c9a8 100644 --- a/src/painting/mod.rs +++ b/src/painting/mod.rs @@ -3,7 +3,7 @@ mod prompt_lines; mod styled_text; mod utils; -pub use painter::Painter; +pub use painter::{Painter, PainterSuspendedState}; pub(crate) use prompt_lines::PromptLines; pub use styled_text::StyledText; pub(crate) use utils::estimate_single_line_wraps; diff --git a/src/painting/painter.rs b/src/painting/painter.rs index 4986a961..e0216b50 100644 --- a/src/painting/painter.rs +++ b/src/painting/painter.rs @@ -14,6 +14,7 @@ use { QueueableCommand, }, std::io::{Result, Write}, + std::ops::RangeInclusive, }; #[cfg(feature = "external_printer")] use {crate::LineBuffer, crossterm::cursor::MoveUp}; @@ -49,6 +50,42 @@ fn skip_buffer_lines(string: &str, skip: usize, offset: Option) -> &str { /// the type used by crossterm operations pub type W = std::io::BufWriter; +#[derive(Debug, PartialEq, Eq)] +pub struct PainterSuspendedState { + previous_prompt_rows_range: RangeInclusive, +} + +#[derive(Debug, PartialEq, Eq)] +enum PromptRowSelector { + UseExistingPrompt { start_row: u16 }, + MakeNewPrompt { new_row: u16 }, +} + +// Selects the row where the next prompt should start on, taking into account and whether it should re-use a previous +// prompt. +fn select_prompt_row( + suspended_state: Option<&PainterSuspendedState>, + (column, row): (u16, u16), // NOTE: Positions are 0 based here +) -> PromptRowSelector { + if let Some(painter_state) = suspended_state { + // The painter was suspended, try to re-use the last prompt position to avoid + // unnecessarily making new prompts. + if painter_state.previous_prompt_rows_range.contains(&row) { + // Cursor is still in the range of the previous prompt, re-use it. + let start_row = *painter_state.previous_prompt_rows_range.start(); + return PromptRowSelector::UseExistingPrompt { start_row }; + } else { + // There was some output or cursor is outside of the range of previous prompt make a + // fresh new prompt. + } + } + + // Assumption: if the cursor is not on the zeroth column, + // there is content we want to leave intact, thus advance to the next row. + let new_row = if column > 0 { row + 1 } else { row }; + PromptRowSelector::MakeNewPrompt { new_row } +} + /// Implementation of the output to the terminal pub struct Painter { // Stdout @@ -85,12 +122,26 @@ impl Painter { self.screen_height().saturating_sub(self.prompt_start_row) } + /// Returns the state necessary before suspending the painter (to run a host command event). + /// + /// This state will be used to re-initialize the painter to re-use last prompt if possible. + pub fn state_before_suspension(&self) -> PainterSuspendedState { + let start_row = self.prompt_start_row; + let final_row = start_row + self.last_required_lines; + PainterSuspendedState { + previous_prompt_rows_range: start_row..=final_row, + } + } + /// Sets the prompt origin position and screen size for a new line editor /// invocation /// /// Not to be used for resizes during a running line editor, use /// [`Painter::handle_resize()`] instead - pub(crate) fn initialize_prompt_position(&mut self) -> Result<()> { + pub(crate) fn initialize_prompt_position( + &mut self, + suspended_state: Option<&PainterSuspendedState>, + ) -> Result<()> { // Update the terminal size self.terminal_size = { let size = terminal::size()?; @@ -102,26 +153,26 @@ impl Painter { size } }; - // Cursor positions are 0 based here. - let (column, row) = cursor::position()?; - // Assumption: if the cursor is not on the zeroth column, - // there is content we want to leave intact, thus advance to the next row - let new_row = if column > 0 { row + 1 } else { row }; - // If we are on the last line and would move beyond the last line due to - // the condition above, we need to make room for the prompt. - // Otherwise printing the prompt would scroll of the stored prompt - // origin, causing issues after repaints. - let new_row = if new_row == self.screen_height() { - self.print_crlf()?; - new_row.saturating_sub(1) - } else { - new_row + let prompt_selector = select_prompt_row(suspended_state, cursor::position()?); + self.prompt_start_row = match prompt_selector { + PromptRowSelector::UseExistingPrompt { start_row } => start_row, + PromptRowSelector::MakeNewPrompt { new_row } => { + // If we are on the last line and would move beyond the last line, we need to make + // room for the prompt. + // Otherwise printing the prompt would scroll off the stored prompt + // origin, causing issues after repaints. + if new_row == self.screen_height() { + self.print_crlf()?; + new_row.saturating_sub(1) + } else { + new_row + } + } }; - self.prompt_start_row = new_row; Ok(()) } - /// Main pain painter for the prompt and buffer + /// Main painter for the prompt and buffer /// It queues all the actions required to print the prompt together with /// lines that make the buffer. /// Using the prompt lines object in this function it is estimated how the @@ -461,7 +512,7 @@ impl Painter { self.stdout.queue(cursor::Show)?; self.stdout.flush()?; - self.initialize_prompt_position() + self.initialize_prompt_position(None) } pub(crate) fn clear_scrollback(&mut self) -> Result<()> { @@ -470,11 +521,11 @@ impl Painter { .queue(crossterm::terminal::Clear(ClearType::Purge))? .queue(cursor::MoveTo(0, 0))? .flush()?; - self.initialize_prompt_position() + self.initialize_prompt_position(None) } // The prompt is moved to the end of the buffer after the event was handled - // If the prompt is in the middle of a multiline buffer, then the output to stdout + // If the prompt is in the middle of a multiline buffer, then the next output to stdout // could overwrite the buffer writing pub(crate) fn move_cursor_to_end(&mut self) -> Result<()> { let final_row = self.prompt_start_row + self.last_required_lines; @@ -619,4 +670,35 @@ mod tests { assert_eq!(skip_buffer_lines(string, 0, Some(0)), "sentence1",); assert_eq!(skip_buffer_lines(string, 1, Some(0)), "sentence2",); } + + #[test] + fn test_select_new_prompt_with_no_state_no_output() { + assert_eq!( + select_prompt_row(None, (0, 12)), + PromptRowSelector::MakeNewPrompt { new_row: 12 } + ); + } + + #[test] + fn test_select_new_prompt_with_no_state_but_output() { + assert_eq!( + select_prompt_row(None, (3, 12)), + PromptRowSelector::MakeNewPrompt { new_row: 13 } + ); + } + + #[test] + fn test_select_existing_prompt() { + let state = PainterSuspendedState { + previous_prompt_rows_range: 11..=13, + }; + assert_eq!( + select_prompt_row(Some(&state), (0, 12)), + PromptRowSelector::UseExistingPrompt { start_row: 11 } + ); + assert_eq!( + select_prompt_row(Some(&state), (3, 12)), + PromptRowSelector::UseExistingPrompt { start_row: 11 } + ); + } } From 4cf8c75d68ccb51451dc483b07dbf3d30ab1da10 Mon Sep 17 00:00:00 2001 From: sigoden Date: Wed, 24 Apr 2024 20:01:55 +0800 Subject: [PATCH 5/7] fix: unexpected spaces after large buffer input (#783) close #297 --- src/painting/painter.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/painting/painter.rs b/src/painting/painter.rs index e0216b50..233e98f8 100644 --- a/src/painting/painter.rs +++ b/src/painting/painter.rs @@ -94,6 +94,7 @@ pub struct Painter { terminal_size: (u16, u16), last_required_lines: u16, large_buffer: bool, + after_cursor_lines: Option, } impl Painter { @@ -104,6 +105,7 @@ impl Painter { terminal_size: (0, 0), last_required_lines: 0, large_buffer: false, + after_cursor_lines: None, } } @@ -232,10 +234,15 @@ impl Painter { self.print_small_buffer(prompt, lines, menu, use_ansi_coloring)?; } - // The last_required_lines is used to move the cursor at the end where stdout - // can print without overwriting the things written during the painting + // The last_required_lines is used to calculate safe range of the current prompt. self.last_required_lines = required_lines; + self.after_cursor_lines = if !lines.after_cursor.is_empty() { + Some(lines.after_cursor.to_string()) + } else { + None + }; + self.stdout.queue(RestorePosition)?; if let Some(shapes) = cursor_config { @@ -525,18 +532,13 @@ impl Painter { } // The prompt is moved to the end of the buffer after the event was handled - // If the prompt is in the middle of a multiline buffer, then the next output to stdout - // could overwrite the buffer writing pub(crate) fn move_cursor_to_end(&mut self) -> Result<()> { - let final_row = self.prompt_start_row + self.last_required_lines; - let scroll = final_row.saturating_sub(self.screen_height() - 1); - if scroll != 0 { - self.queue_universal_scroll(scroll)?; + if let Some(after_cursor) = &self.after_cursor_lines { + self.stdout + .queue(Clear(ClearType::FromCursorDown))? + .queue(Print(after_cursor))?; } - self.stdout - .queue(MoveTo(0, final_row.min(self.screen_height() - 1)))?; - - self.stdout.flush() + self.print_crlf() } /// Prints an external message From 170866730368e51f6eff67eec52660556a3252bb Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Tue, 30 Apr 2024 15:11:46 -0700 Subject: [PATCH 6/7] Bump version for `0.32.0` release (#785) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63b0b361..b2aa8d37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -684,7 +684,7 @@ dependencies = [ [[package]] name = "reedline" -version = "0.31.0" +version = "0.32.0" dependencies = [ "arboard", "chrono", diff --git a/Cargo.toml b/Cargo.toml index cee4d8f8..85d0920c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" name = "reedline" repository = "https://github.com/nushell/reedline" rust-version = "1.62.1" -version = "0.31.0" +version = "0.32.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] From a580ea56d4e5a889468b2969d2a1534379504ab6 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 16 May 2024 07:36:16 -0500 Subject: [PATCH 7/7] fix some new clippy warnings (#790) * fix some new clippy warnings * add sophia's change --- Cargo.toml | 4 ++-- src/core_editor/clip_buffer.rs | 4 +++- src/menu/description_menu.rs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 85d0920c..80efd50f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] -authors = ["JT ", "The Nushell Project Developers"] +authors = ["The Nushell Project Developers"] description = "A readline-like crate for CLI text input" edition = "2021" license = "MIT" name = "reedline" repository = "https://github.com/nushell/reedline" -rust-version = "1.62.1" +rust-version = "1.63.0" version = "0.32.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/core_editor/clip_buffer.rs b/src/core_editor/clip_buffer.rs index 8297a3bb..0bb17e72 100644 --- a/src/core_editor/clip_buffer.rs +++ b/src/core_editor/clip_buffer.rs @@ -6,10 +6,12 @@ pub trait Clipboard: Send { fn get(&mut self) -> (String, ClipboardMode); + #[allow(dead_code)] fn clear(&mut self) { self.set("", ClipboardMode::Normal); } + #[allow(dead_code)] fn len(&mut self) -> usize { self.get().0.len() } @@ -41,7 +43,7 @@ impl LocalClipboard { impl Clipboard for LocalClipboard { fn set(&mut self, content: &str, mode: ClipboardMode) { - self.content = content.to_owned(); + content.clone_into(&mut self.content); self.mode = mode; } diff --git a/src/menu/description_menu.rs b/src/menu/description_menu.rs index 9954b4b8..95197b7a 100644 --- a/src/menu/description_menu.rs +++ b/src/menu/description_menu.rs @@ -589,7 +589,7 @@ impl Menu for DescriptionMenu { .examples .get(example_index) .expect("the example index is always checked"); - suggestion.value = example.clone(); + suggestion.value.clone_from(example); } replace_in_buffer(Some(suggestion), editor); }