Fix autosuggestions on quoted subcommand keyword

The commandline "and fis" rightly gets command autosuggestions whereas
"'and' fis" wrongly gets file autosuggestions.  The former works via
a hack. Extend it to quoted keywords.
This commit is contained in:
Johannes Altmanninger
2025-01-12 11:03:17 +01:00
parent 80a6ece45b
commit e678fb8578
3 changed files with 24 additions and 18 deletions

View File

@@ -24,6 +24,7 @@
TOK_ACCEPT_UNFINISHED, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
};
use crate::wchar::prelude::*;
use std::borrow::Cow;
use std::ops::{ControlFlow, Index, IndexMut};
/**
@@ -3965,11 +3966,11 @@ fn is_keyword_char(c: char) -> bool {
|| c == '!'
}
/// Given a token, returns the keyword it matches, or ParseKeyword::none.
fn keyword_for_token(tok: TokenType, token: &wstr) -> ParseKeyword {
/// Given a token, returns unescaped keyword, or the empty string.
pub(crate) fn unescape_keyword(tok: TokenType, token: &wstr) -> Cow<'_, wstr> {
/* Only strings can be keywords */
if tok != TokenType::string {
return ParseKeyword::none;
return Cow::Borrowed(L!(""));
}
// If token is clean (which most are), we can compare it directly. Otherwise we have to expand
@@ -3977,27 +3978,26 @@ fn keyword_for_token(tok: TokenType, token: &wstr) -> ParseKeyword {
// expansions. So we do our own "cleanliness" check; if we find a character not in our allowed
// set we know it's not a keyword, and if we never find a quote we don't have to expand! Note
// that this lowercase set could be shrunk to be just the characters that are in keywords.
let mut result = ParseKeyword::none;
let mut needs_expand = false;
let mut all_chars_valid = true;
for c in token.chars() {
if !is_keyword_char(c) {
all_chars_valid = false;
break;
return Cow::Borrowed(L!(""));
}
// If we encounter a quote, we need expansion.
needs_expand = needs_expand || c == '"' || c == '\'' || c == '\\'
}
if all_chars_valid {
// Expand if necessary.
if !needs_expand {
result = ParseKeyword::from(token);
} else if let Some(unescaped) = unescape_string(token, UnescapeStringStyle::default()) {
result = ParseKeyword::from(&unescaped[..]);
}
// Expand if necessary.
if !needs_expand {
return Cow::Borrowed(token);
}
result
Cow::Owned(unescape_string(token, UnescapeStringStyle::default()).unwrap_or_default())
}
/// Given a token, returns the keyword it matches, or ParseKeyword::none.
fn keyword_for_token(tok: TokenType, token: &wstr) -> ParseKeyword {
ParseKeyword::from(&unescape_keyword(tok, token)[..])
}
#[test]

View File

@@ -10,6 +10,7 @@
};
use crate::{
ast::unescape_keyword,
common::charptr2wcstring,
reader::{get_quote, is_backslashed},
util::wcsfilecmp,
@@ -669,7 +670,12 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) {
if is_autosuggest {
let prefixed_supercommand_count = tokens
.iter()
.take_while(|token| parser_keywords_is_subcommand(token.get_source(&cmdline)))
.take_while(|token| {
parser_keywords_is_subcommand(&unescape_keyword(
token.type_,
token.get_source(&cmdline),
))
})
.count();
tokens.drain(..prefixed_supercommand_count);
}

View File

@@ -66,8 +66,8 @@ fn reserved_word(cmd: &wstr) -> Option<&'static ReservedWord> {
}
/// Tests if the specified command's parameters should be interpreted as another command.
pub fn parser_keywords_is_subcommand(cmd: &wstr) -> bool {
reserved_word(cmd).is_some_and(|reserved_word| reserved_word.is_super_command)
pub fn parser_keywords_is_subcommand(cmd: &impl AsRef<wstr>) -> bool {
reserved_word(cmd.as_ref()).is_some_and(|reserved_word| reserved_word.is_super_command)
}
/// Tests if the specified command is a reserved word, i.e. if it is the name of one of the builtin