mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-12 06:41:14 -03: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:
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'))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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(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,
|
||||
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. */
|
||||
@@ -2102,4 +2126,132 @@ void complete_print(wcstring &out)
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user