Rename abbreviation triggers

This renames abbreviation triggers from `--trigger-on entry` and
`--trigger-on exec` to `--on-space` and `--on-enter`. These names are less
precise, as abbreviations trigger on any character that terminates a word
or any key binding that triggers exec, but they're also more human friendly
and that's a better tradeoff.
This commit is contained in:
ridiculousfish 2022-11-27 13:04:00 -08:00
parent 5841e9f712
commit 35a4688650
10 changed files with 73 additions and 78 deletions

View file

@ -9,7 +9,7 @@ Synopsis
.. synopsis::
abbr --add NAME [--position command | anywhere] [--regex PATTERN]
[--set-cursor SENTINEL] [--trigger-on entry | exec]...
[--set-cursor SENTINEL] [--on-space] [--on-enter]...
[-f | --function] EXPANSION
abbr --erase NAME ...
abbr --rename OLD_WORD NEW_WORD
@ -27,7 +27,7 @@ After entering ``gco`` and pressing :kbd:`Space` or :kbd:`Enter`, the full text
An abbreviation may match a literal word, or it may match a pattern given by a regular expression. When an abbreviation matches a word, that word is replaced by new text, called its *expansion*. This expansion may be a fixed new phrase, or it can be dynamically created via a fish function.
Abbreviations may expand either after their word is entered, or if they are executed with the enter key. The ``--trigger-on`` option allows limiting expansion to only entering, or only executing.
Abbreviations by default expand either after their word is entered and the user presses space, or if they are executed with the enter key. The ``--on-space`` and ``-on-enter`` options allows limiting expansion to only space or enter, respectively.
Combining these features, it is possible to create custom syntaxes, where a regular expression recognizes matching tokens, and the expansion function interprets them. See the `Examples`_ section.
@ -40,7 +40,7 @@ Abbreviations may be added to :ref:`config.fish <configuration>`. Abbreviations
.. synopsis::
abbr [-a | --add] NAME [--position command | anywhere] [--regex PATTERN]
[--set-cursor SENTINEL] [--trigger-on entry | exec]...
[--set-cursor SENTINEL] [--on-space] [--on-enter]
[-f | --function] EXPANSION
``abbr --add`` creates a new abbreviation. With no other options, the string **NAME** is replaced by **EXPANSION**.
@ -51,7 +51,7 @@ With **--regex**, the abbreviation matches using the regular expression given by
With **--set-cursor**, the cursor is moved to the first occurrence of **SENTINEL** in the expansion. That **SENTINEL** value is erased.
With **--trigger-on entry**, the abbreviation will expand after its word or pattern is ended, for example by typing a space. With **--trigger-on exec**, the abbreviation will expand when the enter key is pressed. These options may be combined. The default is both **entry** and **exec**.
With **--on-space**, the abbreviation will expand after its word or pattern is entered and the user presses space (or another word-boundary character such as semicolon). With **--on-enter**, the abbreviation will expand when the enter key is pressed to execute it. These options may be combined. The default is both **space** and **enter**.
With **-f** or **--function**, **EXPANSION** is treated as the name of a fish function instead of a literal replacement. When the abbreviation matches, the function will be called with the matching token as an argument. If the function's exit status is 0 (success), the token will be replaced by the function's output; otherwise the token will be left unchanged.
@ -98,7 +98,7 @@ This first creates a function ``vim_edit`` which prepends ``vim`` before its arg
::
abbr 4DIRS --trigger-on entry --set-cursor ! "$(string join \n -- 'for dir in */' 'cd $dir' '!' 'cd ..' 'end')"
abbr 4DIRS --on-space --set-cursor ! "$(string join \n -- 'for dir in */' 'cd $dir' '!' 'cd ..' 'end')"
This creates an abbreviation "4DIRS" which expands to a multi-line loop "template." The template enters each directory and then leaves it. The cursor is positioned ready to enter the command to run in each directory, at the location of the ``!``, which is itself erased.

View file

