Re-implement abbreviations as a built-in

Prior to this change, abbreviations were stored as fish variables, often
universal. However we intend to add additional features to abbreviations
which would be very awkward to shoe-horn into variables.

Re-implement abbreviations using a builtin, managing them internally.

Existing abbreviations stored in universal variables are still imported,
for compatibility. However new abbreviations will need to be added to a
function. A follow-up commit will add it.

Now that abbr is a built-in, remove the abbr function; but leave the
abbr.fish file so that stale files from past installs do not override
the abbr builtin.
This commit is contained in:
ridiculousfish 2022-04-01 12:05:27 -07:00
parent 635cc3ee8d
commit 1402bae7f4
20 changed files with 608 additions and 339 deletions

View File

@ -91,7 +91,7 @@ endif()
# List of sources for builtin functions. # List of sources for builtin functions.
set(FISH_BUILTIN_SRCS set(FISH_BUILTIN_SRCS
src/builtin.cpp src/builtins/argparse.cpp src/builtin.cpp src/builtins/abbr.cpp src/builtins/argparse.cpp
src/builtins/bg.cpp src/builtins/bind.cpp src/builtins/block.cpp src/builtins/bg.cpp src/builtins/bind.cpp src/builtins/block.cpp
src/builtins/builtin.cpp src/builtins/cd.cpp src/builtins/command.cpp src/builtins/builtin.cpp src/builtins/cd.cpp src/builtins/command.cpp
src/builtins/commandline.cpp src/builtins/complete.cpp src/builtins/contains.cpp src/builtins/commandline.cpp src/builtins/complete.cpp src/builtins/contains.cpp
@ -107,9 +107,9 @@ set(FISH_BUILTIN_SRCS
# List of other sources. # List of other sources.
set(FISH_SRCS set(FISH_SRCS
src/ast.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp src/env.cpp src/ast.cpp src/abbrs.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp
src/env_dispatch.cpp src/env_universal_common.cpp src/event.cpp src/exec.cpp src/env.cpp src/env_dispatch.cpp src/env_universal_common.cpp src/event.cpp
src/expand.cpp src/fallback.cpp src/fd_monitor.cpp src/fish_version.cpp src/exec.cpp src/expand.cpp src/fallback.cpp src/fd_monitor.cpp src/fish_version.cpp
src/flog.cpp src/function.cpp src/future_feature_flags.cpp src/highlight.cpp src/flog.cpp src/function.cpp src/future_feature_flags.cpp src/highlight.cpp
src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp
src/io.cpp src/iothread.cpp src/job_group.cpp src/kill.cpp src/io.cpp src/iothread.cpp src/job_group.cpp src/kill.cpp

View File

@ -64,17 +64,16 @@ Examples
:: ::
abbr -a -g gco git checkout abbr -a gco git checkout
Add a new abbreviation where ``gco`` will be replaced with ``git checkout`` global to the current shell. Add a new abbreviation where ``gco`` will be replaced with ``git checkout``.
This abbreviation will not be automatically visible to other shells unless the same command is run in those shells (such as when executing the commands in config.fish). This abbreviation will not be automatically visible to other shells unless the same command is run in those shells (such as when executing the commands in config.fish).
:: ::
abbr -a -U l less abbr -a l less
Add a new abbreviation where ``l`` will be replaced with ``less`` universal to all shells. Add a new abbreviation where ``l`` will be replaced with ``less`` universal to all shells.
Note that you omit the **-U** since it is the default.
:: ::

View File

@ -1,210 +1,3 @@
function abbr --description "Manage abbreviations" # This file intentionally left blank.
set -l options --stop-nonopt --exclusive 'a,r,e,l,s,q' --exclusive 'g,U' # This is provided to overwrite existing abbr.fish files, so that any abbr
set -a options h/help a/add r/rename e/erase l/list s/show q/query # function retained from past fish releases does not override the abbr builtin.
set -a options g/global U/universal
argparse -n abbr $options -- $argv
or return
if set -q _flag_help
__fish_print_help abbr
return 0
end
# If run with no options, treat it like --add if we have arguments, or
# --show if we do not have any arguments.
set -l _flag_add
set -l _flag_show
if not set -q _flag_add[1]
and not set -q _flag_rename[1]
and not set -q _flag_erase[1]
and not set -q _flag_list[1]
and not set -q _flag_show[1]
and not set -q _flag_query[1]
if set -q argv[1]
set _flag_add --add
else
set _flag_show --show
end
end
set -l abbr_scope
if set -q _flag_global
set abbr_scope --global
else if set -q _flag_universal
set abbr_scope --universal
end
if set -q _flag_add[1]
__fish_abbr_add $argv
return
else if set -q _flag_erase[1]
set -q argv[1]; or return 1
__fish_abbr_erase $argv
return
else if set -q _flag_rename[1]
__fish_abbr_rename $argv
return
else if set -q _flag_list[1]
__fish_abbr_list $argv
return
else if set -q _flag_show[1]
__fish_abbr_show $argv
return
else if set -q _flag_query[1]
# "--query": Check if abbrs exist.
# If we don't have an argument, it's an automatic failure.
set -q argv[1]; or return 1
set -l escaped _fish_abbr_(string escape --style=var -- $argv)
# We return 0 if any arg exists, whereas `set -q` returns the number of undefined arguments.
# But we should be consistent with `type -q` and `command -q`.
for var in $escaped
set -q $var; and return 0
end
return 1
else
printf ( _ "%s: Could not figure out what to do!\n" ) abbr >&2
return 127
end
end
function __fish_abbr_add --no-scope-shadowing
if not set -q argv[2]
printf ( _ "%s %s: Requires at least two arguments\n" ) abbr --add >&2
return 1
end
# Because of the way abbreviations are expanded there can't be any spaces in the key.
set -l abbr_name $argv[1]
set -l escaped_abbr_name (string escape -- $abbr_name)
if string match -q "* *" -- $abbr_name
set -l msg ( _ "%s %s: Abbreviation %s cannot have spaces in the word\n" )
printf $msg abbr --add $escaped_abbr_name >&2
return 1
end
set -l abbr_val "$argv[2..-1]"
set -l abbr_var_name _fish_abbr_(string escape --style=var -- $abbr_name)
if not set -q $abbr_var_name
# We default to the universal scope if the user didn't explicitly specify a scope and the
# abbreviation isn't already defined.
set -q abbr_scope[1]
or set abbr_scope --universal
end
true # make sure the next `set` command doesn't leak the previous status
set $abbr_scope $abbr_var_name $abbr_val
end
function __fish_abbr_erase --no-scope-shadowing
set -l ret 0
set -l abbr_var_names
for abbr_name in $argv
# Because of the way abbreviations are expanded there can't be any spaces in the key.
set -l escaped_name (string escape -- $abbr_name)
if string match -q "* *" -- $abbr_name
set -l msg ( _ "%s %s: Abbreviation %s cannot have spaces in the word\n" )
printf $msg abbr --erase $escaped_name >&2
return 1
end
set -l abbr_var_name _fish_abbr_(string escape --style=var -- $abbr_name)
set -a abbr_var_names $abbr_var_name
end
# And then erase them all in one go.
# Our return value is that of `set -e`.
set -e $abbr_var_names
end
function __fish_abbr_rename --no-scope-shadowing
if test (count $argv) -ne 2
printf ( _ "%s %s: Requires exactly two arguments\n" ) abbr --rename >&2
return 1
end
set -l old_name $argv[1]
set -l new_name $argv[2]
set -l escaped_old_name (string escape -- $old_name)
set -l escaped_new_name (string escape -- $new_name)
if string match -q "* *" -- $old_name
set -l msg ( _ "%s %s: Abbreviation %s cannot have spaces in the word\n" )
printf $msg abbr --rename $escaped_old_name >&2
return 1
end
if string match -q "* *" -- $new_name
set -l msg ( _ "%s %s: Abbreviation %s cannot have spaces in the word\n" )
printf $msg abbr --rename $escaped_new_name >&2
return 1
end
set -l old_var_name _fish_abbr_(string escape --style=var -- $old_name)
set -l new_var_name _fish_abbr_(string escape --style=var -- $new_name)
if not set -q $old_var_name
printf ( _ "%s %s: No abbreviation named %s\n" ) abbr --rename $escaped_old_name >&2
return 1
end
if set -q $new_var_name
set -l msg ( _ "%s %s: Abbreviation %s already exists, cannot rename %s\n" )
printf $msg abbr --rename $escaped_new_name $escaped_old_name >&2
return 1
end
set -l old_var_val $$old_var_name
if not set -q abbr_scope[1]
# User isn't forcing the scope so use the existing scope.
if set -ql $old_var_name
set abbr_scope --global
else
set abbr_scope --universal
end
end
set -e $old_var_name
set $abbr_scope $new_var_name $old_var_val
end
function __fish_abbr_list --no-scope-shadowing
if set -q argv[1]
printf ( _ "%s %s: Unexpected argument -- '%s'\n" ) abbr --erase $argv[1] >&2
return 1
end
for var_name in (set --names)
string match -q '_fish_abbr_*' $var_name
or continue
set -l abbr_name (string unescape --style=var (string sub -s 12 $var_name))
echo $abbr_name
end
end
function __fish_abbr_show --no-scope-shadowing
if set -q argv[1]
printf ( _ "%s %s: Unexpected argument -- '%s'\n" ) abbr --erase $argv[1] >&2
return 1
end
for var_name in (set --names)
string match -q '_fish_abbr_*' $var_name
or continue
set -l abbr_var_name $var_name
set -l abbr_name (string unescape --style=var -- (string sub -s 12 $abbr_var_name))
set -l abbr_name (string escape --style=script -- $abbr_name)
set -l abbr_val $$abbr_var_name
set -l abbr_val (string escape --style=script -- $abbr_val)
if set -ql $abbr_var_name
printf 'abbr -a %s -- %s %s\n' -l $abbr_name $abbr_val
end
if set -qg $abbr_var_name
printf 'abbr -a %s -- %s %s\n' -g $abbr_name $abbr_val
end
if set -qU $abbr_var_name
printf 'abbr -a %s -- %s %s\n' -U $abbr_name $abbr_val
end
end
end

62
src/abbrs.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "config.h" // IWYU pragma: keep
#include "abbrs.h"
#include "env.h"
#include "global_safety.h"
#include "wcstringutil.h"
static relaxed_atomic_t<uint64_t> k_abbrs_next_order{0};
abbreviation_t::abbreviation_t(wcstring replacement, abbrs_position_t position, bool from_universal)
: replacement(std::move(replacement)),
position(position),
from_universal(from_universal),
order(++k_abbrs_next_order) {}
acquired_lock<abbrs_map_t> abbrs_get_map() {
static owning_lock<std::unordered_map<wcstring, abbreviation_t>> abbrs;
return abbrs.acquire();
}
maybe_t<wcstring> abbrs_expand(const wcstring &token, abbrs_position_t position) {
auto abbrs = abbrs_get_map();
auto iter = abbrs->find(token);
maybe_t<wcstring> result{};
if (iter != abbrs->end()) {
const abbreviation_t &abbr = iter->second;
// Expand only if the positions are "compatible."
if (abbr.position == position || abbr.position == abbrs_position_t::anywhere) {
result = abbr.replacement;
}
}
return result;
}
wcstring_list_t abbrs_get_keys() {
auto abbrs = abbrs_get_map();
wcstring_list_t keys;
keys.reserve(abbrs->size());
for (const auto &kv : *abbrs) {
keys.push_back(kv.first);
}
return keys;
}
void abbrs_import_from_uvars(const std::unordered_map<wcstring, env_var_t> &uvars) {
auto abbrs = abbrs_get_map();
const wchar_t *const prefix = L"_fish_abbr_";
size_t prefix_len = wcslen(prefix);
wcstring name;
const bool from_universal = true;
for (const auto &kv : uvars) {
if (string_prefixes_string(prefix, kv.first)) {
wcstring escaped_name = kv.first.substr(prefix_len);
if (unescape_string(escaped_name, &name, unescape_flags_t{}, STRING_STYLE_VAR)) {
wcstring replacement = join_strings(kv.second.as_list(), L' ');
abbrs->emplace(name, abbreviation_t{std::move(replacement),
abbrs_position_t::command, from_universal});
}
}
}
}

53
src/abbrs.h Normal file
View File

@ -0,0 +1,53 @@
// Support for abbreviations.
//
#ifndef FISH_ABBRS_H
#define FISH_ABBRS_H
#include <unordered_map>
#include "common.h"
#include "maybe.h"
/// Controls where in the command line abbreviations may expand.
enum class abbrs_position_t : uint8_t {
command, // expand in command position
anywhere, // expand in any token
};
struct abbreviation_t {
// Replacement string.
wcstring replacement{};
// Expansion position.
abbrs_position_t position{abbrs_position_t::command};
// Mark if we came from a universal variable.
bool from_universal{};
// A monotone key to allow reconstructing definition order.
uint64_t order{};
explicit abbreviation_t(wcstring replacement,
abbrs_position_t position = abbrs_position_t::command,
bool from_universal = false);
abbreviation_t() = default;
};
using abbrs_map_t = std::unordered_map<wcstring, abbreviation_t>;
/// \return the mutable map of abbreviations, keyed by name.
acquired_lock<abbrs_map_t> abbrs_get_map();
/// \return the replacement value for a abbreviation token, if any.
/// The \p position is given to describe where the token was found.
maybe_t<wcstring> abbrs_expand(const wcstring &token, abbrs_position_t position);
/// \return the list of abbreviation keys.
wcstring_list_t abbrs_get_keys();
/// Import any abbreviations from universal variables.
class env_var_t;
void abbrs_import_from_uvars(const std::unordered_map<wcstring, env_var_t> &uvars);
#endif

View File

@ -29,6 +29,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "builtins/abbr.h"
#include "builtins/argparse.h" #include "builtins/argparse.h"
#include "builtins/bg.h" #include "builtins/bg.h"
#include "builtins/bind.h" #include "builtins/bind.h"
@ -354,6 +355,7 @@ static constexpr builtin_data_t builtin_datas[] = {
{L":", &builtin_true, N_(L"Return a successful result")}, {L":", &builtin_true, N_(L"Return a successful result")},
{L"[", &builtin_test, N_(L"Test a condition")}, {L"[", &builtin_test, N_(L"Test a condition")},
{L"_", &builtin_gettext, N_(L"Translate a string")}, {L"_", &builtin_gettext, N_(L"Translate a string")},
{L"abbr", &builtin_abbr, N_(L"Manage generics")},
{L"and", &builtin_generic, N_(L"Run command if last command succeeded")}, {L"and", &builtin_generic, N_(L"Run command if last command succeeded")},
{L"argparse", &builtin_argparse, N_(L"Parse options in fish script")}, {L"argparse", &builtin_argparse, N_(L"Parse options in fish script")},
{L"begin", &builtin_generic, N_(L"Create a block of code")}, {L"begin", &builtin_generic, N_(L"Create a block of code")},

327
src/builtins/abbr.cpp Normal file
View File

@ -0,0 +1,327 @@
// Implementation of the read builtin.
#include "config.h" // IWYU pragma: keep
#include <termios.h>
#include <unistd.h>
#include <algorithm>
#include <cerrno>
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
#include "../abbrs.h"
#include "../builtin.h"
#include "../common.h"
#include "../env.h"
#include "../io.h"
#include "../wcstringutil.h"
#include "../wgetopt.h"
#include "../wutil.h"
namespace {
static const wchar_t *const CMD = L"abbr";
struct abbr_options_t {
bool add{};
bool rename{};
bool show{};
bool list{};
bool erase{};
bool query{};
maybe_t<abbrs_position_t> position{};
wcstring_list_t args;
bool validate(io_streams_t &streams) {
// Duplicate options?
wcstring_list_t cmds;
if (add) cmds.push_back(L"add");
if (rename) cmds.push_back(L"rename");
if (show) cmds.push_back(L"show");
if (list) cmds.push_back(L"list");
if (erase) cmds.push_back(L"erase");
if (query) cmds.push_back(L"query");
if (cmds.size() > 1) {
streams.err.append_format(_(L"%ls: Cannot combine options %ls\n"), CMD,
join_strings(cmds, L", ").c_str());
return false;
}
// If run with no options, treat it like --add if we have arguments,
// or --show if we do not have any arguments.
if (cmds.empty()) {
show = args.empty();
add = !args.empty();
}
if (!add && position.has_value()) {
streams.err.append_format(_(L"%ls: --position option requires --add\n"), CMD);
return false;
}
return true;
}
};
// Print abbreviations in a fish-script friendly way.
static int abbr_show(const abbr_options_t &, io_streams_t &streams) {
const auto abbrs = abbrs_get_map();
for (const auto &kv : *abbrs) {
wcstring name = escape_string(kv.first);
const auto &abbr = kv.second;
wcstring value = escape_string(abbr.replacement);
const wchar_t *scope = (abbr.from_universal ? L"-U " : L"");
streams.out.append_format(L"abbr -a %ls-- %ls %ls\n", scope, name.c_str(), value.c_str());
}
return STATUS_CMD_OK;
}
// Print the list of abbreviation names.
static int abbr_list(const abbr_options_t &opts, io_streams_t &streams) {
const wchar_t *const subcmd = L"--list";
if (opts.args.size() > 0) {
streams.err.append_format(_(L"%ls %ls: Unexpected argument -- '%ls'\n"), CMD, subcmd,
opts.args.front().c_str());
return STATUS_INVALID_ARGS;
}
const auto abbrs = abbrs_get_map();
for (const auto &kv : *abbrs) {
wcstring name = escape_string(kv.first);
name.push_back(L'\n');
streams.out.append(name);
}
return STATUS_CMD_OK;
}
// Rename an abbreviation, deleting any existing one with the given name.
static int abbr_rename(const abbr_options_t &opts, io_streams_t &streams) {
const wchar_t *const subcmd = L"--rename";
if (opts.args.size() != 2) {
streams.err.append_format(_(L"%ls %ls: Requires exactly two arguments\n"), CMD, subcmd);
return STATUS_INVALID_ARGS;
}
const wcstring &old_name = opts.args[0];
const wcstring &new_name = opts.args[1];
if (old_name.empty() || new_name.empty()) {
streams.err.append_format(_(L"%ls %ls: Name cannot be empty\n"), CMD, subcmd);
return STATUS_INVALID_ARGS;
}
if (std::any_of(new_name.begin(), new_name.end(), iswspace)) {
streams.err.append_format(
_(L"%ls %ls: Abbreviation '%ls' cannot have spaces in the word\n"), CMD, subcmd,
new_name.c_str());
return STATUS_INVALID_ARGS;
}
auto abbrs = abbrs_get_map();
auto old_iter = abbrs->find(old_name);
if (old_iter == abbrs->end()) {
streams.err.append_format(_(L"%ls %ls: No abbreviation named %ls\n"), CMD, subcmd,
old_name.c_str());
return STATUS_CMD_ERROR;
}
// Cannot overwrite an abbreviation.
auto new_iter = abbrs->find(new_name);
if (new_iter != abbrs->end()) {
streams.err.append_format(
_(L"%ls %ls: Abbreviation %ls already exists, cannot rename %ls\n"), CMD, subcmd,
new_name.c_str(), old_name.c_str());
return STATUS_INVALID_ARGS;
}
abbreviation_t abbr = std::move(old_iter->second);
abbrs->erase(old_iter);
bool inserted = abbrs->insert(std::make_pair(std::move(new_name), std::move(abbr))).second;
assert(inserted && "Should have successfully inserted");
(void)inserted;
return STATUS_CMD_OK;
}
// Test if any args is an abbreviation.
static int abbr_query(const abbr_options_t &opts, io_streams_t &) {
// Return success if any of our args matches an abbreviation.
const auto abbrs = abbrs_get_map();
for (const auto &arg : opts.args) {
if (abbrs->find(arg) != abbrs->end()) {
return STATUS_CMD_OK;
}
}
return STATUS_CMD_ERROR;
}
// Add a named abbreviation.
static int abbr_add(const abbr_options_t &opts, io_streams_t &streams) {
const wchar_t *const subcmd = L"--add";
if (opts.args.size() < 2) {
streams.err.append_format(_(L"%ls %ls: Requires at least two arguments\n"), CMD, subcmd);
return STATUS_INVALID_ARGS;
}
const wcstring &name = opts.args[0];
if (name.empty()) {
streams.err.append_format(_(L"%ls %ls: Name cannot be empty\n"), CMD, subcmd);
return STATUS_INVALID_ARGS;
}
if (std::any_of(name.begin(), name.end(), iswspace)) {
streams.err.append_format(
_(L"%ls %ls: Abbreviation '%ls' cannot have spaces in the word\n"), CMD, subcmd,
name.c_str());
return STATUS_INVALID_ARGS;
}
abbreviation_t abbr{L""};
for (auto iter = opts.args.begin() + 1; iter != opts.args.end(); ++iter) {
if (!abbr.replacement.empty()) abbr.replacement.push_back(L' ');
abbr.replacement.append(*iter);
}
if (opts.position) {
abbr.position = *opts.position;
}
// Note historically we have allowed overwriting existing abbreviations.
auto abbrs = abbrs_get_map();
(*abbrs)[name] = std::move(abbr);
return STATUS_CMD_OK;
}
// Erase the named abbreviations.
static int abbr_erase(const abbr_options_t &opts, io_streams_t &) {
if (opts.args.empty()) {
// This has historically been a silent failure.
return STATUS_CMD_ERROR;
}
// Erase each. If any is not found, return ENV_NOT_FOUND which is historical.
int result = STATUS_CMD_OK;
auto abbrs = abbrs_get_map();
for (const auto &arg : opts.args) {
auto iter = abbrs->find(arg);
if (iter == abbrs->end()) {
result = ENV_NOT_FOUND;
} else {
abbrs->erase(iter);
}
}
return result;
}
} // namespace
maybe_t<int> builtin_abbr(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
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 };
// 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
// could be given literally, for example `abbr e emacs -nw`.
static const wchar_t *const short_options = L"-arseqgUh";
static const struct woption long_options[] = {{L"add", no_argument, 'a'},
{L"position", required_argument, 'p'},
{L"rename", no_argument, 'r'},
{L"erase", no_argument, 'e'},
{L"query", no_argument, 'q'},
{L"show", no_argument, 's'},
{L"list", no_argument, 'l'},
{L"global", no_argument, 'g'},
{L"universal", no_argument, 'U'},
{L"help", no_argument, 'h'},
{}};
int argc = builtin_count_args(argv);
int opt;
wgetopter_t w;
bool unrecognized_options_are_args = false;
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
switch (opt) {
case NON_OPTION_ARGUMENT:
// Non-option argument.
// If --add is specified (or implied by specifying no other commands), all
// unrecognized options after the *second* non-option argument are considered part
// of the abbreviation expansion itself, rather than options to the abbr command.
// For example, `abbr e emacs -nw` works, because `-nw` occurs after the second
// non-option, and --add is implied.
opts.args.push_back(w.woptarg);
if (opts.args.size() >= 2 &&
!(opts.rename || opts.show || opts.list || opts.erase || opts.query)) {
unrecognized_options_are_args = true;
}
break;
case 'a':
opts.add = true;
break;
case 'p': {
if (opts.position.has_value()) {
streams.err.append_format(_(L"%ls: Cannot specify multiple positions\n"), CMD);
return STATUS_INVALID_ARGS;
}
if (!wcscmp(w.woptarg, L"command")) {
opts.position = abbrs_position_t::command;
} else if (!wcscmp(w.woptarg, L"anywhere")) {
opts.position = abbrs_position_t::anywhere;
} else {
streams.err.append_format(_(L"%ls: Invalid position '%ls'\nPosition must be "
L"one of: command, anywhere.\n"),
CMD, w.woptarg);
return STATUS_INVALID_ARGS;
}
break;
}
case 'r':
opts.rename = true;
break;
case 'e':
opts.erase = true;
break;
case 'q':
opts.query = true;
break;
case 's':
opts.show = true;
break;
case 'l':
opts.list = true;
break;
case 'g':
case 'U':
// Kept for backwards compatibility but ignored.
break;
case 'h': {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
case '?': {
if (unrecognized_options_are_args) {
opts.args.push_back(argv[w.woptind - 1]);
} else {
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
return STATUS_INVALID_ARGS;
}
}
}
}
opts.args.insert(opts.args.end(), argv + w.woptind, argv + argc);
if (!opts.validate(streams)) {
return STATUS_INVALID_ARGS;
}
if (opts.add) return abbr_add(opts, streams);
if (opts.show) return abbr_show(opts, streams);
if (opts.list) return abbr_list(opts, streams);
if (opts.rename) return abbr_rename(opts, streams);
if (opts.erase) return abbr_erase(opts, streams);
if (opts.query) return abbr_query(opts, streams);
// validate() should error or ensure at least one path is set.
DIE("unreachable");
return STATUS_INVALID_ARGS;
}

11
src/builtins/abbr.h Normal file
View File

@ -0,0 +1,11 @@
// Prototypes for executing builtin_abbr function.
#ifndef FISH_BUILTIN_ABBR_H
#define FISH_BUILTIN_ABBR_H
#include "../maybe.h"
class parser_t;
struct io_streams_t;
maybe_t<int> builtin_abbr(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
#endif

View File

@ -24,6 +24,7 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include "abbrs.h"
#include "autoload.h" #include "autoload.h"
#include "builtin.h" #include "builtin.h"
#include "common.h" #include "common.h"
@ -668,7 +669,8 @@ void completer_t::complete_cmd(const wcstring &str_cmd) {
} }
void completer_t::complete_abbr(const wcstring &cmd) { void completer_t::complete_abbr(const wcstring &cmd) {
std::map<wcstring, wcstring> abbrs = get_abbreviations(ctx.vars); // Copy the map, so we don't hold the lock across the call to complete_strings.
abbrs_map_t abbrs = *abbrs_get_map();
completion_list_t possible_comp; completion_list_t possible_comp;
possible_comp.reserve(abbrs.size()); possible_comp.reserve(abbrs.size());
for (const auto &kv : abbrs) { for (const auto &kv : abbrs) {
@ -678,7 +680,7 @@ void completer_t::complete_abbr(const wcstring &cmd) {
auto desc_func = [&](const wcstring &key) { auto desc_func = [&](const wcstring &key) {
auto iter = abbrs.find(key); auto iter = abbrs.find(key);
assert(iter != abbrs.end() && "Abbreviation not found"); assert(iter != abbrs.end() && "Abbreviation not found");
return format_string(ABBR_DESC, iter->second.c_str()); return format_string(ABBR_DESC, iter->second.replacement.c_str());
}; };
this->complete_strings(cmd, desc_func, possible_comp, COMPLETE_NO_SPACE); this->complete_strings(cmd, desc_func, possible_comp, COMPLETE_NO_SPACE);
} }

View File

@ -18,6 +18,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "abbrs.h"
#include "common.h" #include "common.h"
#include "env_dispatch.h" #include "env_dispatch.h"
#include "env_universal_common.h" #include "env_universal_common.h"
@ -452,6 +453,10 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa
vars.globals().remove(name, ENV_GLOBAL | ENV_EXPORT); vars.globals().remove(name, ENV_GLOBAL | ENV_EXPORT);
} }
} }
// Import any abbreviations from uvars.
// Note we do not dynamically react to changes.
abbrs_import_from_uvars(table);
} }
} }

