diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e5d8bd35f..473824675 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,7 +12,8 @@ Scripting improvements Interactive improvements ------------------------ -- Autosuggestions are now also provided in multi-line command lines. Like `ctrl-r`, autosuggestions operate only on the current line. +- Autosuggestions are now also provided in multi-line command lines. Like `ctrl-r`, autosuggestions operate only on the current line. +- Autosuggestions used to not suggest multi-line commandlines from history; now autosuggestions include individual lines from multi-line command lines. - New feature flag ``buffered-enter-noexec`` with the following effect: when typing a command and :kbd:`enter` while the previous one is still running, the new one will no longer execute immediately. Similarly, keys that are bound to shell commands will be ignored. This mitigates a security issue where a command like ``cat malicious-file.txt`` could write terminal escape codes prompting the terminal to write arbitrary text to fish's standard input. diff --git a/src/history.rs b/src/history.rs index 458836a41..e1aa9d85f 100644 --- a/src/history.rs +++ b/src/history.rs @@ -91,6 +91,8 @@ pub enum SearchType { Contains, /// Search for commands starting with the given string. Prefix, + /// Search for commands where any line matches the given string. + LinePrefix, /// Search for commands containing the given glob pattern. ContainsGlob, /// Search for commands starting with the given glob pattern. @@ -291,6 +293,10 @@ impl HistoryItem { find_subslice(term.as_slice(), content_to_match.as_slice()).is_some() } SearchType::Prefix => content_to_match.as_slice().starts_with(term.as_slice()), + SearchType::LinePrefix => content_to_match + .as_char_slice() + .split(|&c| c == '\n') + .any(|line| line.starts_with(term.as_char_slice())), SearchType::ContainsGlob => { let mut pat = parse_util_unescape_wildcards(term); if !pat.starts_with(ANY_STRING) { diff --git a/src/reader.rs b/src/reader.rs index 1d201a09b..e42ad1383 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2977,7 +2977,7 @@ impl<'a> Reader<'a> { let is_autosuggestion = self.is_at_autosuggestion(); if is_history_search || is_autosuggestion { self.input_data.function_set_status(true); - if is_autosuggestion && !self.autosuggestion.is_from_history { + if is_autosuggestion && !self.autosuggestion.is_whole_item_from_history { self.flash(); return; } @@ -4532,7 +4532,7 @@ struct Autosuggestion { icase: bool, // Whether the autosuggestion is a whole match from history. - is_from_history: bool, + is_whole_item_from_history: bool, } impl Autosuggestion { @@ -4573,14 +4573,14 @@ impl AutosuggestionResult { search_string_range: Range, text: WString, icase: bool, - is_from_history: bool, + is_whole_item_from_history: bool, ) -> Self { Self { autosuggestion: Autosuggestion { text, search_string_range, icase, - is_from_history, + is_whole_item_from_history, }, command_line, needs_load: vec![], @@ -4625,25 +4625,33 @@ fn get_autosuggestion_performer( parse_util_process_extent(&command_line, cursor_pos, None).start, ) == search_string_range { - let mut searcher = - HistorySearch::new_with_type(history, search_string.to_owned(), SearchType::Prefix); + let mut searcher = HistorySearch::new_with_type( + history, + search_string.to_owned(), + SearchType::LinePrefix, + ); while !ctx.check_cancel() && searcher.go_to_next_match(SearchDirection::Backward) { let item = searcher.current_item(); - // Skip items with newlines because they make terrible autosuggestions. - if item.str().contains('\n') { - continue; - } + // Suggest only a single line each time. + let matched_line = item + .str() + .as_char_slice() + .split(|&c| c == '\n') + .rev() + .find(|line| line.starts_with(search_string.as_char_slice())) + .unwrap(); if autosuggest_validate_from_history(item, &working_directory, &ctx) { // The command autosuggestion was handled specially, so we're done. // History items are case-sensitive, see #3978. + let is_whole = matched_line.len() == item.str().len(); return AutosuggestionResult::new( command_line, search_string_range, - searcher.current_string().to_owned(), + matched_line.into(), /*icase=*/ false, - /*is_history=*/ true, + is_whole, ); } } @@ -4693,7 +4701,7 @@ fn get_autosuggestion_performer( search_string_range.clone(), suggestion, true, // normal completions are case-insensitive - /*is_from_history=*/ false, + /*is_whole_item_from_history=*/ false, ); result.needs_load = needs_load; result