diff --git a/doc_src/cmds/abbr.rst b/doc_src/cmds/abbr.rst index 7f0457623..6d5339665 100644 --- a/doc_src/cmds/abbr.rst +++ b/doc_src/cmds/abbr.rst @@ -8,8 +8,8 @@ Synopsis abbr --add NAME [--position command | anywhere] [-r | --regex PATTERN] [-c | --command COMMAND] [--set-cursor[=MARKER]] ([-f | --function FUNCTION] | EXPANSION) - abbr --erase NAME ... - abbr --rename OLD_WORD NEW_WORD + abbr --erase [ [-c | --command COMMAND]... ] NAME ... + abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD abbr --show abbr --list abbr --query NAME ... @@ -134,9 +134,10 @@ Other subcommands :: - abbr --rename OLD_NAME NEW_NAME + abbr --rename [ [-c | --command COMMAND]... ] OLD_NAME NEW_NAME -Renames an abbreviation, from *OLD_NAME* to *NEW_NAME* +Renames an abbreviation, from *OLD_NAME* to *NEW_NAME*. +For command-specific abbreviations, the ``--command`` options must be provided to disambiguate which abbreviation to rename. :: @@ -152,9 +153,10 @@ Prints the names of all abbreviation :: - abbr [-e | --erase] NAME + abbr [-e | --erase] [ [-c | --command COMMAND]... ] NAME ... -Erases the abbreviation with the given name +Erases the abbreviation with the given name. +For command-specific abbreviations, the ``--command`` options must be provided to disambiguate which abbreviation to rename. :: diff --git a/po/de.po b/po/de.po index c268f2b87..5d762731e 100644 --- a/po/de.po +++ b/po/de.po @@ -86,6 +86,10 @@ msgstr "%.*s: Ungültige Umwandlungsspezifikation" msgid "%s" msgstr "" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "" @@ -98,8 +102,8 @@ msgstr "" msgid "%s %s: Name cannot be empty\n" msgstr "%s %s: Name darf nicht leer sein\n" -#, c-format -msgid "%s %s: No abbreviation named %s\n" +#, fuzzy, c-format +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "%s %s: Keine Abkürzung namens %s\n" #, c-format diff --git a/po/en.po b/po/en.po index 55b440b9b..30e548bdc 100644 --- a/po/en.po +++ b/po/en.po @@ -84,6 +84,10 @@ msgstr "%.*s: invalid conversion specification" msgid "%s" msgstr "" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "" @@ -97,7 +101,7 @@ msgid "%s %s: Name cannot be empty\n" msgstr "" #, c-format -msgid "%s %s: No abbreviation named %s\n" +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "" #, c-format diff --git a/po/fr.po b/po/fr.po index 8cd316131..31aab54ab 100644 --- a/po/fr.po +++ b/po/fr.po @@ -215,6 +215,10 @@ msgstr "%.*s : spécification de conversion invalide" msgid "%s" msgstr "%s" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "%s %s : l’abréviation %s existe déjà ; impossible de renommer %s\n" @@ -227,8 +231,8 @@ msgstr "%s %s : l’abréviation « %s » ne peut pas avoir d’espaces dans msgid "%s %s: Name cannot be empty\n" msgstr "%s %s : le nom ne peut pas être vide\n" -#, c-format -msgid "%s %s: No abbreviation named %s\n" +#, fuzzy, c-format +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "%s %s : aucune abréviation nommée %s\n" #, c-format diff --git a/po/pl.po b/po/pl.po index 7011244a1..080da1833 100644 --- a/po/pl.po +++ b/po/pl.po @@ -80,6 +80,10 @@ msgstr "" msgid "%s" msgstr "" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "" @@ -93,7 +97,7 @@ msgid "%s %s: Name cannot be empty\n" msgstr "" #, c-format -msgid "%s %s: No abbreviation named %s\n" +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "" #, c-format diff --git a/po/pt_BR.po b/po/pt_BR.po index c361f5a47..80a28020c 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -85,6 +85,10 @@ msgstr "%.*s: especificação de conversão inválida" msgid "%s" msgstr "" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "" @@ -98,7 +102,7 @@ msgid "%s %s: Name cannot be empty\n" msgstr "" #, c-format -msgid "%s %s: No abbreviation named %s\n" +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "" #, c-format diff --git a/po/sv.po b/po/sv.po index c7b298694..3da4a7a97 100644 --- a/po/sv.po +++ b/po/sv.po @@ -81,6 +81,10 @@ msgstr "" msgid "%s" msgstr "" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "" @@ -94,7 +98,7 @@ msgid "%s %s: Name cannot be empty\n" msgstr "" #, c-format -msgid "%s %s: No abbreviation named %s\n" +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "" #, c-format diff --git a/po/zh_CN.po b/po/zh_CN.po index a70bbba26..971f5ad84 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -107,6 +107,10 @@ msgstr "%.*s: 无效的转换规范" msgid "%s" msgstr "%s" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "%s %s: 缩写 %s 已经存在,无法重命名 %s\n" @@ -119,8 +123,8 @@ msgstr "%s %s: 缩写词 '%s' 不能包含空格\n" msgid "%s %s: Name cannot be empty\n" msgstr "%s %s: 名称不能为空\n" -#, c-format -msgid "%s %s: No abbreviation named %s\n" +#, fuzzy, c-format +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "%s %s: 没有命名为 %s 的缩写\n" #, c-format diff --git a/po/zh_TW.po b/po/zh_TW.po index e7cfb7b6e..2fd4895bd 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -89,6 +89,10 @@ msgstr "%.*s:轉換規格無效" msgid "%s" msgstr "%s" +#, c-format +msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n" +msgstr "" + #, c-format msgid "%s %s: Abbreviation %s already exists, cannot rename %s\n" msgstr "%s %s:縮寫 %s 已存在,無法重新命名 %s\n" @@ -101,8 +105,8 @@ msgstr "%s %s:縮寫「%s」中不能包含空格\n" msgid "%s %s: Name cannot be empty\n" msgstr "%s %s:名稱不能空白\n" -#, c-format -msgid "%s %s: No abbreviation named %s\n" +#, fuzzy, c-format +msgid "%s %s: No abbreviation named %s with the specified command restrictions\n" msgstr "%s %s:沒有縮寫名為 %s\n" #, c-format diff --git a/src/abbrs.rs b/src/abbrs.rs index 0f420ceae..d5f753523 100644 --- a/src/abbrs.rs +++ b/src/abbrs.rs @@ -99,9 +99,11 @@ pub fn matches(&self, token: &wstr, position: Position, command: &wstr) -> bool if !self.matches_position(position) { return false; } + if !self.commands.is_empty() && !self.commands.contains(&command.to_owned()) { return false; } + match &self.regex { Some(r) => r .is_match(token.as_char_slice()) @@ -211,45 +213,44 @@ pub fn add(&mut self, abbr: Abbreviation) { let index = self .abbrs .iter() - .position(|a| a.name == abbr.name) - .expect("Abbreviation not found though its name was present"); + .position(|a| a.name == abbr.name && a.commands == abbr.commands); - self.abbrs.remove(index); + if let Some(idx) = index { + // Exact match found (name + commands), delete old abbr. + self.abbrs.remove(idx); + } } self.abbrs.push(abbr); } /// Rename an abbreviation. This asserts that the old name is used, and the new name is not; the /// caller should check these beforehand with has_name(). - pub fn rename(&mut self, old_name: &wstr, new_name: &wstr) { - let erased = self.used_names.remove(old_name); - let inserted = self.used_names.insert(new_name.to_owned()); - assert!( - erased && inserted, - "Old name not found or new name already present" - ); - for abbr in self.abbrs.iter_mut() { - if abbr.name == old_name { - abbr.name = new_name.to_owned(); - break; - } - } + pub fn rename(&mut self, old_name: &wstr, new_name: &wstr, commands: &[WString]) { + self.abbrs + .iter_mut() + .find(|a| a.name == old_name && a.commands == commands) + .expect("Abbreviation to rename was not found.") + .name = new_name.to_owned(); + + self.used_names.insert(new_name.to_owned()); + self.on_remove(old_name); } - /// Erase an abbreviation by name. + /// Erase an abbreviation by name and command restrictions. /// Return true if erased, false if not found. - pub fn erase(&mut self, name: &wstr) -> bool { - let erased = self.used_names.remove(name); - if !erased { + pub fn erase(&mut self, name: &wstr, commands: &[WString]) -> bool { + let Some(idx) = self + .abbrs + .iter() + .position(|a| a.name == name && a.commands == commands) + else { return false; - } - for (index, abbr) in self.abbrs.iter().enumerate().rev() { - if abbr.name == name { - self.abbrs.remove(index); - return true; - } - } - panic!("Unable to find named abbreviation"); + }; + + self.abbrs.remove(idx); + self.on_remove(name); + + true } /// Return true if we have an abbreviation with the given name. @@ -261,6 +262,16 @@ pub fn has_name(&self, name: &wstr) -> bool { pub fn list(&self) -> &[Abbreviation] { &self.abbrs } + + /// Checks if any other abbreviation still uses name, removing it from used_names if not + fn on_remove(&mut self, name: &wstr) { + if self.abbrs.iter().any(|a| a.name == name) { + return; + } + + let removed = self.used_names.remove(name); + assert!(removed, "Name was not in used_names but should have been"); + } } /// Return the list of replacers for an input token, in priority order, using the global set. @@ -438,13 +449,13 @@ fn rename_abbrs() { assert!(!abbrs_g.has_name(L!("gcc"))); assert!(abbrs_g.has_name(L!("gc"))); - abbrs_g.rename(L!("gc"), L!("gcc")); + abbrs_g.rename(L!("gc"), L!("gcc"), &[]); assert!(abbrs_g.has_name(L!("gcc"))); assert!(!abbrs_g.has_name(L!("gc"))); - assert!(!abbrs_g.erase(L!("gc"))); - assert!(abbrs_g.erase(L!("gcc"))); - assert!(!abbrs_g.erase(L!("gcc"))); + assert!(!abbrs_g.erase(L!("gc"), &[])); + assert!(abbrs_g.erase(L!("gcc"), &[])); + assert!(!abbrs_g.erase(L!("gcc"), &[])); }) } } diff --git a/src/builtins/abbr.rs b/src/builtins/abbr.rs index 13be94309..0a072ca02 100644 --- a/src/builtins/abbr.rs +++ b/src/builtins/abbr.rs @@ -229,26 +229,56 @@ fn abbr_rename(opts: &Options, streams: &mut IoStreams) -> BuiltinResult { return Err(STATUS_INVALID_ARGS); } abbrs::with_abbrs_mut(|abbrs| -> BuiltinResult { - if !abbrs.has_name(old_name) { + if !abbrs + .list() + .iter() + .any(|a| a.name == *old_name && a.commands == opts.commands) + { streams.err.append(wgettext_fmt!( - "%s %s: No abbreviation named %s\n", + "%s %s: No abbreviation named %s with the specified command restrictions\n", CMD, subcmd, old_name.as_utfstr() )); return Err(STATUS_CMD_ERROR); } - if abbrs.has_name(new_name) { - streams.err.append(wgettext_fmt!( - "%s %s: Abbreviation %s already exists, cannot rename %s\n", - CMD, - subcmd, - new_name.as_utfstr(), - old_name.as_utfstr() - )); + if abbrs + .list() + .iter() + .any(|a| a.name == *new_name && a.commands == opts.commands) + { + if opts.commands.is_empty() { + streams.err.append(wgettext_fmt!( + "%s %s: Abbreviation %s already exists, cannot rename %s\n", + CMD, + subcmd, + new_name.as_utfstr(), + old_name.as_utfstr() + )); + } else { + let style = EscapeStringStyle::Script(Default::default()); + let mut cmd_list = WString::new(); + for (i, cmd) in opts.commands.iter().enumerate() { + if i > 0 { + cmd_list.push_str(", "); + } + cmd_list.push_utfstr(&escape_string(cmd, style)); + } + + streams.err.append(wgettext_fmt!( + "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n", + CMD, + subcmd, + new_name.as_utfstr(), + cmd_list.as_utfstr(), + old_name.as_utfstr() + )); + } + return Err(STATUS_INVALID_ARGS); } - abbrs.rename(old_name, new_name); + + abbrs.rename(old_name, new_name, &opts.commands); Ok(SUCCESS) }) } @@ -414,18 +444,20 @@ fn abbr_erase(opts: &Options, parser: &Parser) -> BuiltinResult { abbrs::with_abbrs_mut(|abbrs| -> BuiltinResult { let mut result: BuiltinResult = Ok(SUCCESS); for arg in &opts.args { - if !abbrs.erase(arg) { + if !abbrs.erase(arg, &opts.commands) { result = EnvStackSetResult::NotFound.into(); } // Erase the old uvar - this makes `abbr -e` work. - let esc_src = escape(arg); - if !esc_src.is_empty() { - let var_name = WString::from_str("_fish_abbr_") + esc_src.as_utfstr(); - let ret = parser.vars().remove(&var_name, EnvMode::UNIVERSAL); + if opts.commands.is_empty() { + let esc_src = escape(arg); + if !esc_src.is_empty() { + let var_name = WString::from_str("_fish_abbr_") + esc_src.as_utfstr(); + let ret = parser.vars().remove(&var_name, EnvMode::UNIVERSAL); - if ret == EnvStackSetResult::Ok { - result = Ok(SUCCESS) - }; + if ret == EnvStackSetResult::Ok { + result = Ok(SUCCESS) + }; + } } } result diff --git a/src/complete.rs b/src/complete.rs index 7d47f4065..a8ed6e011 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -2994,7 +2994,7 @@ macro_rules! unique_completion_applies_as { assert_eq!(completions[0].completion, L!("zero")); assert!(completions[0].flags.contains(CompleteFlags::NO_SPACE)); with_abbrs_mut(|abbrset| { - abbrset.erase(L!("testabbrsonetwothreezero")); + abbrset.erase(L!("testabbrsonetwothreezero"), &[]); }); assert_eq!(completions[1].completion, L!("four")); assert!(!completions[1].flags.contains(CompleteFlags::NO_SPACE)); diff --git a/tests/checks/abbr.fish b/tests/checks/abbr.fish index 813292476..ababb75c4 100644 --- a/tests/checks/abbr.fish +++ b/tests/checks/abbr.fish @@ -64,7 +64,7 @@ abbr | grep __abbr4 # Test renaming a nonexistent abbreviation abbr --rename __abbr6 __abbr -# CHECKERR: abbr --rename: No abbreviation named __abbr6 +# CHECKERR: abbr --rename: No abbreviation named __abbr6 with the specified command restrictions # Test renaming to a abbreviation with spaces abbr __abbr4 omega @@ -208,3 +208,29 @@ abbr --add regex_name --regex '(*UTF).*' bar abbr --add foo --set-cursor 'foo % bar' abbr | grep foo # CHECK: abbr -a --set-cursor='%' -- foo 'foo % bar' + +# Command-specific and general abbrs can coexist +abbr __abbr_coexist "general def" +abbr --command foo __abbr_coexist "command def" +abbr | grep __abbr_coexist +# CHECK: abbr -a -- __abbr_coexist 'general def' +# CHECK: abbr -a --position anywhere --command foo -- __abbr_coexist 'command def' + +# Erase general abbreviation +abbr -e __abbr_coexist +abbr | grep __abbr_coexist +# CHECK: abbr -a --position anywhere --command foo -- __abbr_coexist 'command def' + +# Abbrs with different commands coexist +abbr --command bar __abbr_coexist "bar command" +abbr | grep __abbr_coexist +# CHECK: abbr -a --position anywhere --command foo -- __abbr_coexist 'command def' +# CHECK: abbr -a --position anywhere --command bar -- __abbr_coexist 'bar command' + +# Rename command-specific abbr +abbr --rename --command bar __abbr_coexist __abbr_coexist_2 +abbr | grep __abbr_coexist +# CHECK: abbr -a --position anywhere --command foo -- __abbr_coexist 'command def' +# CHECK: abbr -a --position anywhere --command bar -- __abbr_coexist_2 'bar command' +abbr -e --command foo __abbr_coexist +abbr -e --command bar __abbr_coexist_2