View File

@ -1326,31 +1326,3 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc
} }
return result; return result;
} }
maybe_t<wcstring> expand_abbreviation(const wcstring &src, const environment_t &vars) {
if (src.empty()) return none();
wcstring esc_src = escape_string(src, 0, STRING_STYLE_VAR);
if (esc_src.empty()) {
return none();
}
wcstring var_name = L"_fish_abbr_" + esc_src;
if (auto var_value = vars.get(var_name)) {
return var_value->as_string();
}
return none();
}
std::map<wcstring, wcstring> get_abbreviations(const environment_t &vars) {
const wcstring prefix = L"_fish_abbr_";
auto names = vars.get_names(0);
std::map<wcstring, wcstring> result;
for (const wcstring &name : names) {
if (string_prefixes_string(prefix, name)) {
wcstring key;
unescape_string(name.substr(prefix.size()), &key, UNESCAPE_DEFAULT, STRING_STYLE_VAR);
result[key] = vars.get(name)->as_string();
}
}
return result;
}

View File

@ -203,14 +203,6 @@ void expand_tilde(wcstring &input, const environment_t &vars);
/// Perform the opposite of tilde expansion on the string, which is modified in place. /// Perform the opposite of tilde expansion on the string, which is modified in place.
wcstring replace_home_directory_with_tilde(const wcstring &str, const environment_t &vars); wcstring replace_home_directory_with_tilde(const wcstring &str, const environment_t &vars);
/// Abbreviation support. Expand src as an abbreviation, returning the expanded form if found,
/// none() if not.
maybe_t<wcstring> expand_abbreviation(const wcstring &src, const environment_t &vars);
/// \return a snapshot of all abbreviations as a map abbreviation->expansion.
/// The abbreviations are unescaped, i.e. they may not be valid variable identifiers (#6166).
std::map<wcstring, wcstring> get_abbreviations(const environment_t &vars);
// Terrible hacks // Terrible hacks
bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc, bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc,
const char *const *argv); const char *const *argv);

