mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-22 12:41:08 +08:00
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:
parent
635cc3ee8d
commit
1402bae7f4
|
@ -91,7 +91,7 @@ endif()
|
|||
|
||||
# List of sources for builtin functions.
|
||||
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/builtin.cpp src/builtins/cd.cpp src/builtins/command.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.
|
||||
set(FISH_SRCS
|
||||
src/ast.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp src/env.cpp
|
||||
src/env_dispatch.cpp src/env_universal_common.cpp src/event.cpp src/exec.cpp
|
||||
src/expand.cpp src/fallback.cpp src/fd_monitor.cpp src/fish_version.cpp
|
||||
src/ast.cpp src/abbrs.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp
|
||||
src/env.cpp src/env_dispatch.cpp src/env_universal_common.cpp src/event.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/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
|
||||
|
|
|
@ -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).
|
||||
|
||||
::
|
||||
|
||||
abbr -a -U l less
|
||||
abbr -a l less
|
||||
|
||||
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.
|
||||
|
||||
::
|
||||
|
||||
|
|
|
@ -1,210 +1,3 @@
|
|||
function abbr --description "Manage abbreviations"
|
||||
set -l options --stop-nonopt --exclusive 'a,r,e,l,s,q' --exclusive 'g,U'
|
||||
set -a options h/help a/add r/rename e/erase l/list s/show q/query
|
||||
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
|
||||
# This file intentionally left blank.
|
||||
# This is provided to overwrite existing abbr.fish files, so that any abbr
|
||||
# function retained from past fish releases does not override the abbr builtin.
|
||||
|
|
62
src/abbrs.cpp
Normal file
62
src/abbrs.cpp
Normal 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
53
src/abbrs.h
Normal 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
|
|
@ -29,6 +29,7 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "builtins/abbr.h"
|
||||
#include "builtins/argparse.h"
|
||||
#include "builtins/bg.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_test, N_(L"Test a condition")},
|
||||
{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"argparse", &builtin_argparse, N_(L"Parse options in fish script")},
|
||||
{L"begin", &builtin_generic, N_(L"Create a block of code")},
|
||||
|
|
327
src/builtins/abbr.cpp
Normal file
327
src/builtins/abbr.cpp
Normal 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
11
src/builtins/abbr.h
Normal 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
|
|
@ -24,6 +24,7 @@
|
|||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "abbrs.h"
|
||||
#include "autoload.h"
|
||||
#include "builtin.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) {
|
||||
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;
|
||||
possible_comp.reserve(abbrs.size());
|
||||
for (const auto &kv : abbrs) {
|
||||
|
@ -678,7 +680,7 @@ void completer_t::complete_abbr(const wcstring &cmd) {
|
|||
auto desc_func = [&](const wcstring &key) {
|
||||
auto iter = abbrs.find(key);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "abbrs.h"
|
||||
#include "common.h"
|
||||
#include "env_dispatch.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);
|
||||
}
|
||||
}
|
||||
|
||||
// Import any abbreviations from uvars.
|
||||
// Note we do not dynamically react to changes.
|
||||
abbrs_import_from_uvars(table);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1326,31 +1326,3 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
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
|
||||
bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc,
|
||||
const char *const *argv);
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include <sanitizer/lsan_interface.h>
|
||||
#endif
|
||||
|
||||
#include "abbrs.h"
|
||||
#include "ast.h"
|
||||
#include "autoload.h"
|
||||
#include "builtin.h"
|
||||
|
@ -2466,34 +2467,31 @@ static void test_ifind_fuzzy() {
|
|||
|
||||
static void test_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"},
|
||||
{L"foo", L"bar"},
|
||||
{L"gx", L"git checkout"},
|
||||
};
|
||||
for (const auto &kv : abbreviations) {
|
||||
int ret = vars.set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second);
|
||||
if (ret != 0) err(L"Unable to set abbreviation variable");
|
||||
{
|
||||
auto abbrs = abbrs_get_map();
|
||||
abbrs->emplace(L"gc", L"git checkout");
|
||||
abbrs->emplace(L"foo", L"bar");
|
||||
abbrs->emplace(L"gx", L"git checkout");
|
||||
abbrs->emplace(L"yin", abbreviation_t(L"yang", abbrs_position_t::anywhere));
|
||||
}
|
||||
|
||||
if (expand_abbreviation(L"", vars)) err(L"Unexpected success with empty abbreviation");
|
||||
if (expand_abbreviation(L"nothing", vars)) err(L"Unexpected success with missing abbreviation");
|
||||
auto cmd = abbrs_position_t::command;
|
||||
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 != 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 != L"bar") err(L"Wrong abbreviation result for foo");
|
||||
|
||||
maybe_t<wcstring> result;
|
||||
auto expand_abbreviation_in_command = [](const wcstring &cmdline, size_t cursor_pos,
|
||||
const environment_t &vars) -> maybe_t<wcstring> {
|
||||
if (auto edit = reader_expand_abbreviation_in_command(cmdline, cursor_pos, vars)) {
|
||||
auto expand_abbreviation_in_command = [](const wcstring &cmdline,
|
||||
size_t cursor_pos) -> maybe_t<wcstring> {
|
||||
if (auto edit = reader_expand_abbreviation_at_cursor(cmdline, cursor_pos)) {
|
||||
wcstring cmdline_expanded = cmdline;
|
||||
std::vector<highlight_spec_t> colors{cmdline_expanded.size()};
|
||||
apply_edit(&cmdline_expanded, &colors, *edit);
|
||||
|
@ -2501,49 +2499,55 @@ static void test_abbreviations() {
|
|||
}
|
||||
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__);
|
||||
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__);
|
||||
|
||||
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 != L"git checkout somebranch")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
// 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 != L"git checkout somebranch")
|
||||
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",
|
||||
const_strlen(L"echo hi ; g"), vars);
|
||||
result =
|
||||
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 != L"echo hi ; git checkout somebranch")
|
||||
err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
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 != L"echo (echo (echo (echo (git checkout ")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
// 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 != L"if git checkout")
|
||||
err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result->c_str());
|
||||
|
||||
// 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__);
|
||||
|
||||
// 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__);
|
||||
|
||||
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.
|
||||
|
@ -3502,11 +3506,9 @@ static void test_complete() {
|
|||
completions.clear();
|
||||
|
||||
// Test abbreviations.
|
||||
auto &pvars = parser_t::principal_parser().vars();
|
||||
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());
|
||||
do_test(ret == 0);
|
||||
do_test(completions.size() == 2);
|
||||
do_test(completions.at(0).completion == L"four");
|
||||
do_test((completions.at(0).flags & COMPLETE_NO_SPACE) == 0);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "abbrs.h"
|
||||
#include "ast.h"
|
||||
#include "builtin.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);
|
||||
|
||||
// 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
|
||||
if (!is_valid && command_ok) is_valid = path_get_path(cmd, vars).has_value();
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include <set>
|
||||
#include <type_traits>
|
||||
|
||||
#include "abbrs.h"
|
||||
#include "ast.h"
|
||||
#include "color.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'.
|
||||
maybe_t<edit_t> reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos,
|
||||
const environment_t &vars) {
|
||||
// See if we are at "command position". Get the surrounding command substitution, and get the
|
||||
// extent of the first token.
|
||||
maybe_t<edit_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline, size_t cursor_pos) {
|
||||
// Get the surrounding command substitution.
|
||||
const wchar_t *const buff = cmdline.c_str();
|
||||
const wchar_t *cmdsub_begin = nullptr, *cmdsub_end = nullptr;
|
||||
parse_util_cmdsubst_extent(buff, cursor_pos, &cmdsub_begin, &cmdsub_end);
|
||||
|
@ -1369,39 +1368,44 @@ 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 |
|
||||
parse_flag_leave_unterminated);
|
||||
|
||||
// Look for plain statements where the cursor is at the end of the command.
|
||||
const ast::string_t *matching_cmd_node = nullptr;
|
||||
for (const node_t &n : ast) {
|
||||
const auto *stmt = n.try_as<decorated_statement_t>();
|
||||
if (!stmt) continue;
|
||||
|
||||
// Skip if we have a decoration.
|
||||
if (stmt->opt_decoration) continue;
|
||||
|
||||
// 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;
|
||||
// Find a leaf node where the cursor is at its end.
|
||||
const node_t *leaf = nullptr;
|
||||
traversal_t tv = ast.walk();
|
||||
while (const node_t *node = tv.next()) {
|
||||
if (node->category == category_t::leaf) {
|
||||
auto r = node->try_source_range();
|
||||
if (r && r->start <= subcmd_cursor_pos && subcmd_cursor_pos <= r->end()) {
|
||||
leaf = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!leaf) {
|
||||
return none();
|
||||
}
|
||||
|
||||
// Now if we found a command node, expand it.
|
||||
// We found the leaf node before the cursor.
|
||||
// Decide if this leaf is in "command position:" it is the command part of an undecorated
|
||||
// statement.
|
||||
bool leaf_is_command = false;
|
||||
for (const node_t *cursor = leaf; cursor; cursor = cursor->parent) {
|
||||
if (const auto *stmt = cursor->try_as<decorated_statement_t>()) {
|
||||
if (!stmt->opt_decoration && leaf == &stmt->command) {
|
||||
leaf_is_command = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (matching_cmd_node) {
|
||||
assert(!matching_cmd_node->unsourced && "Should not be unsourced");
|
||||
const wcstring token = matching_cmd_node->source(subcmd);
|
||||
if (auto abbreviation = expand_abbreviation(token, vars)) {
|
||||
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 = matching_cmd_node->source_range();
|
||||
result = edit_t(subcmd_offset + r.start, r.length, std::move(*abbreviation));
|
||||
}
|
||||
source_range_t r = leaf->source_range();
|
||||
result = edit_t(subcmd_offset + r.start, r.length, abbreviation.acquire());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1417,7 +1421,7 @@ bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) {
|
|||
// Try expanding abbreviations.
|
||||
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));
|
||||
update_buff_pos(el);
|
||||
result = true;
|
||||
|
|
|
@ -263,8 +263,7 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline,
|
|||
|
||||
/// Expand abbreviations at the given cursor position. Exposed for testing purposes only.
|
||||
/// \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,
|
||||
const environment_t &vars);
|
||||
maybe_t<edit_t> reader_expand_abbreviation_at_cursor(const wcstring &cmdline, size_t cursor_pos);
|
||||
|
||||
/// Apply a completion string. Exposed for testing only.
|
||||
wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags,
|
||||
|
|
|
@ -286,12 +286,12 @@ wcstring_list_t split_string_tok(const wcstring &val, const wcstring &seps, size
|
|||
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{};
|
||||
|
||||
// Reserve the size we will need.
|
||||
// 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) {
|
||||
size += s.size();
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
|
|||
bool first = true;
|
||||
for (const wcstring &s : vals) {
|
||||
if (!first) {
|
||||
result.push_back(sep);
|
||||
result.append(sep, seplen);
|
||||
}
|
||||
result.append(s);
|
||||
first = false;
|
||||
|
@ -310,6 +310,14 @@ wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
|
|||
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) {
|
||||
FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", wc);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
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, const wchar_t *sep);
|
||||
|
||||
inline wcstring to_string(long x) {
|
||||
wchar_t buff[64];
|
||||
|
|
|
@ -1,29 +1,36 @@
|
|||
#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
|
||||
abbr __abbr1 alpha beta gamma
|
||||
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
|
||||
abbr --erase NOT_AN_ABBR
|
||||
abbr | grep __abbr1
|
||||
# CHECK: abbr -a -U -- __abbr1 'alpha beta gamma'
|
||||
# CHECK: abbr -a -- __abbr1 'alpha beta gamma'
|
||||
|
||||
# Adding existing __abbr1 should be idempotent
|
||||
abbr __abbr1 alpha beta gamma
|
||||
abbr | grep __abbr1
|
||||
# CHECK: abbr -a -U -- __abbr1 'alpha beta gamma'
|
||||
# CHECK: abbr -a -- __abbr1 'alpha beta gamma'
|
||||
|
||||
# Replacing __abbr1 definition
|
||||
abbr __abbr1 delta
|
||||
abbr | grep __abbr1
|
||||
# CHECK: abbr -a -U -- __abbr1 delta
|
||||
# CHECK: abbr -a -- __abbr1 delta
|
||||
|
||||
# __abbr1 -s and --show tests
|
||||
abbr -s | grep __abbr1
|
||||
abbr --show | grep __abbr1
|
||||
# CHECK: abbr -a -U -- __abbr1 delta
|
||||
# CHECK: abbr -a -U -- __abbr1 delta
|
||||
# CHECK: abbr -a -- __abbr1 delta
|
||||
# CHECK: abbr -a -- __abbr1 delta
|
||||
|
||||
# Test erasing __abbr1
|
||||
abbr -e __abbr1
|
||||
|
@ -32,13 +39,13 @@ abbr | grep __abbr1
|
|||
# Ensure we escape special characters on output
|
||||
abbr '~__abbr2' '$xyz'
|
||||
abbr | grep __abbr2
|
||||
# CHECK: abbr -a -U -- '~__abbr2' '$xyz'
|
||||
# CHECK: abbr -a -- '~__abbr2' '$xyz'
|
||||
abbr -e '~__abbr2'
|
||||
|
||||
# Ensure we handle leading dashes in abbreviation names properly
|
||||
abbr -- --__abbr3 xyz
|
||||
abbr | grep __abbr3
|
||||
# CHECK: abbr -a -U -- --__abbr3 xyz
|
||||
# CHECK: abbr -a -- --__abbr3 xyz
|
||||
abbr -e -- --__abbr3
|
||||
|
||||
# Test that an abbr word containing spaces is rejected
|
||||
|
@ -51,7 +58,7 @@ abbr __abbr4 omega
|
|||
abbr | grep __abbr5
|
||||
abbr -r __abbr4 __abbr5
|
||||
abbr | grep __abbr5
|
||||
# CHECK: abbr -a -U -- __abbr5 omega
|
||||
# CHECK: abbr -a -- __abbr5 omega
|
||||
abbr -e __abbr5
|
||||
abbr | grep __abbr4
|
||||
|
||||
|
@ -77,7 +84,7 @@ abbr -r __abbr8 __abbr9 __abbr10
|
|||
abbr | grep __abbr8
|
||||
abbr | grep __abbr9
|
||||
abbr | grep __abbr10
|
||||
# CHECK: abbr -a -U -- __abbr8 omega
|
||||
# CHECK: abbr -a -- __abbr8 omega
|
||||
|
||||
# Test renaming to existing abbreviation
|
||||
abbr __abbr11 omega11
|
||||
|
@ -106,3 +113,22 @@ echo $status
|
|||
abbr -q banana __abbr8 foobar
|
||||
echo $status
|
||||
# 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
|
||||
|
|
|
@ -19,13 +19,13 @@ sendline(r"""bind '?' 'echo "<$(commandline)>"; commandline ""'; """)
|
|||
expect_prompt()
|
||||
|
||||
# Basic test.
|
||||
# Default abbreviations expand only in command position.
|
||||
sendline(r"abbr alpha beta")
|
||||
expect_prompt()
|
||||
|
||||
send(r"alpha ?")
|
||||
expect_str(r"<beta >")
|
||||
|
||||
# Default abbreviations expand only in command position.
|
||||
send(r"echo alpha ?")
|
||||
expect_str(r"<echo alpha >")
|
||||
|
||||
|
@ -50,3 +50,12 @@ sendline(r"echo )")
|
|||
expect_str(r"Unexpected ')' for unopened parenthesis")
|
||||
send(r"?")
|
||||
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 >")
|
||||
|
|
Loading…
Reference in New Issue
Block a user