aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--builtin.cpp13
-rw-r--r--builtin.h25
-rw-r--r--builtin_commandline.cpp57
-rw-r--r--builtin_complete.cpp44
-rw-r--r--complete.cpp158
-rw-r--r--complete.h8
-rw-r--r--doc_src/complete.txt18
-rw-r--r--doc_src/function.txt1
-rw-r--r--fish_tests.cpp27
-rw-r--r--share/functions/alias.fish2
10 files changed, 317 insertions, 36 deletions
diff --git a/builtin.cpp b/builtin.cpp
index cb06481f..0213f5cb 100644
--- a/builtin.cpp
+++ b/builtin.cpp
@@ -1809,6 +1809,8 @@ int define_function(parser_t &parser, const wcstring_list_t &c_args, const wcstr
bool shadows = true;
woptind=0;
+
+ wcstring_list_t wrap_targets;
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-variable", required_argument, 0, 'v' },
{ L"on-event", required_argument, 0, 'e' },
+ { L"wraps", required_argument, 0, 'w' },
{ L"help", no_argument, 0, 'h' },
{ L"argument-names", no_argument, 0, 'a' },
{ 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':
shadows = 0;
break;
+
+ case 'w':
+ wrap_targets.push_back(woptarg);
+ break;
case 'h':
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();
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;
diff --git a/builtin.h b/builtin.h
index fb9a904d..3472995c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -164,12 +164,25 @@ void builtin_pop_io(parser_t &parser);
wcstring builtin_get_desc(const wcstring &b);
-/**
- Slightly kludgy function used with 'complete -C' in order to make
- the commandline builtin operate on the string to complete instead
- of operating on whatever is to be completed.
-*/
-const wchar_t *builtin_complete_get_temporary_buffer();
+
+/** Support for setting and removing transient command lines.
+ This is used by 'complete -C' in order to make
+ 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.
+*/
+class builtin_commandline_scoped_transient_t
+{
+ size_t token;
+ public:
+ builtin_commandline_scoped_transient_t(const wcstring &cmd);
+ ~builtin_commandline_scoped_transient_t();
+};
/**
diff --git a/builtin_commandline.cpp b/builtin_commandline.cpp
index 88084c3b..0af6f656 100644
--- a/builtin_commandline.cpp
+++ b/builtin_commandline.cpp
@@ -79,6 +79,51 @@ static size_t get_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.
@@ -216,11 +261,15 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
int search_mode = 0;
int paging_mode = 0;
const wchar_t *begin = NULL, *end = NULL;
-
- current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer();
- if (current_buffer)
+
+ scoped_push<const wchar_t *> saved_current_buffer(&current_buffer);
+ scoped_push<size_t> saved_current_cursor_pos(&current_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
{
diff --git a/builtin_complete.cpp b/builtin_complete.cpp
index 3b7a13a2..f0eb6e1b 100644
--- a/builtin_complete.cpp
+++ b/builtin_complete.cpp
@@ -24,12 +24,6 @@ Functions used for implementing the complete builtin.
#include "parser.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
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
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 path;
+ wcstring_list_t wrap_targets;
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"authoritative", no_argument, 0, 'A' },
{ L"condition", required_argument, 0, 'n' },
+ { L"wraps", required_argument, 0, 'w' },
{ L"do-complete", optional_argument, 0, 'C' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
@@ -422,6 +411,10 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
case 'n':
condition = woptarg;
break;
+
+ case 'w':
+ wrap_targets.push_back(woptarg);
+ break;
case 'C':
do_complete = true;
@@ -495,9 +488,9 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
const wchar_t *token;
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;
- temporary_buffer = do_complete_param.c_str();
+
+ /* Create a scoped transient command line, so that bulitin_commandline will see our argument, not the reader buffer */
+ builtin_commandline_scoped_transient_t temp_buffer(do_complete_param);
if (recursion_level < 1)
{
@@ -536,9 +529,6 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
recursion_level--;
}
-
- temporary_buffer = prev_temporary_buffer;
-
}
else if (woptind != argc)
{
@@ -558,7 +548,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
else
{
int flags = COMPLETE_AUTO_SPACE;
-
+
if (remove)
{
builtin_complete_remove(cmd,
@@ -566,6 +556,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
short_opt.c_str(),
gnu_opt,
old_opt);
+
}
else
{
@@ -581,7 +572,18 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
desc,
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);
+ }
+ }
}
}
diff --git a/complete.cpp b/complete.cpp
index cf915e7d..c6f185be 100644
--- a/complete.cpp
+++ b/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, &current_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 = &current_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;
}
diff --git a/complete.h b/complete.h
index 10f351fe..45eb2dc0 100644
--- a/complete.h
+++ b/complete.h
@@ -267,4 +267,12 @@ void append_completion(std::vector<completion_t> &completions, const wcstring &c
/* Function used for testing */
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
diff --git a/doc_src/complete.txt b/doc_src/complete.txt
index c2085ccb..0c1ffa91 100644
--- a/doc_src/complete.txt
+++ b/doc_src/complete.txt
@@ -1,7 +1,7 @@
\section complete complete - edit command specific tab-completions
\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
@@ -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>-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>-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
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
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
completions for a specific command by specifying <tt>complete -e -c
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
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.
+
diff --git a/doc_src/function.txt b/doc_src/function.txt
index 8d8e4519..cadc0d26 100644
--- a/doc_src/function.txt
+++ b/doc_src/function.txt
@@ -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>-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>-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.
diff --git a/fish_tests.cpp b/fish_tests.cpp
index 7cc5fb9a..bdf12af1 100644
--- a/fish_tests.cpp
+++ b/fish_tests.cpp
@@ -166,6 +166,21 @@ static void err(const wchar_t *blah, ...)
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)
/* Test sane escapes */
@@ -1924,6 +1939,18 @@ static void test_complete(void)
do_test(completions.empty());
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)
diff --git a/share/functions/alias.fish b/share/functions/alias.fish
index 236654ec..56beed12 100644
--- a/share/functions/alias.fish
+++ b/share/functions/alias.fish
@@ -48,5 +48,5 @@ function alias --description "Legacy function for creating shellscript functions
end
end
- eval "function $name; $prefix $body \$argv; end"
+ eval "function $name --wraps $body; $prefix $body \$argv; end"
end