View File

@ -46,6 +46,7 @@
#include <sanitizer/lsan_interface.h> #include <sanitizer/lsan_interface.h>
#endif #endif
#include "abbrs.h"
#include "ast.h" #include "ast.h"
#include "autoload.h" #include "autoload.h"
#include "builtin.h" #include "builtin.h"
@ -2466,34 +2467,31 @@ static void test_ifind_fuzzy() {
static void test_abbreviations() { static void test_abbreviations() {
say(L"Testing abbreviations"); say(L"Testing abbreviations");
auto &vars = parser_t::principal_parser().vars();
vars.push(true);
const std::vector<std::pair<const wcstring, const wcstring>> abbreviations = { {
{L"gc", L"git checkout"}, auto abbrs = abbrs_get_map();
{L"foo", L"bar"}, abbrs->emplace(L"gc", L"git checkout");
{L"gx", L"git checkout"}, abbrs->emplace(L"foo", L"bar");
}; abbrs->emplace(L"gx", L"git checkout");
for (const auto &kv : abbreviations) { abbrs->emplace(L"yin", abbreviation_t(L"yang", abbrs_position_t::anywhere));
int ret = vars.set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second);
if (ret != 0) err(L"Unable to set abbreviation variable");
} }
if (expand_abbreviation(L"", vars)) err(L"Unexpected success with empty abbreviation"); auto cmd = abbrs_position_t::command;
if (expand_abbreviation(L"nothing", vars)) err(L"Unexpected success with missing abbreviation"); if (abbrs_expand(L"", cmd)) err(L"Unexpected success with empty abbreviation");
if (abbrs_expand(L"nothing", cmd)) err(L"Unexpected success with missing abbreviation");
auto mresult = expand_abbreviation(L"gc", vars); auto mresult = abbrs_expand(L"gc", cmd);
if (!mresult) err(L"Unexpected failure with gc abbreviation"); if (!mresult) err(L"Unexpected failure with gc abbreviation");
if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc"); if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc");
mresult = expand_abbreviation(L"foo", vars); mresult = abbrs_expand(L"foo", cmd);
if (!mresult) err(L"Unexpected failure with foo abbreviation"); if (!mresult) err(L"Unexpected failure with foo abbreviation");
if (*mresult != L"bar") err(L"Wrong abbreviation result for foo"); if (*mresult != L"bar") err(L"Wrong abbreviation result for foo");
maybe_t<wcstring> result; maybe_t<wcstring> result;
auto expand_abbreviation_in_command = [](const wcstring &cmdline, size_t cursor_pos, auto expand_abbreviation_in_command = [](const wcstring &cmdline,
const environment_t &vars) -> maybe_t<wcstring> { size_t cursor_pos) -> maybe_t<wcstring> {
if (auto edit = reader_expand_abbreviation_in_command(cmdline, cursor_pos, vars)) { if (auto edit = reader_expand_abbreviation_at_cursor(cmdline, cursor_pos)) {
wcstring cmdline_expanded = cmdline; wcstring cmdline_expanded = cmdline;
std::vector<highlight_spec_t> colors{cmdline_expanded.size()}; std::vector<highlight_spec_t> colors{cmdline_expanded.size()};
apply_edit(&cmdline_expanded, &colors, *edit); apply_edit(&cmdline_expanded, &colors, *edit);
@ -2501,49 +2499,55 @@ static void test_abbreviations() {
} }
return none_t(); return none_t();
}; };
result = expand_abbreviation_in_command(L"just a command", 3, vars); result = expand_abbreviation_in_command(L"just a command", 3);
if (result) err(L"Command wrongly expanded on line %ld", (long)__LINE__); if (result) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
result = expand_abbreviation_in_command(L"gc somebranch", 0, vars); result = expand_abbreviation_in_command(L"gc somebranch", 0);
if (!result) err(L"Command not expanded on line %ld", (long)__LINE__); if (!result) err(L"Command not expanded on line %ld", (long)__LINE__);
result = expand_abbreviation_in_command(L"gc somebranch", const_strlen(L"gc"), vars); result = expand_abbreviation_in_command(L"gc somebranch", const_strlen(L"gc"));
if (!result) err(L"gc not expanded"); if (!result) err(L"gc not expanded");
if (result != L"git checkout somebranch") if (result != L"git checkout somebranch")
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str()); err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
// Space separation. // Space separation.
result = expand_abbreviation_in_command(L"gx somebranch", const_strlen(L"gc"), vars); result = expand_abbreviation_in_command(L"gx somebranch", const_strlen(L"gc"));
if (!result) err(L"gx not expanded"); if (!result) err(L"gx not expanded");
if (result != L"git checkout somebranch") if (result != L"git checkout somebranch")
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str()); err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
result = expand_abbreviation_in_command(L"echo hi ; gc somebranch", result =
const_strlen(L"echo hi ; g"), vars); expand_abbreviation_in_command(L"echo hi ; gc somebranch", const_strlen(L"echo hi ; g"));
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__); if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
if (result != L"echo hi ; git checkout somebranch") if (result != L"echo hi ; git checkout somebranch")
err(L"gc incorrectly expanded on line %ld", (long)__LINE__); err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
result = expand_abbreviation_in_command(L"echo (echo (echo (echo (gc ", result = expand_abbreviation_in_command(L"echo (echo (echo (echo (gc ",
const_strlen(L"echo (echo (echo (echo (gc"), vars); const_strlen(L"echo (echo (echo (echo (gc"));
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__); if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
if (result != L"echo (echo (echo (echo (git checkout ") if (result != L"echo (echo (echo (echo (git checkout ")
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str()); err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
// If commands should be expanded. // If commands should be expanded.
result = expand_abbreviation_in_command(L"if gc", const_strlen(L"if gc"), vars); result = expand_abbreviation_in_command(L"if gc", const_strlen(L"if gc"));
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__); if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
if (result != L"if git checkout") if (result != L"if git checkout")
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str()); err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
// Others should not be. // Others should not be.
result = expand_abbreviation_in_command(L"of gc", const_strlen(L"of gc"), vars); result = expand_abbreviation_in_command(L"of gc", const_strlen(L"of gc"));
if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
// Others should not be. // Others should not be.
result = expand_abbreviation_in_command(L"command gc", const_strlen(L"command gc"), vars); result = expand_abbreviation_in_command(L"command gc", const_strlen(L"command gc"));
if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); if (result) err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
vars.pop(); // yin/yang expands everywhere.
result = expand_abbreviation_in_command(L"command yin", const_strlen(L"command yin"));
if (!result) err(L"gc not expanded on line %ld", (long)__LINE__);
if (result != L"command yang") {
err(L"command yin incorrectly expanded on line %ld to '%ls'", (long)__LINE__,
result->c_str());
}
} }
/// Test path functions. /// Test path functions.
@ -3502,11 +3506,9 @@ static void test_complete() {
completions.clear(); completions.clear();
// Test abbreviations. // Test abbreviations.
auto &pvars = parser_t::principal_parser().vars();
function_add(L"testabbrsonetwothreefour", func_props); function_add(L"testabbrsonetwothreefour", func_props);
int ret = pvars.set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); abbrs_get_map()->emplace(L"testabbrsonetwothreezero", L"expansion");
completions = complete(L"testabbrsonetwothree", {}, parser->context()); completions = complete(L"testabbrsonetwothree", {}, parser->context());
do_test(ret == 0);
do_test(completions.size() == 2); do_test(completions.size() == 2);
do_test(completions.at(0).completion == L"four"); do_test(completions.at(0).completion == L"four");
do_test((completions.at(0).flags & COMPLETE_NO_SPACE) == 0); do_test((completions.at(0).flags & COMPLETE_NO_SPACE) == 0);

