mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
Support for command wrapping ("aliases")
Add the --wraps option to 'complete' and 'function'. This allows a command to (recursively) inherit the completions of a wrapped command. Fixes #393. When evaluating a completion, we inspect the entire "wrap chain" for a command, i.e. we follow the sequence of wrapping until we either hit a loop (which we silently ignore) or the end of the chain. We then evaluate completions as if the wrapping command were substituted with the wrapped command. Currently this only works for commands, i.e. 'complete --command gco --wraps git\ checkout' won't work (that would seem to encroaching on abbreviations anyways). It might be useful to show an error message for that case. The commandline builtin reflects the commandline with the wrapped command substituted in, so e.g. git completions (which inspect the command line) will just work. This sort of command line munging is also performed by 'complete -C' so it's not totally without precedent. 'alias will also now mark its generated function as wrapping the 'target.
This commit is contained in:
parent
fe68d30be9
commit
06400b83b1
10 changed files with 316 additions and 35 deletions
13
builtin.cpp
13
builtin.cpp
|
@ -1809,6 +1809,8 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
|
||||||
bool shadows = true;
|
bool shadows = true;
|
||||||
|
|
||||||
woptind=0;
|
woptind=0;
|
||||||
|
|
||||||
|
wcstring_list_t wrap_targets;
|
||||||
|
|
||||||
const struct woption long_options[] =
|
const struct woption long_options[] =
|
||||||
{
|
{
|
||||||
|
@ -1818,6 +1820,7 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
|
||||||
{ L"on-process-exit", required_argument, 0, 'p' },
|
{ L"on-process-exit", required_argument, 0, 'p' },
|
||||||
{ L"on-variable", required_argument, 0, 'v' },
|
{ L"on-variable", required_argument, 0, 'v' },
|
||||||
{ L"on-event", required_argument, 0, 'e' },
|
{ L"on-event", required_argument, 0, 'e' },
|
||||||
|
{ L"wraps", required_argument, 0, 'w' },
|
||||||
{ L"help", no_argument, 0, 'h' },
|
{ L"help", no_argument, 0, 'h' },
|
||||||
{ L"argument-names", no_argument, 0, 'a' },
|
{ L"argument-names", no_argument, 0, 'a' },
|
||||||
{ L"no-scope-shadowing", no_argument, 0, 'S' },
|
{ L"no-scope-shadowing", no_argument, 0, 'S' },
|
||||||
|
@ -1979,6 +1982,10 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
|
||||||
case 'S':
|
case 'S':
|
||||||
shadows = 0;
|
shadows = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
wrap_targets.push_back(woptarg);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
builtin_print_help(parser, argv[0], stdout_buffer);
|
builtin_print_help(parser, argv[0], stdout_buffer);
|
||||||
|
@ -2086,6 +2093,12 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
|
||||||
d.definition = contents.c_str();
|
d.definition = contents.c_str();
|
||||||
|
|
||||||
function_add(d, parser, definition_line_offset);
|
function_add(d, parser, definition_line_offset);
|
||||||
|
|
||||||
|
// Handle wrap targets
|
||||||
|
for (size_t w=0; w < wrap_targets.size(); w++)
|
||||||
|
{
|
||||||
|
complete_add_wrapper(name, wrap_targets.at(w));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
23
builtin.h
23
builtin.h
|
@ -164,12 +164,25 @@ void builtin_pop_io(parser_t &parser);
|
||||||
wcstring builtin_get_desc(const wcstring &b);
|
wcstring builtin_get_desc(const wcstring &b);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Slightly kludgy function used with 'complete -C' in order to make
|
/** Support for setting and removing transient command lines.
|
||||||
the commandline builtin operate on the string to complete instead
|
This is used by 'complete -C' in order to make
|
||||||
of operating on whatever is to be completed.
|
the commandline builtin operate on the string to complete instead
|
||||||
|
of operating on whatever is to be completed. It's also used by
|
||||||
|
completion wrappers, to allow a command to appear as the command
|
||||||
|
being wrapped for the purposes of completion.
|
||||||
|
|
||||||
|
Instantiating an instance of builtin_commandline_scoped_transient_t
|
||||||
|
pushes the command as the new transient commandline. The destructor removes it.
|
||||||
|
It will assert if construction/destruction does not happen in a stack-like (LIFO) order.
|
||||||
*/
|
*/
|
||||||
const wchar_t *builtin_complete_get_temporary_buffer();
|
class builtin_commandline_scoped_transient_t
|
||||||
|
{
|
||||||
|
size_t token;
|
||||||
|
public:
|
||||||
|
builtin_commandline_scoped_transient_t(const wcstring &cmd);
|
||||||
|
~builtin_commandline_scoped_transient_t();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -79,6 +79,51 @@ static size_t get_cursor_pos()
|
||||||
return current_cursor_pos;
|
return current_cursor_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static pthread_mutex_t transient_commandline_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static wcstring_list_t *get_transient_stack()
|
||||||
|
{
|
||||||
|
ASSERT_IS_MAIN_THREAD();
|
||||||
|
ASSERT_IS_LOCKED(transient_commandline_lock);
|
||||||
|
// A pointer is a little more efficient than an object as a static because we can elide the thread-safe initialization
|
||||||
|
static wcstring_list_t *result = NULL;
|
||||||
|
if (! result)
|
||||||
|
{
|
||||||
|
result = new wcstring_list_t();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_top_transient(wcstring *out_result)
|
||||||
|
{
|
||||||
|
ASSERT_IS_MAIN_THREAD();
|
||||||
|
bool result = false;
|
||||||
|
scoped_lock locker(transient_commandline_lock);
|
||||||
|
const wcstring_list_t *stack = get_transient_stack();
|
||||||
|
if (! stack->empty())
|
||||||
|
{
|
||||||
|
out_result->assign(stack->back());
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
builtin_commandline_scoped_transient_t::builtin_commandline_scoped_transient_t(const wcstring &cmd)
|
||||||
|
{
|
||||||
|
ASSERT_IS_MAIN_THREAD();
|
||||||
|
scoped_lock locker(transient_commandline_lock);
|
||||||
|
wcstring_list_t *stack = get_transient_stack();
|
||||||
|
stack->push_back(cmd);
|
||||||
|
this->token = stack->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
builtin_commandline_scoped_transient_t::~builtin_commandline_scoped_transient_t()
|
||||||
|
{
|
||||||
|
ASSERT_IS_MAIN_THREAD();
|
||||||
|
scoped_lock locker(transient_commandline_lock);
|
||||||
|
wcstring_list_t *stack = get_transient_stack();
|
||||||
|
assert(this->token == stack->size());
|
||||||
|
stack->pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Replace/append/insert the selection with/at/after the specified string.
|
Replace/append/insert the selection with/at/after the specified string.
|
||||||
|
@ -216,11 +261,15 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
|
||||||
int search_mode = 0;
|
int search_mode = 0;
|
||||||
int paging_mode = 0;
|
int paging_mode = 0;
|
||||||
const wchar_t *begin = NULL, *end = NULL;
|
const wchar_t *begin = NULL, *end = NULL;
|
||||||
|
|
||||||
current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer();
|
scoped_push<const wchar_t *> saved_current_buffer(¤t_buffer);
|
||||||
if (current_buffer)
|
scoped_push<size_t> saved_current_cursor_pos(¤t_cursor_pos);
|
||||||
|
|
||||||
|
wcstring transient_commandline;
|
||||||
|
if (get_top_transient(&transient_commandline))
|
||||||
{
|
{
|
||||||
current_cursor_pos = wcslen(current_buffer);
|
current_buffer = transient_commandline.c_str();
|
||||||
|
current_cursor_pos = transient_commandline.size();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,12 +24,6 @@ Functions used for implementing the complete builtin.
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "reader.h"
|
#include "reader.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Internal storage for the builtin_complete_get_temporary_buffer() function.
|
|
||||||
*/
|
|
||||||
static const wchar_t *temporary_buffer;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
builtin_complete_* are a set of rather silly looping functions that
|
builtin_complete_* are a set of rather silly looping functions that
|
||||||
make sure that all the proper combinations of complete_add or
|
make sure that all the proper combinations of complete_add or
|
||||||
|
@ -270,13 +264,6 @@ static void builtin_complete_remove(const wcstring_list_t &cmd,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const wchar_t *builtin_complete_get_temporary_buffer()
|
|
||||||
{
|
|
||||||
ASSERT_IS_MAIN_THREAD();
|
|
||||||
return temporary_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The complete builtin. Used for specifying programmable
|
The complete builtin. Used for specifying programmable
|
||||||
tab-completions. Calls the functions in complete.c for any heavy
|
tab-completions. Calls the functions in complete.c for any heavy
|
||||||
|
@ -300,6 +287,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
|
|
||||||
wcstring_list_t cmd;
|
wcstring_list_t cmd;
|
||||||
wcstring_list_t path;
|
wcstring_list_t path;
|
||||||
|
wcstring_list_t wrap_targets;
|
||||||
|
|
||||||
static int recursion_level=0;
|
static int recursion_level=0;
|
||||||
|
|
||||||
|
@ -326,6 +314,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
{ L"unauthoritative", no_argument, 0, 'u' },
|
{ L"unauthoritative", no_argument, 0, 'u' },
|
||||||
{ L"authoritative", no_argument, 0, 'A' },
|
{ L"authoritative", no_argument, 0, 'A' },
|
||||||
{ L"condition", required_argument, 0, 'n' },
|
{ L"condition", required_argument, 0, 'n' },
|
||||||
|
{ L"wraps", required_argument, 0, 'w' },
|
||||||
{ L"do-complete", optional_argument, 0, 'C' },
|
{ L"do-complete", optional_argument, 0, 'C' },
|
||||||
{ L"help", no_argument, 0, 'h' },
|
{ L"help", no_argument, 0, 'h' },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
|
@ -422,6 +411,10 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
case 'n':
|
case 'n':
|
||||||
condition = woptarg;
|
condition = woptarg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
wrap_targets.push_back(woptarg);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'C':
|
case 'C':
|
||||||
do_complete = true;
|
do_complete = true;
|
||||||
|
@ -495,9 +488,9 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
const wchar_t *token;
|
const wchar_t *token;
|
||||||
|
|
||||||
parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0);
|
parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0);
|
||||||
|
|
||||||
const wchar_t *prev_temporary_buffer = temporary_buffer;
|
/* Create a scoped transient command line, so that bulitin_commandline will see our argument, not the reader buffer */
|
||||||
temporary_buffer = do_complete_param.c_str();
|
builtin_commandline_scoped_transient_t temp_buffer(do_complete_param);
|
||||||
|
|
||||||
if (recursion_level < 1)
|
if (recursion_level < 1)
|
||||||
{
|
{
|
||||||
|
@ -536,9 +529,6 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
|
|
||||||
recursion_level--;
|
recursion_level--;
|
||||||
}
|
}
|
||||||
|
|
||||||
temporary_buffer = prev_temporary_buffer;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (woptind != argc)
|
else if (woptind != argc)
|
||||||
{
|
{
|
||||||
|
@ -558,7 +548,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int flags = COMPLETE_AUTO_SPACE;
|
int flags = COMPLETE_AUTO_SPACE;
|
||||||
|
|
||||||
if (remove)
|
if (remove)
|
||||||
{
|
{
|
||||||
builtin_complete_remove(cmd,
|
builtin_complete_remove(cmd,
|
||||||
|
@ -566,6 +556,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
short_opt.c_str(),
|
short_opt.c_str(),
|
||||||
gnu_opt,
|
gnu_opt,
|
||||||
old_opt);
|
old_opt);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -581,7 +572,18 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
|
||||||
desc,
|
desc,
|
||||||
flags);
|
flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle wrap targets (probably empty)
|
||||||
|
// We only wrap commands, not paths
|
||||||
|
for (size_t w=0; w < wrap_targets.size(); w++)
|
||||||
|
{
|
||||||
|
const wcstring &wrap_target = wrap_targets.at(w);
|
||||||
|
for (size_t i=0; i < cmd.size(); i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
(remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i), wrap_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
158
complete.cpp
158
complete.cpp
|
@ -1516,7 +1516,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
|
||||||
|
|
||||||
if ((o->short_opt == L'\0') && (o->long_opt[0]==L'\0'))
|
if ((o->short_opt == L'\0') && (o->long_opt[0]==L'\0'))
|
||||||
{
|
{
|
||||||
use_files &= ((o->result_mode & NO_FILES)==0);
|
use_files = use_files && ((o->result_mode & NO_FILES)==0);
|
||||||
complete_from_args(str, o->comp, o->localized_desc(), o->flags);
|
complete_from_args(str, o->comp, o->localized_desc(), o->flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1997,10 +1997,34 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
|
||||||
unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) &&
|
unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) &&
|
||||||
unescape_string(current_argument, ¤t_argument_unescape, UNESCAPE_INCOMPLETE))
|
unescape_string(current_argument, ¤t_argument_unescape, UNESCAPE_INCOMPLETE))
|
||||||
{
|
{
|
||||||
do_file = completer.complete_param(current_command_unescape,
|
// Have to walk over the command and its entire wrap chain
|
||||||
|
// If any command disables do_file, then they all do
|
||||||
|
do_file = true;
|
||||||
|
const wcstring_list_t wrap_chain = complete_get_wrap_chain(current_command_unescape);
|
||||||
|
for (size_t i=0; i < wrap_chain.size(); i++)
|
||||||
|
{
|
||||||
|
// Hackish, this. The first command in the chain is always the given command. For every command past the first, we need to create a transient commandline for builtin_commandline. But not for COMPLETION_REQUEST_AUTOSUGGESTION, which may occur on background threads.
|
||||||
|
builtin_commandline_scoped_transient_t *transient_cmd = NULL;
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
assert(wrap_chain.at(i) == current_command_unescape);
|
||||||
|
}
|
||||||
|
else if (! (flags & COMPLETION_REQUEST_AUTOSUGGESTION))
|
||||||
|
{
|
||||||
|
assert(cmd_node != NULL);
|
||||||
|
wcstring faux_cmdline = cmd;
|
||||||
|
faux_cmdline.replace(cmd_node->source_start, cmd_node->source_length, wrap_chain.at(i));
|
||||||
|
transient_cmd = new builtin_commandline_scoped_transient_t(faux_cmdline);
|
||||||
|
}
|
||||||
|
if (! completer.complete_param(wrap_chain.at(i),
|
||||||
previous_argument_unescape,
|
previous_argument_unescape,
|
||||||
current_argument_unescape,
|
current_argument_unescape,
|
||||||
!had_ddash);
|
!had_ddash))
|
||||||
|
{
|
||||||
|
do_file = false;
|
||||||
|
}
|
||||||
|
delete transient_cmd; //may be null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we have found no command specific completions at all, fall back to using file completions. */
|
/* If we have found no command specific completions at all, fall back to using file completions. */
|
||||||
|
@ -2102,4 +2126,132 @@ void complete_print(wcstring &out)
|
||||||
out.append(L"\n");
|
out.append(L"\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Append wraps. This is a wonky interface where even values are the commands, and odd values are the targets that they wrap. */
|
||||||
|
const wcstring_list_t wrap_pairs = complete_get_wrap_pairs();
|
||||||
|
assert(wrap_pairs.size() % 2 == 0);
|
||||||
|
for (size_t i=0; i < wrap_pairs.size(); )
|
||||||
|
{
|
||||||
|
const wcstring &cmd = wrap_pairs.at(i++);
|
||||||
|
const wcstring &target = wrap_pairs.at(i++);
|
||||||
|
append_format(out, L"complete --command %ls --wraps %ls\n", cmd.c_str(), target.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Completion "wrapper" support. The map goes from wrapping-command to wrapped-command-list */
|
||||||
|
static pthread_mutex_t wrapper_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
typedef std::map<wcstring, wcstring_list_t> wrapper_map_t;
|
||||||
|
static wrapper_map_t &wrap_map()
|
||||||
|
{
|
||||||
|
ASSERT_IS_LOCKED(wrapper_lock);
|
||||||
|
// A pointer is a little more efficient than an object as a static because we can elide the thread-safe initialization
|
||||||
|
static wrapper_map_t *wrapper_map = NULL;
|
||||||
|
if (wrapper_map == NULL)
|
||||||
|
{
|
||||||
|
wrapper_map = new wrapper_map_t();
|
||||||
|
}
|
||||||
|
return *wrapper_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a new target that is wrapped by command. Example: sgrep (command) wraps grep (target). */
|
||||||
|
bool complete_add_wrapper(const wcstring &command, const wcstring &new_target)
|
||||||
|
{
|
||||||
|
if (command.empty() || new_target.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_lock locker(wrapper_lock);
|
||||||
|
wrapper_map_t &wraps = wrap_map();
|
||||||
|
wcstring_list_t *targets = &wraps[command];
|
||||||
|
// If it's already present, we do nothing
|
||||||
|
if (std::find(targets->begin(), targets->end(), new_target) == targets->end())
|
||||||
|
{
|
||||||
|
targets->push_back(new_target);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool complete_remove_wrapper(const wcstring &command, const wcstring &target_to_remove)
|
||||||
|
{
|
||||||
|
if (command.empty() || target_to_remove.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_lock locker(wrapper_lock);
|
||||||
|
wrapper_map_t &wraps = wrap_map();
|
||||||
|
bool result = false;
|
||||||
|
wrapper_map_t::iterator current_targets_iter = wraps.find(command);
|
||||||
|
if (current_targets_iter != wraps.end())
|
||||||
|
{
|
||||||
|
wcstring_list_t *targets = ¤t_targets_iter->second;
|
||||||
|
wcstring_list_t::iterator where = std::find(targets->begin(), targets->end(), target_to_remove);
|
||||||
|
if (where != targets->end())
|
||||||
|
{
|
||||||
|
targets->erase(where);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcstring_list_t complete_get_wrap_chain(const wcstring &command)
|
||||||
|
{
|
||||||
|
if (command.empty())
|
||||||
|
{
|
||||||
|
return wcstring_list_t();
|
||||||
|
}
|
||||||
|
scoped_lock locker(wrapper_lock);
|
||||||
|
const wrapper_map_t &wraps = wrap_map();
|
||||||
|
|
||||||
|
wcstring_list_t result;
|
||||||
|
std::set<wcstring> visited; // set of visited commands
|
||||||
|
wcstring_list_t to_visit(1, command); // stack of remaining-to-visit commands
|
||||||
|
|
||||||
|
wcstring target;
|
||||||
|
while (! to_visit.empty())
|
||||||
|
{
|
||||||
|
// Grab the next command to visit, put it in target
|
||||||
|
target.swap(to_visit.back());
|
||||||
|
to_visit.pop_back();
|
||||||
|
|
||||||
|
// Try inserting into visited. If it was already present, we skip it; this is how we avoid loops.
|
||||||
|
if (! visited.insert(target).second)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the target in the result. Note this is the command itself, if this is the first iteration of the loop.
|
||||||
|
result.push_back(target);
|
||||||
|
|
||||||
|
// Enqueue its children
|
||||||
|
wrapper_map_t::const_iterator target_children_iter = wraps.find(target);
|
||||||
|
if (target_children_iter != wraps.end())
|
||||||
|
{
|
||||||
|
const wcstring_list_t &children = target_children_iter->second;
|
||||||
|
to_visit.insert(to_visit.end(), children.begin(), children.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcstring_list_t complete_get_wrap_pairs()
|
||||||
|
{
|
||||||
|
wcstring_list_t result;
|
||||||
|
scoped_lock locker(wrapper_lock);
|
||||||
|
const wrapper_map_t &wraps = wrap_map();
|
||||||
|
for (wrapper_map_t::const_iterator outer = wraps.begin(); outer != wraps.end(); ++outer)
|
||||||
|
{
|
||||||
|
const wcstring &cmd = outer->first;
|
||||||
|
const wcstring_list_t &targets = outer->second;
|
||||||
|
for (size_t i=0; i < targets.size(); i++)
|
||||||
|
{
|
||||||
|
result.push_back(cmd);
|
||||||
|
result.push_back(targets.at(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,4 +267,12 @@ void append_completion(std::vector<completion_t> &completions, const wcstring &c
|
||||||
/* Function used for testing */
|
/* Function used for testing */
|
||||||
void complete_set_variable_names(const wcstring_list_t *names);
|
void complete_set_variable_names(const wcstring_list_t *names);
|
||||||
|
|
||||||
|
/* Support for "wrap targets." A wrap target is a command that completes liek another command. The target chain is the sequence of wraps (A wraps B wraps C...). Any loops in the chain are silently ignored. */
|
||||||
|
bool complete_add_wrapper(const wcstring &command, const wcstring &wrap_target);
|
||||||
|
bool complete_remove_wrapper(const wcstring &command, const wcstring &wrap_target);
|
||||||
|
wcstring_list_t complete_get_wrap_chain(const wcstring &command);
|
||||||
|
|
||||||
|
/* Wonky interface: returns all wraps. Even-values are the commands, odd values are the targets. */
|
||||||
|
wcstring_list_t complete_get_wrap_pairs();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
\section complete complete - edit command specific tab-completions
|
\section complete complete - edit command specific tab-completions
|
||||||
|
|
||||||
\subsection complete-synopsis Synopsis
|
\subsection complete-synopsis Synopsis
|
||||||
<tt>complete (-c|--command|-p|--path) COMMAND [(-s|--short-option) SHORT_OPTION] [(-l|--long-option|-o|--old-option) LONG_OPTION [(-a||--arguments) OPTION_ARGUMENTS] [(-d|--description) DESCRIPTION] </tt>
|
<tt>complete (-c|--command|-p|--path) COMMAND [(-s|--short-option) SHORT_OPTION] [(-l|--long-option|-o|--old-option) LONG_OPTION [(-a||--arguments) OPTION_ARGUMENTS] [(-w|--wraps) WRAPPED_COMMAND] [(-d|--description) DESCRIPTION] </tt>
|
||||||
|
|
||||||
\subsection complete-description Description
|
\subsection complete-description Description
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ the fish manual.
|
||||||
- <tt>-u</tt> or <tt>--unauthoritative</tt> implies that there may be more options than the ones specified, and that fish should not assume that options not listed are spelling errors
|
- <tt>-u</tt> or <tt>--unauthoritative</tt> implies that there may be more options than the ones specified, and that fish should not assume that options not listed are spelling errors
|
||||||
- <tt>-A</tt> or <tt>--authoritative</tt> implies that there may be no more options than the ones specified, and that fish should assume that options not listed are spelling errors
|
- <tt>-A</tt> or <tt>--authoritative</tt> implies that there may be no more options than the ones specified, and that fish should assume that options not listed are spelling errors
|
||||||
- <tt>-x</tt> or <tt>--exclusive</tt> implies both <tt>-r</tt> and <tt>-f</tt>
|
- <tt>-x</tt> or <tt>--exclusive</tt> implies both <tt>-r</tt> and <tt>-f</tt>
|
||||||
|
- <tt>-w WRAPPED_COMMAND</tt> or <tt>--wraps=WRAPPED_COMMAND</tt> causes the specified command to inherit completions from the wrapped comamnd.
|
||||||
|
|
||||||
Command specific tab-completions in \c fish are based on the notion
|
Command specific tab-completions in \c fish are based on the notion
|
||||||
of options and arguments. An option is a parameter which begins with a
|
of options and arguments. An option is a parameter which begins with a
|
||||||
|
@ -41,6 +42,14 @@ switches may all be used multiple times to specify multiple commands
|
||||||
which have the same completion or multiple switches accepted by a
|
which have the same completion or multiple switches accepted by a
|
||||||
command.
|
command.
|
||||||
|
|
||||||
|
The \c -w or \c --wraps options causes the specified command to inherit
|
||||||
|
completions from another command. The inheriting command is said to
|
||||||
|
"wrap" the inherited command. The wrapping command may have its own
|
||||||
|
completions in addition to inherited ones. A command may wrap multiple
|
||||||
|
commands, and wrapping is transitive: if A wraps B, and B wraps C,
|
||||||
|
then A automatically inherits all of C's completions. Wrapping can
|
||||||
|
be removed using the \c -e or \c --erase options.
|
||||||
|
|
||||||
When erasing completions, it is possible to either erase all
|
When erasing completions, it is possible to either erase all
|
||||||
completions for a specific command by specifying <tt>complete -e -c
|
completions for a specific command by specifying <tt>complete -e -c
|
||||||
COMMAND</tt>, or by specifying a specific completion option to delete
|
COMMAND</tt>, or by specifying a specific completion option to delete
|
||||||
|
@ -75,3 +84,10 @@ This can be written as:
|
||||||
where \c __fish_contains_opt is a function that checks the commandline
|
where \c __fish_contains_opt is a function that checks the commandline
|
||||||
buffer for the presence of a specified set of options.
|
buffer for the presence of a specified set of options.
|
||||||
|
|
||||||
|
To implement an alias, use the \c -w or \c --wraps option:
|
||||||
|
|
||||||
|
<tt>complete -c hub -w git</tt>
|
||||||
|
|
||||||
|
Now hub inherits all of the completions from git. Note this can
|
||||||
|
also be specified in a function declaration.
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ The following options are available:
|
||||||
|
|
||||||
- <code>-a NAMES</code> or <code>--argument-names NAMES</code> assigns the value of successive command-line arguments to the names given in NAMES.
|
- <code>-a NAMES</code> or <code>--argument-names NAMES</code> assigns the value of successive command-line arguments to the names given in NAMES.
|
||||||
- <code>-d DESCRIPTION</code> or \c --description=DESCRIPTION is a description of what the function does, suitable as a completion description.
|
- <code>-d DESCRIPTION</code> or \c --description=DESCRIPTION is a description of what the function does, suitable as a completion description.
|
||||||
|
- <code>-w WRAPPED_COMMAND</code> or \c --wraps=WRAPPED_COMMAND causes the function to inherit completions from the given wrapped command. See the documentation for \c complete for more information.
|
||||||
- <code>-e</code> or <code>--on-event EVENT_NAME</code> tells fish to run this function when the specified named event is emitted. Fish internally generates named events e.g. when showing the prompt.
|
- <code>-e</code> or <code>--on-event EVENT_NAME</code> tells fish to run this function when the specified named event is emitted. Fish internally generates named events e.g. when showing the prompt.
|
||||||
- <code>-j PID</code> or <code> --on-job-exit PID</code> tells fish to run this function when the job with group ID PID exits. Instead of PID, the string 'caller' can be specified. This is only legal when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
|
- <code>-j PID</code> or <code> --on-job-exit PID</code> tells fish to run this function when the job with group ID PID exits. Instead of PID, the string 'caller' can be specified. This is only legal when in a command substitution, and will result in the handler being triggered by the exit of the job which created this command substitution.
|
||||||
- <code>-p PID</code> or <code> --on-process-exit PID</code> tells fish to run this function when the fish child process with process ID PID exits.
|
- <code>-p PID</code> or <code> --on-process-exit PID</code> tells fish to run this function when the fish child process with process ID PID exits.
|
||||||
|
|
|
@ -166,6 +166,21 @@ static void err(const wchar_t *blah, ...)
|
||||||
wprintf(L"\n");
|
wprintf(L"\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Joins a wcstring_list_t via commas
|
||||||
|
static wcstring comma_join(const wcstring_list_t &lst)
|
||||||
|
{
|
||||||
|
wcstring result;
|
||||||
|
for (size_t i=0; i < lst.size(); i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
result.push_back(L',');
|
||||||
|
}
|
||||||
|
result.append(lst.at(i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#define do_test(e) do { if (! (e)) err(L"Test failed on line %lu: %s", __LINE__, #e); } while (0)
|
#define do_test(e) do { if (! (e)) err(L"Test failed on line %lu: %s", __LINE__, #e); } while (0)
|
||||||
|
|
||||||
/* Test sane escapes */
|
/* Test sane escapes */
|
||||||
|
@ -1924,6 +1939,18 @@ static void test_complete(void)
|
||||||
do_test(completions.empty());
|
do_test(completions.empty());
|
||||||
|
|
||||||
complete_set_variable_names(NULL);
|
complete_set_variable_names(NULL);
|
||||||
|
|
||||||
|
/* Test wraps */
|
||||||
|
do_test(comma_join(complete_get_wrap_chain(L"wrapper1")) == L"wrapper1");
|
||||||
|
complete_add_wrapper(L"wrapper1", L"wrapper2");
|
||||||
|
do_test(comma_join(complete_get_wrap_chain(L"wrapper1")) == L"wrapper1,wrapper2");
|
||||||
|
complete_add_wrapper(L"wrapper2", L"wrapper3");
|
||||||
|
do_test(comma_join(complete_get_wrap_chain(L"wrapper1")) == L"wrapper1,wrapper2,wrapper3");
|
||||||
|
complete_add_wrapper(L"wrapper3", L"wrapper1"); //loop!
|
||||||
|
do_test(comma_join(complete_get_wrap_chain(L"wrapper1")) == L"wrapper1,wrapper2,wrapper3");
|
||||||
|
complete_remove_wrapper(L"wrapper1", L"wrapper2");
|
||||||
|
do_test(comma_join(complete_get_wrap_chain(L"wrapper1")) == L"wrapper1");
|
||||||
|
do_test(comma_join(complete_get_wrap_chain(L"wrapper2")) == L"wrapper2,wrapper3,wrapper1");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_1_completion(wcstring line, const wcstring &completion, complete_flags_t flags, bool append_only, wcstring expected, long source_line)
|
static void test_1_completion(wcstring line, const wcstring &completion, complete_flags_t flags, bool append_only, wcstring expected, long source_line)
|
||||||
|
|
|
@ -48,5 +48,5 @@ function alias --description "Legacy function for creating shellscript functions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
eval "function $name; $prefix $body \$argv; end"
|
eval "function $name --wraps $body; $prefix $body \$argv; end"
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue