diff options
-rw-r--r-- | builtin.cpp | 13 | ||||
-rw-r--r-- | builtin.h | 25 | ||||
-rw-r--r-- | builtin_commandline.cpp | 57 | ||||
-rw-r--r-- | builtin_complete.cpp | 44 | ||||
-rw-r--r-- | complete.cpp | 158 | ||||
-rw-r--r-- | complete.h | 8 | ||||
-rw-r--r-- | doc_src/complete.txt | 18 | ||||
-rw-r--r-- | doc_src/function.txt | 1 | ||||
-rw-r--r-- | fish_tests.cpp | 27 | ||||
-rw-r--r-- | share/functions/alias.fish | 2 |
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; @@ -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(¤t_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 { 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, ¤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; } @@ -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 |