diff --git a/.gitignore b/.gitignore index 63e163f6a..efa933d97 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *~ *.exe +.DS_Store Makefile autom4te.cache/ build/ @@ -39,15 +40,10 @@ messages.pot lexicon.txt lexicon_filter -lexicon-debug.log +lexicon.log Fish-Shell.sublime-workspace Fish-Shell.sublime-project .editorconfig doc_src/.editorconfig - - -lexicon.log - -.DS_Store diff --git a/builtin.cpp b/builtin.cpp index cb06481f0..0213f5cbd 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 fb9a904d2..3472995cf 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. + +/** 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. */ -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(); +}; /** diff --git a/builtin_commandline.cpp b/builtin_commandline.cpp index 88084c3ba..0af6f6565 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 saved_current_buffer(¤t_buffer); + scoped_push 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 3b7a13a2e..f0eb6e1b1 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/common.cpp b/common.cpp index 001eab0fc..b8a2661d2 100644 --- a/common.cpp +++ b/common.cpp @@ -1019,7 +1019,22 @@ static void escape_string_internal(const wchar_t *orig_in, size_t in_len, wcstri out += *in; break; } + + + // Experimental fix for #1614 + // The hope is that any time these appear in a string, they came from wildcard expansion + case ANY_CHAR: + out += L'?'; + break; + case ANY_STRING: + out += L'*'; + break; + + case ANY_STRING_RECURSIVE: + out += L"**"; + break; + case L'&': case L'$': case L' ': diff --git a/complete.cpp b/complete.cpp index cf915e7d4..d7ecdf73e 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 &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. */ @@ -2072,7 +2096,7 @@ void complete_print(wcstring &out) append_switch(out, e->cmd_is_path ? L"path" : L"command", - e->cmd); + escape_string(e->cmd, ESCAPE_ALL)); if (o->short_opt != 0) @@ -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 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 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 10f351fed..45eb2dc02 100644 --- a/complete.h +++ b/complete.h @@ -267,4 +267,12 @@ void append_completion(std::vector &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 9c0eff74e..501eac86d 100644 --- a/doc_src/complete.txt +++ b/doc_src/complete.txt @@ -2,11 +2,12 @@ \subsection complete-synopsis Synopsis \fish{synopsis} -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] +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] \endfish @@ -16,21 +17,23 @@ For an introduction to specifying completions, see Writing your own completions in the fish manual. -- `COMMAND` is the name of the command for which to add a completion -- `SHORT_OPTION` is a one character option for the command -- `LONG_OPTION` is a multi character option for the command -- `OPTION_ARGUMENTS` is parameter containing a space-separated list of possible option-arguments, which may contain subshells -- `DESCRIPTION` is a description of what the option and/or option arguments do -- `-C STRING` or `--do-complete=STRING` makes complete try to find all possible completions for the specified string -- `-e` or `--erase` implies that the specified completion should be deleted -- `-f` or `--no-files` specifies that the option specified by this completion may not be followed by a filename +<<<<<<< HEAD +- `COMMAND` is the name of the command for which to add a completion. +- `SHORT_OPTION` is a one character option for the command. +- `LONG_OPTION` is a multi character option for the command. +- `OPTION_ARGUMENTS` is parameter containing a space-separated list of possible option-arguments, which may contain subshells. +- `DESCRIPTION` is a description of what the option and/or option arguments do. +- `-C STRING` or `--do-complete=STRING` makes complete try to find all possible completions for the specified string. +- `-w WRAPPED_COMMAND` or `--wraps=WRAPPED_COMMAND` causes the specified command to inherit completions from the wrapped command. +- `-e` or `--erase` implies that the specified completion should be deleted. +- `-f` or `--no-files` specifies that the option specified by this completion may not be followed by a filename. - `-n` or `--condition` specifies a shell command that must return 0 if the completion is to be used. This makes it possible to specify completions that should only be used in some cases. -- `-o` or `--old-option` implies that the command uses old long style options with only one dash -- `-p` or `--path` implies that the string `COMMAND` is the full path of the command -- `-r` or `--require-parameter` specifies that the option specified by this completion always must have an option argument, i.e. may not be followed by another option -- `-u` or `--unauthoritative` implies that there may be more options than the ones specified, and that fish should not assume that options not listed are spelling errors -- `-A` or `--authoritative` implies that there may be no more options than the ones specified, and that fish should assume that options not listed are spelling errors -- `-x` or `--exclusive` implies both `-r` and `-f` +- `-o` or `--old-option` implies that the command uses old long style options with only one dash. +- `-p` or `--path` implies that the string `COMMAND` is the full path of the command. +- `-r` or `--require-parameter` specifies that the option specified by this completion always must have an option argument, i.e. may not be followed by another option. +- `-u` or `--unauthoritative` implies that there may be more options than the ones specified, and that fish should not assume that options not listed are spelling errors. +- `-A` or `--authoritative` implies that there may be no more options than the ones specified, and that fish should assume that options not listed are spelling errors. +- `-x` or `--exclusive` implies both `-r` and `-f`. Command specific tab-completions in `fish` are based on the notion of options and arguments. An option is a parameter which begins with a @@ -48,6 +51,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 `complete -e -c COMMAND`, or by specifying a specific completion option to delete @@ -84,3 +95,10 @@ This can be written as: where `__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 `-w` or `--wraps` option: + +`complete -c hub -w git` + +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 9980c45ba..f2f6ecd4e 100644 --- a/doc_src/function.txt +++ b/doc_src/function.txt @@ -16,6 +16,7 @@ The following options are available: - `-a NAMES` or `--argument-names NAMES` assigns the value of successive command-line arguments to the names given in NAMES. - `-d DESCRIPTION` or `--description=DESCRIPTION` is a description of what the function does, suitable as a completion description. +- `-w WRAPPED_COMMAND` or `--wraps=WRAPPED_COMMAND` causes the function to inherit completions from the given wrapped command. See the documentation for `complete` for more information. - `-e` or `--on-event EVENT_NAME` tells fish to run this function when the specified named event is emitted. Fish internally generates named events e.g. when showing the prompt. - `-j PID` or `--on-job-exit PID` 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. - `-p PID` or `--on-process-exit PID` tells fish to run this function when the fish child process with process ID PID exits. @@ -58,6 +59,5 @@ function mkdir -d "Create a directory and set CWD" end \endfish -will run the mkdir command, and if it is successful, change the -current working directory to the one just created. +This will run the `mkdir` command, and if it is successful, change the current working directory to the one just created. diff --git a/fish_tests.cpp b/fish_tests.cpp index 960931542..bdf12af10 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -143,20 +143,44 @@ static void err(const wchar_t *blah, ...) va_list va; va_start(va, blah); err_count++; + + // Xcode's term doesn't support color (even though TERM claims it does) + bool colorize = ! getenv("RUNNING_IN_XCODE"); // show errors in red - fputs("\x1b[31m", stdout); + if (colorize) + { + fputs("\x1b[31m", stdout); + } wprintf(L"Error: "); vwprintf(blah, va); va_end(va); // return to normal color - fputs("\x1b[0m", stdout); + if (colorize) + { + fputs("\x1b[0m", stdout); + } 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 */ @@ -1915,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/pager.cpp b/pager.cpp index b05732404..57f93184c 100644 --- a/pager.cpp +++ b/pager.cpp @@ -464,6 +464,13 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co { rendering->remaining_to_disclose = 0; } + + /* If we have only one row remaining to disclose, then squelch the comment row. This prevents us from consuming a line to show "...and 1 more row" */ + if (! this->fully_disclosed && rendering->remaining_to_disclose == 1) + { + term_height += 1; + rendering->remaining_to_disclose = 0; + } int pref_tot_width=0; int min_tot_width = 0; diff --git a/reader.cpp b/reader.cpp index 057ab7067..bb3b57fc6 100644 --- a/reader.cpp +++ b/reader.cpp @@ -403,6 +403,9 @@ public: /* Sets the command line contents, without clearing the pager */ static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos); +/* Clears the pager */ +static void clear_pager(); + /** The current interactive reading context */ @@ -1564,6 +1567,9 @@ static void accept_autosuggestion(bool full) { if (! data->autosuggestion.empty()) { + /* Accepting an autosuggestion clears the pager */ + clear_pager(); + /* Accept the autosuggestion */ if (full) { diff --git a/share/completions/ssh.fish b/share/completions/ssh.fish index aada35e01..adf0a22d7 100644 --- a/share/completions/ssh.fish +++ b/share/completions/ssh.fish @@ -15,7 +15,7 @@ complete -x -c ssh -d Hostname -a " " complete -x -c ssh -d User -a " -(__fish_print_users)@ +(__fish_print_users | sgrep -v '^_')@ " complete -c ssh --description "Command to run" -x -a '(__fish_complete_subcommand --fcs-skip=2)' diff --git a/share/functions/__fish_print_users.fish b/share/functions/__fish_print_users.fish index 0a67ab55f..90e690eb1 100644 --- a/share/functions/__fish_print_users.fish +++ b/share/functions/__fish_print_users.fish @@ -2,6 +2,8 @@ function __fish_print_users --description "Print a list of local users" if test -x /usr/bin/getent getent passwd | cut -d : -f 1 + else if test -x /usr/bin/dscl # OS X support + dscl . -list /Users else sgrep -ve '^#' /etc/passwd | cut -d : -f 1 end diff --git a/share/functions/alias.fish b/share/functions/alias.fish index 236654ecd..56beed128 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