@ -18,11 +18,11 @@ bool abbreviation_t::matches_position(abbrs_position_t position) const {
return this->position == abbrs_position_t::anywhere || this->position == position;
}
bool abbreviation_t::matches_phases(abbrs_phases_t p) const { return bool(this->phases & p); }
bool abbreviation_t::triggers_on(abbrs_triggers_t t) const { return bool(this->triggers & t); }
bool abbreviation_t::matches(const wcstring &token, abbrs_position_t position,
abbrs_phases_t phases) const {
if (!this->matches_position(position) || !this->matches_phases(phases)) {
abbrs_triggers_t trigger) const {
if (!this->matches_position(position) || !this->triggers_on(trigger)) {
return false;
}
if (this->is_regex()) {
@ -38,12 +38,12 @@ acquired_lock<abbrs_set_t> abbrs_get_set() {
}
abbrs_replacer_list_t abbrs_set_t::match(const wcstring &token, abbrs_position_t position,
abbrs_phases_t phases) const {
abbrs_triggers_t trigger) const {
abbrs_replacer_list_t result{};
// Later abbreviations take precedence so walk backwards.
for (auto it = abbrs_.rbegin(); it != abbrs_.rend(); ++it) {
const abbreviation_t &abbr = *it;
if (abbr.matches(token, position, phases)) {
if (abbr.matches(token, position, trigger)) {
result.push_back(abbrs_replacer_t{abbr.replacement, abbr.replacement_is_function,
abbr.set_cursor_indicator});
}
@ -52,9 +52,9 @@ abbrs_replacer_list_t abbrs_set_t::match(const wcstring &token, abbrs_position_t
}
bool abbrs_set_t::has_match(const wcstring &token, abbrs_position_t position,
abbrs_phases_t phases) const {
abbrs_triggers_t trigger) const {
for (const auto &abbr : abbrs_) {
if (abbr.matches(token, position, phases)) {
if (abbr.matches(token, position, trigger)) {
return true;
}
}

View file

@ -19,18 +19,18 @@ enum class abbrs_position_t : uint8_t {
anywhere, // expand in any token
};
/// Describes a phase of expansion.
enum abbrs_phase_t : uint8_t {
// Expands "on space" immediately after the user types it.
abbrs_phase_entry = 1 << 0,
/// Describes the reason for triggering expansion.
enum abbrs_trigger_on_t : uint8_t {
// Expands on "space" (any token-closing character) immediately after the user types it.
abbrs_trigger_on_space = 1 << 0,
// Expands "on enter" before submitting the command to be executed.
abbrs_phase_exec = 1 << 1,
// Expands on "enter" (any exec key binding) before submitting the command to be executed.
abbrs_trigger_on_enter = 1 << 1,
// Default set of phases.
abbrs_phases_default = abbrs_phase_entry | abbrs_phase_exec,
// Default set of triggers.
abbrs_trigger_on_default = abbrs_trigger_on_space | abbrs_trigger_on_enter,
};
using abbrs_phases_t = uint8_t;
using abbrs_triggers_t = uint8_t;
struct abbreviation_t {
// Abbreviation name. This is unique within the abbreviation set.
@ -61,14 +61,14 @@ struct abbreviation_t {
/// Mark if we came from a universal variable.
bool from_universal{};
/// Set of phases in which this abbreviation expands.
abbrs_phases_t phases{abbrs_phases_default};
/// Set of conditions in which this abbreviation expands.
abbrs_triggers_t triggers{abbrs_trigger_on_default};
// \return true if this is a regex abbreviation.
bool is_regex() const { return this->regex.has_value(); }
// \return true if we match a token at a given position in a given set of phases.
bool matches(const wcstring &token, abbrs_position_t position, abbrs_phases_t phases) const;
// \return true if we match a token at a given position and trigger.
bool matches(const wcstring &token, abbrs_position_t position, abbrs_triggers_t trigger) const;
// Construct from a name, a key which matches a token, a replacement token, a position, and
// whether we are derived from a universal variable.
@ -82,8 +82,8 @@ struct abbreviation_t {
// \return if we expand in a given position.
bool matches_position(abbrs_position_t position) const;
// \return if we expand in a given set of phases.
bool matches_phases(abbrs_phases_t p) const;
// \return if we trigger in this phase.
bool triggers_on(abbrs_triggers_t t) const;
};
/// The result of an abbreviation expansion.
@ -123,10 +123,11 @@ class abbrs_set_t {
/// \return the list of replacers for an input token, in priority order.
/// The \p position is given to describe where the token was found.
abbrs_replacer_list_t match(const wcstring &token, abbrs_position_t position,
abbrs_phases_t phases) const;
abbrs_triggers_t trigger) const;
/// \return whether we would have at least one replacer for a given token.
bool has_match(const wcstring &token, abbrs_position_t position, abbrs_phases_t phases) const;
bool has_match(const wcstring &token, abbrs_position_t position,
abbrs_triggers_t trigger) const;
/// Add an abbreviation. Any abbreviation with the same name is replaced.
void add(abbreviation_t &&abbr);
@ -163,8 +164,8 @@ acquired_lock<abbrs_set_t> abbrs_get_set();
/// \return the list of replacers for an input token, in priority order, using the global set.
/// The \p position is given to describe where the token was found.
inline abbrs_replacer_list_t abbrs_match(const wcstring &token, abbrs_position_t position,
abbrs_phases_t phases) {
return abbrs_get_set()->match(token, position, phases);
abbrs_triggers_t trigger) {
return abbrs_get_set()->match(token, position, trigger);
}
#endif

View file

@ -41,7 +41,7 @@ struct abbr_options_t {
bool function{};
maybe_t<wcstring> regex_pattern;
maybe_t<abbrs_position_t> position{};
maybe_t<abbrs_phases_t> phases{};
maybe_t<abbrs_triggers_t> triggers{};
maybe_t<wcstring> set_cursor_indicator{};
wcstring_list_t args;
@ -79,8 +79,9 @@ struct abbr_options_t {
streams.err.append_format(_(L"%ls: --function option requires --add\n"), CMD);
return false;
}
if (!add && phases.has_value()) {
streams.err.append_format(_(L"%ls: --trigger-on option requires --add\n"), CMD);
if (!add && triggers.has_value()) {
streams.err.append_format(_(L"%ls: --on-space and --on-enter options require --add\n"),
CMD);
return false;
}
if (!add && set_cursor_indicator.has_value()) {
@ -116,12 +117,12 @@ static int abbr_show(const abbr_options_t &, io_streams_t &streams) {
comps.push_back(L"--set-cursor");
comps.push_back(escape_string(*abbr.set_cursor_indicator));
}
if (abbr.phases != abbrs_phases_default) {
if (abbr.phases & abbrs_phase_entry) {
comps.push_back(L"--trigger-on entry");
if (abbr.triggers != abbrs_trigger_on_default) {
if (abbr.triggers & abbrs_trigger_on_space) {
comps.push_back(L"--on-space");
}
if (abbr.phases & abbrs_phase_exec) {
comps.push_back(L"--trigger-on exec");
if (abbr.triggers & abbrs_trigger_on_enter) {
comps.push_back(L"--on-enter");
}
}
if (abbr.replacement_is_function) {
@ -263,7 +264,7 @@ static int abbr_add(const abbr_options_t &opts, io_streams_t &streams) {
abbr.regex = std::move(regex);
abbr.replacement_is_function = opts.function;
abbr.set_cursor_indicator = opts.set_cursor_indicator;
abbr.phases = opts.phases.value_or(abbrs_phases_default);
abbr.triggers = opts.triggers.value_or(abbrs_trigger_on_default);
abbrs_get_set()->add(std::move(abbr));
return STATUS_CMD_OK;
}
@ -292,7 +293,7 @@ maybe_t<int> builtin_abbr(parser_t &parser, io_streams_t &streams, const wchar_t
const wchar_t *cmd = argv[0];
abbr_options_t opts;
// Note 1 is returned by wgetopt to indicate a non-option argument.
enum { NON_OPTION_ARGUMENT = 1, REGEX_SHORT, EXPAND_ON_SHORT };
enum { NON_OPTION_ARGUMENT = 1, REGEX_SHORT, ON_SPACE_SHORT, ON_ENTER_SHORT };
// Note the leading '-' causes wgetopter to return arguments in order, instead of permuting
// them. We need this behavior for compatibility with pre-builtin abbreviations where options
@ -301,7 +302,8 @@ maybe_t<int> builtin_abbr(parser_t &parser, io_streams_t &streams, const wchar_t
static const struct woption long_options[] = {{L"add", no_argument, 'a'},
{L"position", required_argument, 'p'},
{L"regex", required_argument, REGEX_SHORT},
{L"trigger-on", required_argument, 't'},
{L"on-space", no_argument, ON_SPACE_SHORT},
{L"on-enter", no_argument, ON_ENTER_SHORT},
{L"set-cursor", required_argument, 'C'},
{L"function", no_argument, 'f'},
{L"rename", no_argument, 'r'},
@ -361,19 +363,12 @@ maybe_t<int> builtin_abbr(parser_t &parser, io_streams_t &streams, const wchar_t
opts.regex_pattern = w.woptarg;
break;
}
case 't': {
abbrs_phases_t phases = opts.phases.value_or(0);
if (!wcscmp(w.woptarg, L"entry")) {
phases |= abbrs_phase_entry;
} else if (!wcscmp(w.woptarg, L"exec")) {
phases |= abbrs_phase_exec;
} else {
streams.err.append_format(_(L"%ls: Invalid --trigger-on '%ls'\n"
L"Must be one of: entry, exec.\n"),
CMD, w.woptarg);
return STATUS_INVALID_ARGS;
}
opts.phases = phases;
case ON_SPACE_SHORT: {
opts.triggers = opts.triggers.value_or(0) | abbrs_trigger_on_space;
break;
}
case ON_ENTER_SHORT: {
opts.triggers = opts.triggers.value_or(0) | abbrs_trigger_on_enter;
break;
}
case 'C': {

View file

@ -2481,7 +2481,7 @@ static void test_abbreviations() {
// Helper to expand an abbreviation, enforcing we have no more than one result.
auto abbr_expand_1 = [](const wcstring &token, abbrs_position_t pos) -> maybe_t<wcstring> {
auto result = abbrs_match(token, pos, abbrs_phases_default);
auto result = abbrs_match(token, pos, abbrs_trigger_on_default);
if (result.size() > 1) {
err(L"abbreviation expansion for %ls returned more than 1 result", token.c_str());
}
@ -2507,7 +2507,7 @@ static void test_abbreviations() {
auto expand_abbreviation_in_command = [](const wcstring &cmdline,
size_t cursor_pos) -> maybe_t<wcstring> {
if (auto replacement = reader_expand_abbreviation_at_cursor(
cmdline, cursor_pos, abbrs_phases_default, parser_t::principal_parser())) {
cmdline, cursor_pos, abbrs_trigger_on_default, parser_t::principal_parser())) {
wcstring cmdline_expanded = cmdline;
std::vector<highlight_spec_t> colors{cmdline_expanded.size()};
apply_edit(&cmdline_expanded, &colors, edit_t{replacement->range, replacement->text});

View file

@ -1336,7 +1336,8 @@ static bool command_is_valid(const wcstring &cmd, enum statement_decoration_t de
// Abbreviations
if (!is_valid && abbreviation_ok)
is_valid = abbrs_get_set()->has_match(cmd, abbrs_position_t::command, abbrs_phases_default);
is_valid =
abbrs_get_set()->has_match(cmd, abbrs_position_t::command, abbrs_trigger_on_default);
// Regular commands
if (!is_valid && command_ok) is_valid = path_get_path(cmd, vars).has_value();

View file

@ -788,9 +788,9 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
/// Do what we need to do whenever our pager selection changes.
void pager_selection_changed();
/// Expand abbreviations in the given phases at the current cursor position, minus
/// Expand abbreviations in the given triggers at the current cursor position, minus
/// cursor_backtrack.
bool expand_abbreviation_at_cursor(size_t cursor_backtrack, abbrs_phases_t phases);
bool expand_abbreviation_at_cursor(size_t cursor_backtrack, abbrs_triggers_t triggers);
/// \return true if the command line has changed and repainting is needed. If \p colors is not
/// null, then also return true if the colors have changed.
@ -1445,11 +1445,11 @@ static std::vector<positioned_token_t> extract_tokens(const wcstring &str) {
return result;
}
/// Expand abbreviations in the given phase at the given cursor position.
/// Expand abbreviations in the given triggers at the given cursor position.
/// cursor. \return the replacement. This does NOT inspect the current reader data.
maybe_t<abbrs_replacement_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline,
size_t cursor_pos,
abbrs_phases_t phases,
abbrs_triggers_t triggers,
parser_t &parser) {
// Find the token containing the cursor. Usually users edit from the end, so walk backwards.
const auto tokens = extract_tokens(cmdline);
@ -1464,7 +1464,7 @@ maybe_t<abbrs_replacement_t> reader_expand_abbreviation_at_cursor(const wcstring
iter->is_cmd ? abbrs_position_t::command : abbrs_position_t::anywhere;
wcstring token_str = cmdline.substr(range.start, range.length);
auto replacers = abbrs_match(token_str, position, phases);
auto replacers = abbrs_match(token_str, position, triggers);
for (const auto &replacer : replacers) {
if (auto replacement = expand_replacer(range, token_str, replacer, parser)) {
return replacement;
@ -1476,7 +1476,8 @@ maybe_t<abbrs_replacement_t> reader_expand_abbreviation_at_cursor(const wcstring
/// Expand abbreviations at the current cursor position, minus the given cursor backtrack. This may
/// change the command line but does NOT repaint it. This is to allow the caller to coalesce
/// repaints.
bool reader_data_t::expand_abbreviation_at_cursor(size_t cursor_backtrack, abbrs_phases_t phases) {
bool reader_data_t::expand_abbreviation_at_cursor(size_t cursor_backtrack,
abbrs_triggers_t triggers) {
bool result = false;
editable_line_t *el = active_edit_line();
@ -1484,8 +1485,8 @@ bool reader_data_t::expand_abbreviation_at_cursor(size_t cursor_backtrack, abbrs
// Try expanding abbreviations.
this->update_commandline_state();
size_t cursor_pos = el->position() - std::min(el->position(), cursor_backtrack);
if (auto replacement = reader_expand_abbreviation_at_cursor(el->text(), cursor_pos, phases,
this->parser())) {
if (auto replacement = reader_expand_abbreviation_at_cursor(el->text(), cursor_pos,
triggers, this->parser())) {
push_edit(el, edit_t{replacement->range, std::move(replacement->text)});
update_buff_pos(el, replacement->cursor);
result = true;
@ -4188,7 +4189,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
case rl::expand_abbr: {
if (expand_abbreviation_at_cursor(1, abbrs_phase_entry)) {
if (expand_abbreviation_at_cursor(1, abbrs_trigger_on_space)) {
inputter.function_set_status(true);
} else {
inputter.function_set_status(false);
@ -4281,7 +4282,7 @@ parser_test_error_bits_t reader_data_t::expand_for_execute() {
// Exec abbreviations at the cursor.
// Note we want to expand abbreviations even if incomplete.
if (expand_abbreviation_at_cursor(0, abbrs_phase_exec)) {
if (expand_abbreviation_at_cursor(0, abbrs_trigger_on_enter)) {
// Trigger syntax highlighting as we are likely about to execute this command.
this->super_highlight_me_plenty();
if (conf.syntax_check_ok) {

View file

@ -266,12 +266,13 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline,
/// Expand at most one abbreviation at the given cursor position, updating the position if the
/// abbreviation wants to move the cursor. Use the parser to run any abbreviations which want
/// function calls. \return none if no abbreviations were expanded, otherwise the resulting edit.
using abbrs_phases_t = uint8_t;
/// function calls. \return none if no abbreviations were expanded, otherwise the resulting
/// replacement.
using abbrs_triggers_t = uint8_t;
struct abbrs_replacement_t;
maybe_t<abbrs_replacement_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline,
size_t cursor_pos,
abbrs_phases_t phases,
abbrs_triggers_t triggers,
parser_t &parser);
/// Apply a completion string. Exposed for testing only.

View file

@ -167,7 +167,3 @@ abbr --add bogus --position never stuff
abbr --add bogus --position anywhere --position command stuff
# CHECKERR: abbr: Cannot specify multiple positions
abbr --add --trigger-on derp zzxjoanw stuff
# CHECKERR: abbr: Invalid --trigger-on 'derp'
# CHECKERR: Must be one of: entry, exec.

View file

@ -148,12 +148,12 @@ expect_prompt()
send(r"""echo LLL derp?""")
expect_str(r"echo derp | less ")
# Test trigger-on.
# Test --on-enter and --on-space.
sendline(r"""abbr --erase (abbr --list) """)
expect_prompt()
sendline(r"""abbr --add entry-only --position anywhere --trigger-on entry 'worked1'""")
sendline(r"""abbr --add entry-only --position anywhere --on-space 'worked1'""")
expect_prompt()
sendline(r"""abbr --add exec-only --position anywhere --trigger-on exec 'worked2'""")
sendline(r"""abbr --add exec-only --position anywhere --on-enter 'worked2'""")
expect_prompt()
sendline(r"echo entry-only") # should not trigger, no space
expect_prompt(r"entry-only")