View File

@ -16,6 +16,7 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include "abbrs.h"
#include "ast.h" #include "ast.h"
#include "builtin.h" #include "builtin.h"
#include "color.h" #include "color.h"
@ -1334,7 +1335,8 @@ static bool command_is_valid(const wcstring &cmd, enum statement_decoration_t de
if (!is_valid && function_ok) is_valid = function_exists_no_autoload(cmd); if (!is_valid && function_ok) is_valid = function_exists_no_autoload(cmd);
// Abbreviations // Abbreviations
if (!is_valid && abbreviation_ok) is_valid = expand_abbreviation(cmd, vars).has_value(); if (!is_valid && abbreviation_ok)
is_valid = abbrs_expand(cmd, abbrs_position_t::command).has_value();
// Regular commands // Regular commands
if (!is_valid && command_ok) is_valid = path_get_path(cmd, vars).has_value(); if (!is_valid && command_ok) is_valid = path_get_path(cmd, vars).has_value();

View File

@ -44,6 +44,7 @@
#include <set> #include <set>
#include <type_traits> #include <type_traits>
#include "abbrs.h"
#include "ast.h" #include "ast.h"
#include "color.h" #include "color.h"
#include "common.h" #include "common.h"
@ -1347,10 +1348,8 @@ void reader_data_t::pager_selection_changed() {
} }
/// Expand abbreviations at the given cursor position. Does NOT inspect 'data'. /// Expand abbreviations at the given cursor position. Does NOT inspect 'data'.
maybe_t<edit_t> reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, maybe_t<edit_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline, size_t cursor_pos) {
const environment_t &vars) { // Get the surrounding command substitution.
// See if we are at "command position". Get the surrounding command substitution, and get the
// extent of the first token.
const wchar_t *const buff = cmdline.c_str(); const wchar_t *const buff = cmdline.c_str();
const wchar_t *cmdsub_begin = nullptr, *cmdsub_end = nullptr; const wchar_t *cmdsub_begin = nullptr, *cmdsub_end = nullptr;
parse_util_cmdsubst_extent(buff, cursor_pos, &cmdsub_begin, &cmdsub_end); parse_util_cmdsubst_extent(buff, cursor_pos, &cmdsub_begin, &cmdsub_end);
@ -1369,40 +1368,45 @@ maybe_t<edit_t> reader_expand_abbreviation_in_command(const wcstring &cmdline, s
ast_t::parse(subcmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens | ast_t::parse(subcmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens |
parse_flag_leave_unterminated); parse_flag_leave_unterminated);
// Look for plain statements where the cursor is at the end of the command. // Find a leaf node where the cursor is at its end.
const ast::string_t *matching_cmd_node = nullptr; const node_t *leaf = nullptr;
for (const node_t &n : ast) { traversal_t tv = ast.walk();
const auto *stmt = n.try_as<decorated_statement_t>(); while (const node_t *node = tv.next()) {
if (!stmt) continue; if (node->category == category_t::leaf) {
auto r = node->try_source_range();
// Skip if we have a decoration. if (r && r->start <= subcmd_cursor_pos && subcmd_cursor_pos <= r->end()) {
if (stmt->opt_decoration) continue; leaf = node;
break;
// See if the command's source range range contains our cursor, including at the end. }
auto msource = stmt->command.try_source_range();
if (!msource) continue;
// Now see if its source range contains our cursor, including at the end.
if (subcmd_cursor_pos >= msource->start &&
subcmd_cursor_pos <= msource->start + msource->length) {
// Success!
matching_cmd_node = &stmt->command;
break;
} }
} }
if (!leaf) {
return none();
}
// Now if we found a command node, expand it. // We found the leaf node before the cursor.
maybe_t<edit_t> result{}; // Decide if this leaf is in "command position:" it is the command part of an undecorated
if (matching_cmd_node) { // statement.
assert(!matching_cmd_node->unsourced && "Should not be unsourced"); bool leaf_is_command = false;
const wcstring token = matching_cmd_node->source(subcmd); for (const node_t *cursor = leaf; cursor; cursor = cursor->parent) {
if (auto abbreviation = expand_abbreviation(token, vars)) { if (const auto *stmt = cursor->try_as<decorated_statement_t>()) {
// There was an abbreviation! Replace the token in the full command. Maintain the if (!stmt->opt_decoration && leaf == &stmt->command) {
// relative position of the cursor. leaf_is_command = true;
source_range_t r = matching_cmd_node->source_range(); break;
result = edit_t(subcmd_offset + r.start, r.length, std::move(*abbreviation)); }
} }
} }
abbrs_position_t expand_position =
leaf_is_command ? abbrs_position_t::command : abbrs_position_t::anywhere;
// Now we can expand the abbreviation.
maybe_t<edit_t> result{};
if (auto abbreviation = abbrs_expand(leaf->source(subcmd), expand_position)) {
// There was an abbreviation! Replace the token in the full command. Maintain the
// relative position of the cursor.
source_range_t r = leaf->source_range();
result = edit_t(subcmd_offset + r.start, r.length, abbreviation.acquire());
}
return result; return result;
} }
@ -1417,7 +1421,7 @@ bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) {
// Try expanding abbreviations. // Try expanding abbreviations.
size_t cursor_pos = el->position() - std::min(el->position(), cursor_backtrack); size_t cursor_pos = el->position() - std::min(el->position(), cursor_backtrack);
if (auto edit = reader_expand_abbreviation_in_command(el->text(), cursor_pos, vars())) { if (auto edit = reader_expand_abbreviation_at_cursor(el->text(), cursor_pos)) {
push_edit(el, std::move(*edit)); push_edit(el, std::move(*edit));
update_buff_pos(el); update_buff_pos(el);
result = true; result = true;

View File

@ -263,8 +263,7 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline,
/// Expand abbreviations at the given cursor position. Exposed for testing purposes only. /// Expand abbreviations at the given cursor position. Exposed for testing purposes only.
/// \return none if no abbreviations were expanded, otherwise the new command line. /// \return none if no abbreviations were expanded, otherwise the new command line.
maybe_t<edit_t> reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, maybe_t<edit_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline, size_t cursor_pos);
const environment_t &vars);
/// Apply a completion string. Exposed for testing only. /// Apply a completion string. Exposed for testing only.
wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags, wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags,

View File

@ -286,12 +286,12 @@ wcstring_list_t split_string_tok(const wcstring &val, const wcstring &seps, size
return out; return out;
} }
wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) { static wcstring join_strings_impl(const wcstring_list_t &vals, const wchar_t *sep, size_t seplen) {
if (vals.empty()) return wcstring{}; if (vals.empty()) return wcstring{};
// Reserve the size we will need. // Reserve the size we will need.
// count-1 separators, plus the length of all strings. // count-1 separators, plus the length of all strings.
size_t size = vals.size() - 1; size_t size = (vals.size() - 1) * seplen;
for (const wcstring &s : vals) { for (const wcstring &s : vals) {
size += s.size(); size += s.size();
} }
@ -302,7 +302,7 @@ wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
bool first = true; bool first = true;
for (const wcstring &s : vals) { for (const wcstring &s : vals) {
if (!first) { if (!first) {
result.push_back(sep); result.append(sep, seplen);
} }
result.append(s); result.append(s);
first = false; first = false;
@ -310,6 +310,14 @@ wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
return result; return result;
} }
wcstring join_strings(const wcstring_list_t &vals, wchar_t c) {
return join_strings_impl(vals, &c, 1);
}
wcstring join_strings(const wcstring_list_t &vals, const wchar_t *sep) {
return join_strings_impl(vals, sep, wcslen(sep));
}
void wcs2string_bad_char(wchar_t wc) { void wcs2string_bad_char(wchar_t wc) {
FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", wc); FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", wc);
} }

View File

@ -137,8 +137,9 @@ wcstring_list_t split_string(const wcstring &val, wchar_t sep);
wcstring_list_t split_string_tok(const wcstring &val, const wcstring &seps, wcstring_list_t split_string_tok(const wcstring &val, const wcstring &seps,
size_t max_results = std::numeric_limits<size_t>::max()); size_t max_results = std::numeric_limits<size_t>::max());
/// Join a list of strings by a separator character. /// Join a list of strings by a separator character or string.
wcstring join_strings(const wcstring_list_t &vals, wchar_t sep); wcstring join_strings(const wcstring_list_t &vals, wchar_t sep);
wcstring join_strings(const wcstring_list_t &vals, const wchar_t *sep);
inline wcstring to_string(long x) { inline wcstring to_string(long x) {
wchar_t buff[64]; wchar_t buff[64];

View File

@ -1,29 +1,36 @@
#RUN: %fish %s #RUN: %fish %s
# Universal abbreviations are imported.
set -U _fish_abbr_cuckoo somevalue
set fish (status fish-path)
$fish -c abbr
# CHECK: abbr -a -U -- cuckoo somevalue
# Test basic add and list of __abbr1 # Test basic add and list of __abbr1
abbr __abbr1 alpha beta gamma abbr __abbr1 alpha beta gamma
abbr | grep __abbr1 abbr | grep __abbr1
# CHECK: abbr -a -U -- __abbr1 'alpha beta gamma' # CHECK: abbr -a -- __abbr1 'alpha beta gamma'
# Erasing one that doesn\'t exist should do nothing # Erasing one that doesn\'t exist should do nothing
abbr --erase NOT_AN_ABBR abbr --erase NOT_AN_ABBR
abbr | grep __abbr1 abbr | grep __abbr1
# CHECK: abbr -a -U -- __abbr1 'alpha beta gamma' # CHECK: abbr -a -- __abbr1 'alpha beta gamma'
# Adding existing __abbr1 should be idempotent # Adding existing __abbr1 should be idempotent
abbr __abbr1 alpha beta gamma abbr __abbr1 alpha beta gamma
abbr | grep __abbr1 abbr | grep __abbr1
# CHECK: abbr -a -U -- __abbr1 'alpha beta gamma' # CHECK: abbr -a -- __abbr1 'alpha beta gamma'
# Replacing __abbr1 definition # Replacing __abbr1 definition
abbr __abbr1 delta abbr __abbr1 delta
abbr | grep __abbr1 abbr | grep __abbr1
# CHECK: abbr -a -U -- __abbr1 delta # CHECK: abbr -a -- __abbr1 delta
# __abbr1 -s and --show tests # __abbr1 -s and --show tests
abbr -s | grep __abbr1 abbr -s | grep __abbr1
abbr --show | grep __abbr1 abbr --show | grep __abbr1
# CHECK: abbr -a -U -- __abbr1 delta # CHECK: abbr -a -- __abbr1 delta
# CHECK: abbr -a -U -- __abbr1 delta # CHECK: abbr -a -- __abbr1 delta
# Test erasing __abbr1 # Test erasing __abbr1
abbr -e __abbr1 abbr -e __abbr1
@ -32,13 +39,13 @@ abbr | grep __abbr1
# Ensure we escape special characters on output # Ensure we escape special characters on output
abbr '~__abbr2' '$xyz' abbr '~__abbr2' '$xyz'
abbr | grep __abbr2 abbr | grep __abbr2
# CHECK: abbr -a -U -- '~__abbr2' '$xyz' # CHECK: abbr -a -- '~__abbr2' '$xyz'
abbr -e '~__abbr2' abbr -e '~__abbr2'
# Ensure we handle leading dashes in abbreviation names properly # Ensure we handle leading dashes in abbreviation names properly
abbr -- --__abbr3 xyz abbr -- --__abbr3 xyz
abbr | grep __abbr3 abbr | grep __abbr3
# CHECK: abbr -a -U -- --__abbr3 xyz # CHECK: abbr -a -- --__abbr3 xyz
abbr -e -- --__abbr3 abbr -e -- --__abbr3
# Test that an abbr word containing spaces is rejected # Test that an abbr word containing spaces is rejected
@ -51,7 +58,7 @@ abbr __abbr4 omega
abbr | grep __abbr5 abbr | grep __abbr5
abbr -r __abbr4 __abbr5 abbr -r __abbr4 __abbr5
abbr | grep __abbr5 abbr | grep __abbr5
# CHECK: abbr -a -U -- __abbr5 omega # CHECK: abbr -a -- __abbr5 omega
abbr -e __abbr5 abbr -e __abbr5
abbr | grep __abbr4 abbr | grep __abbr4
@ -77,7 +84,7 @@ abbr -r __abbr8 __abbr9 __abbr10
abbr | grep __abbr8 abbr | grep __abbr8
abbr | grep __abbr9 abbr | grep __abbr9
abbr | grep __abbr10 abbr | grep __abbr10
# CHECK: abbr -a -U -- __abbr8 omega # CHECK: abbr -a -- __abbr8 omega
# Test renaming to existing abbreviation # Test renaming to existing abbreviation
abbr __abbr11 omega11 abbr __abbr11 omega11
@ -106,3 +113,22 @@ echo $status
abbr -q banana __abbr8 foobar abbr -q banana __abbr8 foobar
echo $status echo $status
# CHECK: 0 # CHECK: 0
abbr --add grape --position nowhere juice
echo $status
# CHECKERR: abbr: Invalid position 'nowhere'
# CHECKERR: Position must be one of: command, anywhere.
# CHECK: 2
abbr --add grape --position anywhere juice
echo $status
# CHECK: 0
abbr --add grape --position command juice
echo $status
# CHECK: 0
abbr --query banana --position anywhere
echo $status
# CHECKERR: abbr: --position option requires --add
# CHECK: 2

View File

@ -19,13 +19,13 @@ sendline(r"""bind '?' 'echo "<$(commandline)>"; commandline ""'; """)
expect_prompt() expect_prompt()
# Basic test. # Basic test.
# Default abbreviations expand only in command position.
sendline(r"abbr alpha beta") sendline(r"abbr alpha beta")
expect_prompt() expect_prompt()
send(r"alpha ?") send(r"alpha ?")
expect_str(r"<beta >") expect_str(r"<beta >")
# Default abbreviations expand only in command position.
send(r"echo alpha ?") send(r"echo alpha ?")
expect_str(r"<echo alpha >") expect_str(r"<echo alpha >")
@ -50,3 +50,12 @@ sendline(r"echo )")
expect_str(r"Unexpected ')' for unopened parenthesis") expect_str(r"Unexpected ')' for unopened parenthesis")
send(r"?") send(r"?")
expect_str(r"<echo )>") expect_str(r"<echo )>")
# Support position anywhere.
sendline(r"abbr alpha --position anywhere beta2")
send(r"alpha ?")
expect_str(r"<beta2 >")
send(r"echo alpha ?")
expect_str(r"<echo beta2 >")