From 9ef47a43a4127c16df468ee24f488358073dfa2c Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Thu, 20 Jul 2017 17:54:06 -0700 Subject: [PATCH 01/18] change how `argparse` handles boolean flags When reporting whether a boolean flag was seen report the actual flags rather than a summary count. For example, if you have option spec `h/help` and we parse `-h --help -h` don't do the equivalent of `set _flag_h 3` do `set _flag_h -h --help -h`. Partial fix for #4226 --- doc_src/argparse.txt | 2 +- src/builtin_argparse.cpp | 8 ++++++++ tests/argparse.out | 16 ++++++++-------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/doc_src/argparse.txt b/doc_src/argparse.txt index ae763382d..fc95946e0 100644 --- a/doc_src/argparse.txt +++ b/doc_src/argparse.txt @@ -13,7 +13,7 @@ Each OPTION_SPEC can be written in the domain specific language <a href="#argpar Each option that is seen in the ARG list will result in a var name of the form `_flag_X`, where `X` is the short flag letter and the long flag name. The OPTION_SPEC always requires a short flag even if it can't be used. So there will always be `_flag_X` var set using the short flag letter if the corresponding short or long flag is seen. The long flag name var (e.g., `_flag_help`) will only be defined, obviously, if the OPTION_SPEC includes a long flag name. -For example `_flag_h` and `_flag_help` if `-h` or `--help` is seen. The var will be set with local scope (i.e., as if the script had done `set -l _flag_X`). If the flag is a boolean (that is, does not have an associated value) the value is a count of how many times the flag was seen. If the option can have zero or more values the flag var will have zero or more values corresponding to the values collected when the ARG list is processed. If the flag was not seen the flag var will not be set. +For example `_flag_h` and `_flag_help` if `-h` or `--help` is seen. The var will be set with local scope (i.e., as if the script had done `set -l _flag_X`). If the flag is a boolean (that is, does not have an associated value) the values are the short and long flags seen. If the option is not a boolean flag the values will be zero or more values corresponding to the values collected when the ARG list is processed. If the flag was not seen the flag var will not be set. The following `argparse` options are available. They must appear before all OPTION_SPECs: diff --git a/src/builtin_argparse.cpp b/src/builtin_argparse.cpp index 4488317a4..6d6518976 100644 --- a/src/builtin_argparse.cpp +++ b/src/builtin_argparse.cpp @@ -437,6 +437,7 @@ static void populate_option_strings( // Add a count for how many times we saw each boolean flag but only if we saw the flag at least // once. static void update_bool_flag_counts(argparse_cmd_opts_t &opts) { + return; for (auto it : opts.options) { auto opt_spec = it.second; // The '#' short flag is special. It doesn't take any values but isn't a boolean arg. @@ -539,6 +540,13 @@ static int argparse_parse_flags(argparse_cmd_opts_t &opts, const wchar_t *short_ option_spec_t *opt_spec = found->second; opt_spec->num_seen++; if (opt_spec->num_allowed == 0) { + // It's a boolean flag. Save the flag we saw since it might be useful to know if the + // short or long flag was given. + if (long_idx == -1) { + opt_spec->vals.push_back(wcstring(1, L'-') + opt_spec->short_flag); + } else { + opt_spec->vals.push_back(L"--" + opt_spec->long_flag); + } assert(!w.woptarg); long_idx = -1; continue; diff --git a/tests/argparse.out b/tests/argparse.out index 71df1f721..eb3752ec8 100644 --- a/tests/argparse.out +++ b/tests/argparse.out @@ -2,8 +2,8 @@ # One arg and no matching flags argv help # Five args with two matching a flag -_flag_h 2 -_flag_help 2 +_flag_h '--help' '-h' +_flag_help '--help' '-h' argv 'help' 'me' 'a lot more' # Required, optional, and multiple flags _flag_a ABC @@ -12,23 +12,23 @@ _flag_d _flag_def _flag_g 'g1' 'g2' 'g3' _flag_ghk 'g1' 'g2' 'g3' -_flag_h 1 -_flag_help 1 +_flag_h --help +_flag_help --help argv 'help' 'me' # --stop-nonopt works _flag_a A2 _flag_abc A2 -_flag_h 1 -_flag_help 1 +_flag_h -h +_flag_help -h argv 'non-opt' 'second non-opt' '--help' # Implicit int flags work _flag_val 123 argv 'abc' 'def' _flag_t woohoo _flag_token woohoo -_flag_v 2 +_flag_v '-v' '--verbose' _flag_val -234 -_flag_verbose 2 +_flag_verbose '-v' '--verbose' argv 'a1' 'a2' # Should be set to 987 _flag_m 987 From f3130ce70b5aba97f6bf86c2174946d1c6ccc8bc Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Fri, 21 Jul 2017 15:55:52 -0700 Subject: [PATCH 02/18] fix `argparse` handling of short flag only specs @faho noticed that option specs which don't have a long flag name are not handled correctly. This fixes that and adds unit tests. Fixes #4232 --- src/builtin_argparse.cpp | 136 ++++++++++++++++++++------------------- tests/argparse.err | 2 + tests/argparse.in | 27 ++++++++ tests/argparse.out | 19 ++++++ 4 files changed, 119 insertions(+), 65 deletions(-) diff --git a/src/builtin_argparse.cpp b/src/builtin_argparse.cpp index 6d6518976..45475219d 100644 --- a/src/builtin_argparse.cpp +++ b/src/builtin_argparse.cpp @@ -181,8 +181,9 @@ static int parse_exclusive_args(argparse_cmd_opts_t &opts, io_streams_t &streams } static bool parse_flag_modifiers(argparse_cmd_opts_t &opts, option_spec_t *opt_spec, - const wcstring &option_spec, const wchar_t *s, + const wcstring &option_spec, const wchar_t **opt_spec_str, io_streams_t &streams) { + const wchar_t *s = *opt_spec_str; if (opt_spec->short_flag == opts.implicit_int_flag && *s && *s != L'!') { streams.err.append_format( _(L"%ls: Implicit int short flag '%lc' does not allow modifiers like '%lc'\n"), @@ -224,6 +225,7 @@ static bool parse_flag_modifiers(argparse_cmd_opts_t &opts, option_spec_t *opt_s } opts.options.emplace(opt_spec->short_flag, opt_spec); + *opt_spec_str = s; return true; } @@ -250,8 +252,18 @@ static bool parse_option_spec_sep(argparse_cmd_opts_t &opts, option_spec_t *opt_ } else if (*s == L'-') { opt_spec->short_flag_valid = false; s++; + if (!*s) { + streams.err.append_format(BUILTIN_ERR_INVALID_OPT_SPEC, opts.name.c_str(), + option_spec.c_str(), *(s - 1)); + return false; + } } else if (*s == L'/') { s++; // the struct is initialized assuming short_flag_valid should be true + if (!*s) { + streams.err.append_format(BUILTIN_ERR_INVALID_OPT_SPEC, opts.name.c_str(), + option_spec.c_str(), *(s - 1)); + return false; + } } else if (*s == L'#') { if (opts.implicit_int_flag) { streams.err.append_format(_(L"%ls: Implicit int flag '%lc' already defined\n"), @@ -261,10 +273,11 @@ static bool parse_option_spec_sep(argparse_cmd_opts_t &opts, option_spec_t *opt_ opts.implicit_int_flag = opt_spec->short_flag; opt_spec->num_allowed = 1; // mandatory arg and can appear only once s++; // the struct is initialized assuming short_flag_valid should be true + if (!*s) opts.options.emplace(opt_spec->short_flag, opt_spec); } else { - // Long flag name not allowed if second char isn't '/' or '-' so just check for + // Long flag name not allowed if second char isn't '/', '-' or '#' so just check for // behavior modifier chars. - return parse_flag_modifiers(opts, opt_spec, option_spec, s, streams); + if (!parse_flag_modifiers(opts, opt_spec, option_spec, &s, streams)) return false; } *opt_spec_str = s; @@ -272,8 +285,8 @@ static bool parse_option_spec_sep(argparse_cmd_opts_t &opts, option_spec_t *opt_ } /// This parses an option spec string into a struct option_spec. -static bool parse_option_spec(argparse_cmd_opts_t &opts, wcstring option_spec, - io_streams_t &streams) { +static bool parse_option_spec(argparse_cmd_opts_t &opts, //!OCLINT(high npath complexity) + wcstring option_spec, io_streams_t &streams) { if (option_spec.empty()) { streams.err.append_format(_(L"%ls: An option spec must have a short flag letter\n"), opts.name.c_str()); @@ -288,25 +301,28 @@ static bool parse_option_spec(argparse_cmd_opts_t &opts, wcstring option_spec, } option_spec_t *opt_spec = new option_spec_t(*s++); + if (!*s) { + // Bool short flag only. + opts.options.emplace(opt_spec->short_flag, opt_spec); + return true; + } if (!parse_option_spec_sep(opts, opt_spec, option_spec, &s, streams)) return false; + if (!*s) return true; // parsed the entire string so the option spec doesn't have a long flag // Collect the long flag name. const wchar_t *e = s; while (*e && (*e == L'-' || *e == L'_' || iswalnum(*e))) e++; - if (e == s) { - streams.err.append_format(BUILTIN_ERR_INVALID_OPT_SPEC, opts.name.c_str(), - option_spec.c_str(), *(s - 1)); - return false; + if (e != s) { + opt_spec->long_flag = wcstring(s, e - s); + if (opts.long_to_short_flag.find(opt_spec->long_flag) != opts.long_to_short_flag.end()) { + streams.err.append_format(L"%ls: Long flag '%ls' already defined\n", opts.name.c_str(), + opt_spec->long_flag.c_str()); + return false; + } + opts.long_to_short_flag.emplace(opt_spec->long_flag, opt_spec->short_flag); } - opt_spec->long_flag = wcstring(s, e - s); - if (opts.long_to_short_flag.find(opt_spec->long_flag) != opts.long_to_short_flag.end()) { - streams.err.append_format(L"%ls: Long flag '%ls' already defined\n", opts.name.c_str(), - opt_spec->long_flag.c_str()); - return false; - } - opts.long_to_short_flag.emplace(opt_spec->long_flag, opt_spec->short_flag); - return parse_flag_modifiers(opts, opt_spec, option_spec, e, streams); + return parse_flag_modifiers(opts, opt_spec, option_spec, &e, streams); } static int collect_option_specs(argparse_cmd_opts_t &opts, int *optind, int argc, wchar_t **argv, @@ -434,21 +450,6 @@ static void populate_option_strings( long_options.get()[i] = {NULL, 0, NULL, 0}; } -// Add a count for how many times we saw each boolean flag but only if we saw the flag at least -// once. -static void update_bool_flag_counts(argparse_cmd_opts_t &opts) { - return; - for (auto it : opts.options) { - auto opt_spec = it.second; - // The '#' short flag is special. It doesn't take any values but isn't a boolean arg. - if (opt_spec->short_flag == L'#') continue; - if (opt_spec->num_allowed != 0 || opt_spec->num_seen == 0) continue; - wchar_t count[20]; - swprintf(count, sizeof count / sizeof count[0], L"%d", opt_spec->num_seen); - opt_spec->vals.push_back(wcstring(count)); - } -} - static int validate_arg(argparse_cmd_opts_t &opts, option_spec_t *opt_spec, bool is_long_flag, const wchar_t *woptarg, io_streams_t &streams) { // Obviously if there is no arg validation command we assume the arg is okay. @@ -511,6 +512,42 @@ static int check_for_implicit_int(argparse_cmd_opts_t &opts, const wchar_t *val, return STATUS_CMD_OK; } +static int handle_flag(argparse_cmd_opts_t &opts, option_spec_t *opt_spec, int long_idx, + const wchar_t *woptarg, io_streams_t &streams) { + opt_spec->num_seen++; + if (opt_spec->num_allowed == 0) { + // It's a boolean flag. Save the flag we saw since it might be useful to know if the + // short or long flag was given. + assert(!woptarg); + if (long_idx == -1) { + opt_spec->vals.push_back(wcstring(1, L'-') + opt_spec->short_flag); + } else { + opt_spec->vals.push_back(L"--" + opt_spec->long_flag); + } + return STATUS_CMD_OK; + } + + if (woptarg) { + int retval = validate_arg(opts, opt_spec, long_idx != -1, woptarg, streams); + if (retval != STATUS_CMD_OK) return retval; + } + + if (opt_spec->num_allowed == -1 || opt_spec->num_allowed == 1) { + // We're depending on `wgetopt_long()` to report that a mandatory value is missing if + // `opt_spec->num_allowed == 1` and thus return ':' so that we don't take this branch if + // the mandatory arg is missing. + opt_spec->vals.clear(); + if (woptarg) { + opt_spec->vals.push_back(woptarg); + } + } else { + assert(woptarg); + opt_spec->vals.push_back(woptarg); + } + + return STATUS_CMD_OK; +} + static int argparse_parse_flags(argparse_cmd_opts_t &opts, const wchar_t *short_options, const woption *long_options, const wchar_t *cmd, int argc, wchar_t **argv, int *optind, parser_t &parser, @@ -538,38 +575,8 @@ static int argparse_parse_flags(argparse_cmd_opts_t &opts, const wchar_t *short_ assert(found != opts.options.end()); option_spec_t *opt_spec = found->second; - opt_spec->num_seen++; - if (opt_spec->num_allowed == 0) { - // It's a boolean flag. Save the flag we saw since it might be useful to know if the - // short or long flag was given. - if (long_idx == -1) { - opt_spec->vals.push_back(wcstring(1, L'-') + opt_spec->short_flag); - } else { - opt_spec->vals.push_back(L"--" + opt_spec->long_flag); - } - assert(!w.woptarg); - long_idx = -1; - continue; - } - - if (w.woptarg) { - int retval = validate_arg(opts, opt_spec, long_idx != -1, w.woptarg, streams); - if (retval != STATUS_CMD_OK) return retval; - } - - if (opt_spec->num_allowed == -1 || opt_spec->num_allowed == 1) { - // We're depending on `wgetopt_long()` to report that a mandatory value is missing if - // `opt_spec->num_allowed == 1` and thus return ':' so that we don't take this branch if - // the mandatory arg is missing. - opt_spec->vals.clear(); - if (w.woptarg) { - opt_spec->vals.push_back(w.woptarg); - } - } else { - assert(w.woptarg); - opt_spec->vals.push_back(w.woptarg); - } - + int retval = handle_flag(opts, opt_spec, long_idx, w.woptarg, streams); + if (retval != STATUS_CMD_OK) return retval; long_idx = -1; } @@ -607,7 +614,6 @@ static int argparse_parse_args(argparse_cmd_opts_t &opts, const wcstring_list_t retval = check_for_mutually_exclusive_flags(opts, streams); if (retval != STATUS_CMD_OK) return retval; - update_bool_flag_counts(opts); for (int i = optind; argv[i]; i++) opts.argv.push_back(argv[i]); diff --git a/tests/argparse.err b/tests/argparse.err index 37bcf54ca..fa8592ef3 100644 --- a/tests/argparse.err +++ b/tests/argparse.err @@ -29,6 +29,8 @@ argparse: Long flag 'short' already defined argparse: Implicit int flag '#' already defined # Defining an implicit int flag with modifiers argparse: Implicit int short flag 'v' does not allow modifiers like '=' +# Implicit int short flag only with custom validation fails +argparse: Value '499' for flag 'x' less than min allowed of '500' # Implicit int flag validation fails argparse: Value '765x' for flag 'max' is not an integer argparse: Value 'a1' for flag 'm' is not an integer diff --git a/tests/argparse.in b/tests/argparse.in index da5c5f1dc..6514d44b9 100644 --- a/tests/argparse.in +++ b/tests/argparse.in @@ -86,15 +86,42 @@ set -l for v in (set -l -n); set -e $v; end argparse 'v/verbose' '#-val' 't/token=' -- -123 a1 --token woohoo --234 -v a2 --verbose set -l + echo '# Should be set to 987' for v in (set -l -n); set -e $v; end argparse 'm#max' -- argle -987 bargle set -l + echo '# Should be set to 765' for v in (set -l -n); set -e $v; end argparse 'm#max' -- argle -987 bargle --max 765 set -l +echo '# Bool short flag only' +for v in (set -l -n); set -e $v; end +argparse 'C' 'v' -- -C -v arg1 -v arg2 +set -l + +echo '# Value taking short flag only' +for v in (set -l -n); set -e $v; end +argparse 'x=' 'v/verbose' -- --verbose arg1 -v -x arg2 +set -l + +echo '# Implicit int short flag only' +for v in (set -l -n); set -e $v; end +argparse 'x#' 'v/verbose' -- -v -v argle -v -x 321 bargle +set -l + +echo '# Implicit int short flag only with custom validation passes' +for v in (set -l -n); set -e $v; end +argparse 'x#!_validate_int --max 500' 'v/verbose' -- -v -v -x 499 -v +set -l + +echo '# Implicit int short flag only with custom validation fails' >&2 +for v in (set -l -n); set -e $v; end +argparse 'x#!_validate_int --min 500' 'v/verbose' -- -v -v -x 499 -v +set -l + ########## # Verify that flag value validation works. diff --git a/tests/argparse.out b/tests/argparse.out index eb3752ec8..81f19adee 100644 --- a/tests/argparse.out +++ b/tests/argparse.out @@ -38,6 +38,25 @@ argv 'argle' 'bargle' _flag_m 765 _flag_max 765 argv 'argle' 'bargle' +# Bool short flag only +_flag_C -C +_flag_v '-v' '-v' +argv 'arg1' 'arg2' +# Value taking short flag only +_flag_v '--verbose' '-v' +_flag_verbose '--verbose' '-v' +_flag_x arg2 +argv arg1 +# Implicit int short flag only +_flag_v '-v' '-v' '-v' +_flag_verbose '-v' '-v' '-v' +_flag_x 321 +argv 'argle' 'bargle' +# Implicit int short flag only with custom validation passes +_flag_v '-v' '-v' '-v' +_flag_verbose '-v' '-v' '-v' +_flag_x 499 +argv # Check the exit status from argparse validation _flag_name max _flag_value 83 From d068f846e893fe17d2a1d90d49a12275ebbcf976 Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Fri, 21 Jul 2017 16:25:03 -0700 Subject: [PATCH 03/18] simplify `history` function The fix for #4232 allows us to simplify the `history` function slightly. --- share/functions/history.fish | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/share/functions/history.fish b/share/functions/history.fish index 0545aae23..bfe2651ad 100644 --- a/share/functions/history.fish +++ b/share/functions/history.fish @@ -48,12 +48,6 @@ function history --description "display or manipulate interactive command histor set show_time --show-time end - set -q _flag_null - and set -l null --null - - set -q _flag_case_sensitive - and set -l case_sensitive --case-sensitive - set -q _flag_prefix and set -l search_mode --prefix set -q _flag_contains @@ -96,9 +90,9 @@ function history --description "display or manipulate interactive command histor set -l pager less set -q PAGER and set pager $PAGER - builtin history search $search_mode $show_time $max_count $case_sensitive $null -- $argv | eval $pager + builtin history search $search_mode $show_time $max_count $_flag_case_sensitive $_flag_null -- $argv | eval $pager else - builtin history search $search_mode $show_time $max_count $case_sensitive $null -- $argv + builtin history search $search_mode $show_time $max_count $_flag_case_sensitive $_flag_null -- $argv end case delete # interactively delete history @@ -112,14 +106,14 @@ function history --description "display or manipulate interactive command histor and set search_mode "--contains" if test $search_mode = "--exact" - builtin history delete $search_mode $case_sensitive $argv + builtin history delete $search_mode $_flag_case_sensitive $argv return end # TODO: Fix this so that requesting history entries with a timestamp works: # set -l found_items (builtin history search $search_mode $show_time -- $argv) set -l found_items - builtin history search $search_mode $case_sensitive --null -- $argv | while read -lz x + builtin history search $search_mode $_flag_case_sensitive --null -- $argv | while read -lz x set found_items $found_items $x end if set -q found_items[1] From 3fc0faaebb0e553d254723b118b24c72b7e10622 Mon Sep 17 00:00:00 2001 From: Raphael P <raphael@imagine-app.fr> Date: Fri, 21 Jul 2017 22:36:27 +0200 Subject: [PATCH 04/18] Update completions for heroku pg:backups heroku pgbackups has been deprecated and replaced by heroku pg:backups command. See: https://devcenter.heroku.com/changelog-items/623 Completion for new subcommand has been added as well. --- share/completions/heroku.fish | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/share/completions/heroku.fish b/share/completions/heroku.fish index 2d11b15bb..ae46b8ef5 100644 --- a/share/completions/heroku.fish +++ b/share/completions/heroku.fish @@ -74,7 +74,6 @@ complete $heroku_looking -xa maintenance -d 'manage maintenance mode for an app' complete $heroku_looking -xa members -d 'manage membership in organization accounts' complete $heroku_looking -xa orgs -d 'manage organization accounts' complete $heroku_looking -xa pg -d 'manage heroku-postgresql databases' -complete $heroku_looking -xa pgbackups -d 'manage backups of heroku postgresql databases' complete $heroku_looking -xa plugins -d 'manage plugins to the heroku gem' complete $heroku_looking -xa regions -d 'list available regions' complete $heroku_looking -xa stack -d 'manage the stack for an app' @@ -168,6 +167,19 @@ complete -c heroku -n '__fish_heroku_using_command logs' -s p -l ps -l PS -d "on complete -c heroku -n '__fish_heroku_using_command logs' -s s -l source -l SOURCE -d "only display logs from the given source" complete -c heroku -n '__fish_heroku_using_command logs' -s t -l tail -d "continually stream logs" +# PG subcommands +complete $heroku_looking -xa pg:backups -d "manage backups of heroku postgresql databases" +complete $heroku_looking -xa pg:backups:cancel -d "cancel an in-progress backup or restore (default newest)" +complete $heroku_looking -xa pg:backups:capture -d "capture a new backup" +complete $heroku_looking -xa pg:backups:delete -d "delete a backup" +complete $heroku_looking -xa pg:backups:download -d "downloads database backup" +complete $heroku_looking -xa pg:backups:info -d "get information about a specific backup" +complete $heroku_looking -xa pg:backups:restore -d "restore a backup (default latest) to a database" +complete $heroku_looking -xa pg:backups:schedule -d "schedule daily backups for given database" +complete $heroku_looking -xa pg:backups:schedules -d "list backup schedule" +complete $heroku_looking -xa pg:backups:unschedule -d "stop daily backups" +complete $heroku_looking -xa pg:backups:url -d "get secret but publicly accessible URL of a backup" + # PS subcommands complete $heroku_looking -xa ps:resize -d "resize dynos to the given size (DYNO1=1X|2X|PX)" complete -c heroku -n '__fish_heroku_using_command ps:resize' -fa '(__fish_list_heroku_dynos)' -d "resize dynos to the given size (DYNO1=1X|2X|PX)" From 7304d8241684f7890099cd8fd60ca045ded3d025 Mon Sep 17 00:00:00 2001 From: Fabian Homborg <FHomborg@gmail.com> Date: Sat, 22 Jul 2017 14:09:07 +0200 Subject: [PATCH 05/18] Fix file completion for `tail` --- share/completions/tail.fish | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/completions/tail.fish b/share/completions/tail.fish index 66c426d66..72a6793c4 100644 --- a/share/completions/tail.fish +++ b/share/completions/tail.fish @@ -1,6 +1,6 @@ if tail --version > /dev/null ^ /dev/null complete -c tail -s c -l bytes -x -d 'output the last K bytes; alternatively, use -c +K to output bytes starting with the Kth of each file' - complete -c tail -s f -l follow -xa 'name descriptor' -d 'output appended data as the file grows; -f -l follow, and --follow=descriptor are equivalent' + complete -c tail -s f -l follow -a 'name descriptor' -d 'output appended data as the file grows; -f -l follow, and --follow=descriptor are equivalent' complete -c tail -s F -d 'same as --follow=name --retry' complete -c tail -s n -l lines -x -d 'output the last K lines, instead of the last 10; or use -n +K to output lines starting with the Kth' complete -c tail -l max-unchanged-stats -x -d 'with --follow=name, reopen a FILE which has not changed size after N iterations' @@ -9,8 +9,8 @@ if tail --version > /dev/null ^ /dev/null complete -c tail -l retry -d 'keep trying to open a file even when it is or becomes inaccessible; useful when following by name, i.e., with --follow=name' complete -c tail -s s -l sleep-interval -x -d 'with -f, sleep for approximately N seconds (default 1.0) between iterations' complete -c tail -s v -l verbose -d 'always output headers giving file names' - complete -c tail -l help -d 'display this help and exit' - complete -c tail -l version -d 'output version information and exit' + complete -c tail -x -l help -d 'display this help and exit' + complete -c tail -x -l version -d 'output version information and exit' else # OSX and similar - no longopts (and fewer shortopts) complete -c tail -s b -x -d 'output last K 512 byte blocks' complete -c tail -s c -x -d 'output the last K bytes or only K bytes with -r' From f8fa69f81766d77df48a68a66ec4d8d143469423 Mon Sep 17 00:00:00 2001 From: Rabah Meradi <rabahmeradi@gmail.com> Date: Sat, 15 Jul 2017 13:32:00 +0200 Subject: [PATCH 06/18] Document how to erase a path from $PATH variable Fixes #3161 --- doc_src/tutorial.hdr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc_src/tutorial.hdr b/doc_src/tutorial.hdr index 0e7727532..9d2ae535a 100644 --- a/doc_src/tutorial.hdr +++ b/doc_src/tutorial.hdr @@ -581,6 +581,12 @@ To prepend /usr/local/bin and /usr/sbin to `$PATH`, you can write: >_ set PATH /usr/local/bin /usr/sbin $PATH \endfish +To remove /usr/local/bin from `$PATH`, you can write: + +\fish{cli-dark} +>_ set PATH (string match -v /usr/local/bin $PATH) +\end{fish} + You can do so directly in `config.fish`, like you might do in other shells with `.profile`. See [this example](#path_example). A faster way is to modify the `$fish_user_paths` [universal variable](#tut_universal), which is automatically prepended to `$PATH`. For example, to permanently add `/usr/local/bin` to your `$PATH`, you could write: From d2d707a6faef50ad2b8cfe31ff49e33b6260944f Mon Sep 17 00:00:00 2001 From: Mohamed Akram <mohd-akram@users.noreply.github.com> Date: Fri, 21 Jul 2017 04:43:46 +0400 Subject: [PATCH 07/18] Ignore comments when creating man page completion --- share/tools/create_manpage_completions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/tools/create_manpage_completions.py b/share/tools/create_manpage_completions.py index fb63f8fab..0eb4045bc 100755 --- a/share/tools/create_manpage_completions.py +++ b/share/tools/create_manpage_completions.py @@ -528,7 +528,6 @@ class TypeDarwinManParser(ManParser): line = line.replace('.Nm', CMDNAME) line = line.replace('\\ ', ' ') line = line.replace('\& ', '') - line = line.replace(r'.\"', '') return line def is_option(self, line): @@ -567,6 +566,9 @@ class TypeDarwinManParser(ManParser): desc_lines = [] while lines and not self.is_option(lines[0]): line = lossy_unicode(lines.pop(0).strip()) + # Ignore comments + if line.startswith(r'.\"'): + continue if line.startswith('.'): line = self.groff_replace_escapes(line) line = self.trim_groff(line).strip() From 92f39f7b897fd7ea5289150ff2d2855bf98cfe91 Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Tue, 25 Jul 2017 14:02:50 -0700 Subject: [PATCH 08/18] fix bug introduced by commit 86af63cd3 --- share/functions/funcsave.fish | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/functions/funcsave.fish b/share/functions/funcsave.fish index a52e1a3fe..870967958 100644 --- a/share/functions/funcsave.fish +++ b/share/functions/funcsave.fish @@ -22,16 +22,16 @@ function funcsave --description "Save the current definition of all specified fu end end - set -l res 0 + set -l retval 0 for funcname in $argv if functions -q -- $funcname - functions -- $i >$configdir/fish/functions/$funcname + functions -- $funcname >$configdir/fish/functions/$funcname.fish else printf (_ "%s: Unknown function '%s'\n") funcsave $funcname - set res 1 + set retval 1 end end - return $res + return $retval end From 8e2d1657564e2d06c6f977a3ed6a85d4af8115aa Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Mon, 24 Jul 2017 11:58:31 -0700 Subject: [PATCH 09/18] document need for double-quotes around `test` args --- doc_src/test.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc_src/test.txt b/doc_src/test.txt index 43d9f5e57..c5aa4b0fb 100644 --- a/doc_src/test.txt +++ b/doc_src/test.txt @@ -14,6 +14,8 @@ The first form (`test`) is preferred. For compatibility with other shells, the s This test is mostly POSIX-compatible. +When using a variable as an argument for a test operator you should almost always enclose it in double-quotes. There are only two situations it is safe to omit the quote marks. The first is when the argument is a literal string with no whitespace or other characters special to the shell (e.g., semicolon). For example, `test -b /my/file`. The second is using a variable that expands to exactly one element including if that element is the empty string (e.g., `set x ''`). If the variable is not set, set but with no value, or set to more than one value you must enclose it in double-quotes. For example, `test "$x" = "$y"`. Since it is always safe to enclose variables in double-quotes when used as `test` arguments that is the recommended practice. + \subsection test-files Operators for files and directories - `-b FILE` returns true if `FILE` is a block device. From a071deaf6176769088de5141e663c577108aa63c Mon Sep 17 00:00:00 2001 From: Thales Mello <thalesmello@gmail.com> Date: Wed, 26 Jul 2017 08:31:35 -0300 Subject: [PATCH 10/18] Make npm run-script completion faster with `jq` (#4241) * Make npm run-script completion faster with `jq` When jq is available, it's actually faster to invoke jq and parse the `package.json` invoking the `npm` command. Also, prior to this commit, both `__fish_complete_npm` and `__fish_npm_run` were being run whenever completions for `npm run` subcommand was being used, which was actually making repetitive work (invoking npm command twice). This pull request is supposed to make completion without `jq` faster as well * Refactor npm.fish for code reutilization Created function to handle both cases of npm run completion parse, with or without `jq` completion. * Remove unecessary blank line --- share/completions/npm.fish | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/share/completions/npm.fish b/share/completions/npm.fish index d531dd1d3..0c57814a4 100644 --- a/share/completions/npm.fish +++ b/share/completions/npm.fish @@ -64,21 +64,28 @@ function __fish_complete_npm --description "Complete the commandline using npm's end # use npm completion for most of the things, -# except options completion because it sucks at it. +# except options completion (because it sucks at it) +# and run-script completion (reading package.json is faster). # see: https://github.com/npm/npm/issues/9524 # and: https://github.com/fish-shell/fish-shell/pull/2366 -complete -f -c npm -n 'not __fish_npm_needs_option' -a "(__fish_complete_npm)" +complete -f -c npm -n 'not __fish_npm_needs_option; and not __fish_npm_using_command run; and not __fish_npm_using_command run-script' -a "(__fish_complete_npm)" # list available npm scripts and their parial content +function __fish_parse_npm_run_completions + while read -l name + set -l trim 20 + read -l value + set value (string sub -l $trim -- $value) + printf "%s\t%s\n" $name $value + end +end + function __fish_npm_run # Like above, only try to call npm if there's a command by that name to facilitate aliases that call nvm. - if command -sq npm - command npm run | string match -r -v '^[^ ]|^$' | string trim | while read -l name - set -l trim 20 - read -l value - echo "$value" | cut -c1-$trim | read -l value - printf "%s\t%s\n" $name $value - end + if command -sq jq; and test -e package.json + jq -r '.scripts | to_entries[] | .key,.value' <package.json | __fish_parse_npm_run_completions + else if command -sq npm + command npm run | string match -r -v '^[^ ]|^$' | string trim | __fish_parse_npm_run_completions end end From defdc92b5734c76010479ad2025065a66e1ac5aa Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Wed, 26 Jul 2017 20:41:00 -0700 Subject: [PATCH 11/18] fix ssh config `Include` file handling Fixes #4253 --- share/functions/__fish_print_hostnames.fish | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/share/functions/__fish_print_hostnames.fish b/share/functions/__fish_print_hostnames.fish index 8844154d2..0bf89e334 100644 --- a/share/functions/__fish_print_hostnames.fish +++ b/share/functions/__fish_print_hostnames.fish @@ -44,14 +44,14 @@ function __fish_print_hostnames -d "Print a list of known hostnames" function _ssh_include --argument-names ssh_config # Relative paths in Include directive use /etc/ssh or ~/.ssh depending on # system or user level config. -F will not override this behaviour - if test $ssh_config = '/etc/ssh/ssh_config' + set -l relative_path $HOME/.ssh + if string match '/etc/ssh/*' -- $ssh_config set relative_path '/etc/ssh' - else - set relative_path $HOME/.ssh end function _recursive --no-scope-shadowing - set paths + set -l orig_dir $PWD + set -l paths for config in $argv set paths $paths (cat $config ^/dev/null \ # Keep only Include lines @@ -62,10 +62,11 @@ function __fish_print_hostnames -d "Print a list of known hostnames" | string trim | string replace -r -a '\s+' ' ') end + cd $relative_path set -l new_paths for path in $paths set -l expanded_path - eval set expanded_path (echo $path) + eval "set expanded_path (printf \"%s\n\" $path)" for path in $expanded_path # Resolve "relative" paths in accordance to ssh path resolution if string match -qv '/*' $path @@ -75,9 +76,9 @@ function __fish_print_hostnames -d "Print a list of known hostnames" set new_paths $new_paths $path end end + cd $orig_dir if test -n "$new_paths" - _recursive $new_paths end end From 4e026588f4aaf26fc49c16657359497d90ecce38 Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Thu, 27 Jul 2017 14:34:19 -0700 Subject: [PATCH 12/18] modify prev commit to use builtin cd Using the `cd` function can have undesirable side effects like mucking with the directory history. So force the use of the builtin cd. --- share/functions/__fish_print_hostnames.fish | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/functions/__fish_print_hostnames.fish b/share/functions/__fish_print_hostnames.fish index 0bf89e334..efa3cac4f 100644 --- a/share/functions/__fish_print_hostnames.fish +++ b/share/functions/__fish_print_hostnames.fish @@ -62,7 +62,7 @@ function __fish_print_hostnames -d "Print a list of known hostnames" | string trim | string replace -r -a '\s+' ' ') end - cd $relative_path + builtin cd $relative_path set -l new_paths for path in $paths set -l expanded_path @@ -76,7 +76,7 @@ function __fish_print_hostnames -d "Print a list of known hostnames" set new_paths $new_paths $path end end - cd $orig_dir + builtin cd $orig_dir if test -n "$new_paths" _recursive $new_paths From 18219646a085d3e536e1af6a28471283813eb04a Mon Sep 17 00:00:00 2001 From: Jon Eyolfson <jon@eyl.io> Date: Thu, 27 Jul 2017 19:49:03 -0400 Subject: [PATCH 13/18] Remove completer_t::complete_special_cd The class `completer_t` declares `complete_special_cd`, an unused method. I searched the entire source tree and this declaration seems to be the only instance of `complete_special_cd`. There is no definition or uses which likely means this is dead code. --- src/complete.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/complete.cpp b/src/complete.cpp index 3ca2c8131..ac364724c 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -317,8 +317,6 @@ class completer_t { void complete_param_expand(const wcstring &str, bool do_file, bool handle_as_special_cd = false); - void complete_special_cd(const wcstring &str); - void complete_cmd(const wcstring &str, bool use_function, bool use_builtin, bool use_command, bool use_implicit_cd); From a1c4475c0def343793131dfd7a75ec85483d4c21 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Tue, 25 Jul 2017 23:26:23 -0500 Subject: [PATCH 14/18] Silenced (wrong) -Wmaybe-uninitialized warnings --- src/expand.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/expand.cpp b/src/expand.cpp index 029c0af76..e00a1bff0 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -492,7 +492,7 @@ static bool find_job(const wchar_t *proc, expand_flags_t flags, while (const job_t *j = jobs.next()) { if (j->command_is_empty()) continue; - size_t offset; + size_t offset = 0; if (match_pid(j->command(), proc, &offset)) { if (flags & EXPAND_FOR_COMPLETIONS) { append_completion(completions, j->command_wcstr() + offset + wcslen(proc), @@ -514,7 +514,7 @@ static bool find_job(const wchar_t *proc, expand_flags_t flags, for (const process_ptr_t &p : j->processes) { if (p->actual_cmd.empty()) continue; - size_t offset; + size_t offset = 0; if (match_pid(p->actual_cmd, proc, &offset)) { if (flags & EXPAND_FOR_COMPLETIONS) { append_completion(completions, wcstring(p->actual_cmd, offset + wcslen(proc)), @@ -552,7 +552,7 @@ static void find_process(const wchar_t *proc, expand_flags_t flags, pid_t process_pid; process_iterator_t iterator; while (iterator.next_process(&process_name, &process_pid)) { - size_t offset; + size_t offset = 0; if (match_pid(process_name, proc, &offset)) { if (flags & EXPAND_FOR_COMPLETIONS) { append_completion(out, process_name.c_str() + offset + wcslen(proc), From 7a18c37b3959207f516718c7c991271955240eec Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Tue, 25 Jul 2017 23:34:22 -0500 Subject: [PATCH 15/18] Using write_ignore instead of write where the result is not checked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This silences warnings from the compiler about ignoring return value of ‘ssize_t write(int, const void*, size_t)’, declared with attribute warn_unused_result [-Wunused-result]. --- src/common.cpp | 16 +++------------- src/common.h | 6 +----- src/env_universal_common.cpp | 4 ++-- src/path.cpp | 2 +- src/reader.cpp | 8 ++++---- src/wutil.cpp | 2 +- 6 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/common.cpp b/src/common.cpp index d0dea1c20..b52dbf076 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -598,16 +598,6 @@ void __attribute__((noinline)) debug(int level, const char *msg, ...) { errno = errno_old; } -void read_ignore(int fd, void *buff, size_t count) { - size_t ignore __attribute__((unused)); - ignore = read(fd, buff, count); -} - -void write_ignore(int fd, const void *buff, size_t count) { - size_t ignore __attribute__((unused)); - ignore = write(fd, buff, count); -} - void debug_safe(int level, const char *msg, const char *param1, const char *param2, const char *param3, const char *param4, const char *param5, const char *param6, const char *param7, const char *param8, const char *param9, const char *param10, @@ -626,14 +616,14 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para const char *end = strchr(cursor, '%'); if (end == NULL) end = cursor + strlen(cursor); - write_ignore(STDERR_FILENO, cursor, end - cursor); + (void)write(STDERR_FILENO, cursor, end - cursor); if (end[0] == '%' && end[1] == 's') { // Handle a format string. assert(param_idx < sizeof params / sizeof *params); const char *format = params[param_idx++]; if (!format) format = "(null)"; - write_ignore(STDERR_FILENO, format, strlen(format)); + (void)write(STDERR_FILENO, format, strlen(format)); cursor = end + 2; } else if (end[0] == '\0') { // Must be at the end of the string. @@ -645,7 +635,7 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para } // We always append a newline. - write_ignore(STDERR_FILENO, "\n", 1); + (void)write(STDERR_FILENO, "\n", 1); errno = errno_old; } diff --git a/src/common.h b/src/common.h index d4a2f646f..aa0085378 100644 --- a/src/common.h +++ b/src/common.h @@ -179,10 +179,6 @@ extern bool g_profiling_active; /// Name of the current program. Should be set at startup. Used by the debug function. extern const wchar_t *program_name; -// Variants of read() and write() that ignores return values, defeating a warning. -void read_ignore(int fd, void *buff, size_t count); -void write_ignore(int fd, const void *buff, size_t count); - /// Set to false at run-time if it's been determined we can't trust the last modified timestamp on /// the tty. extern bool has_working_tty_timestamps; @@ -207,7 +203,7 @@ extern bool has_working_tty_timestamps; { \ char exit_read_buff; \ show_stackframe(L'E'); \ - read_ignore(0, &exit_read_buff, 1); \ + (void)read(0, &exit_read_buff, 1); \ exit_without_destructors(1); \ } diff --git a/src/env_universal_common.cpp b/src/env_universal_common.cpp index c340e5961..00228113f 100644 --- a/src/env_universal_common.cpp +++ b/src/env_universal_common.cpp @@ -1209,7 +1209,7 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { // would cause us to hang! size_t read_amt = 64 * 1024; void *buff = malloc(read_amt); - read_ignore(this->pipe_fd, buff, read_amt); + (void)read(this->pipe_fd, buff, read_amt); free(buff); } @@ -1308,7 +1308,7 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { while (this->readback_amount > 0) { char buff[64]; size_t amt_to_read = mini(this->readback_amount, sizeof buff); - read_ignore(this->pipe_fd, buff, amt_to_read); + (void)read(this->pipe_fd, buff, amt_to_read); this->readback_amount -= amt_to_read; } assert(this->readback_amount == 0); diff --git a/src/path.cpp b/src/path.cpp index fcb383474..7af742974 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -275,7 +275,7 @@ static void maybe_issue_path_warning(const wcstring &which_dir, const wcstring & debug(0, _(L"The error was '%s'."), strerror(saved_errno)); debug(0, _(L"Please set $%ls to a directory where you have write access."), env_var); } - write(STDERR_FILENO, "\n", 1); + (void)write(STDERR_FILENO, "\n", 1); } static void path_create(wcstring &path, const wcstring &xdg_var, const wcstring &which_dir, diff --git a/src/reader.cpp b/src/reader.cpp index 1e0e05698..8dff6cc6a 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -696,14 +696,14 @@ void reader_write_title(const wcstring &cmd, bool reset_cursor_position) { for (size_t i = 0; i < lst.size(); i++) { fputws(lst.at(i).c_str(), stdout); } - write(STDOUT_FILENO, "\a", 1); + (void)write(STDOUT_FILENO, "\a", 1); } proc_pop_interactive(); set_color(rgb_color_t::reset(), rgb_color_t::reset()); if (reset_cursor_position && !lst.empty()) { // Put the cursor back at the beginning of the line (issue #2453). - write(STDOUT_FILENO, "\r", 1); + (void)write(STDOUT_FILENO, "\r", 1); } } @@ -1291,7 +1291,7 @@ static void reader_flash() { } reader_repaint(); - write(STDOUT_FILENO, "\a", 1); + (void)write(STDOUT_FILENO, "\a", 1); pollint.tv_sec = 0; pollint.tv_nsec = 100 * 1000000; @@ -3229,7 +3229,7 @@ const wchar_t *reader_readline(int nchars) { reader_repaint_if_needed(); } - write(STDOUT_FILENO, "\n", 1); + (void)write(STDOUT_FILENO, "\n", 1); // Ensure we have no pager contents when we exit. if (!data->pager.empty()) { diff --git a/src/wutil.cpp b/src/wutil.cpp index 5b69c47f4..fd1586950 100644 --- a/src/wutil.cpp +++ b/src/wutil.cpp @@ -338,7 +338,7 @@ void safe_perror(const char *message) { safe_append(buff, safe_strerror(err), sizeof buff); safe_append(buff, "\n", sizeof buff); - write_ignore(STDERR_FILENO, buff, strlen(buff)); + (void)write(STDERR_FILENO, buff, strlen(buff)); errno = err; } From 96fca8b4ecfc9fa097a20e7f6396ecbaba21fbf8 Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Thu, 27 Jul 2017 21:32:49 -0700 Subject: [PATCH 16/18] fix how fish behaves when FISH_HISTORY is set Without this change setting `FISH_HISTORY` causes interactive input to no longer provide autosuggestions, completions, etc. Fixes #4234 --- src/env.cpp | 3 +-- src/reader.cpp | 5 +++++ src/reader.h | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 2a4cf434a..eec86d2ec 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -613,8 +613,7 @@ static void react_to_variable_change(const wcstring &key) { } else if (key == L"FISH_READ_BYTE_LIMIT") { env_set_read_limit(); } else if (key == L"FISH_HISTORY") { - history_destroy(); - reader_push(history_session_id().c_str()); + reader_change_history(history_session_id().c_str()); } } diff --git a/src/reader.cpp b/src/reader.cpp index 8dff6cc6a..c46d13af8 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1995,6 +1995,11 @@ static parser_test_error_bits_t default_test(const wchar_t *b) { return 0; } +void reader_change_history(const wchar_t *name) { + data->history->save(); + data->history = &history_t::history_with_name(name); +} + void reader_push(const wchar_t *name) { reader_data_t *n = new reader_data_t(); diff --git a/src/reader.h b/src/reader.h index c773ea1ed..3dc0d0802 100644 --- a/src/reader.h +++ b/src/reader.h @@ -72,6 +72,9 @@ const wchar_t *reader_current_filename(); /// \param fn The fileanme to push void reader_push_current_filename(const wchar_t *fn); +/// Change the history file for the current command reading context. +void reader_change_history(const wchar_t *fn); + /// Pop the current filename from the stack of read files. void reader_pop_current_filename(); From d68b631919e4a4a0c147d54d989b0bb8fbf1adf5 Mon Sep 17 00:00:00 2001 From: Daniel K <code.danielk@gmail.com> Date: Fri, 28 Jul 2017 19:59:00 +0200 Subject: [PATCH 17/18] fix check for existing variable --- share/completions/git.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/completions/git.fish b/share/completions/git.fish index 6a2b92746..d8e9df6d7 100644 --- a/share/completions/git.fish +++ b/share/completions/git.fish @@ -161,7 +161,7 @@ function __fish_git_using_command # Check aliases. set -l varname __fish_git_alias_(string escape --style=var -- $cmd) - set -q $$varname + set -q $varname and contains -- $$varname $argv and return 0 return 1 From f4414a06311267de8940a88fb18a728d2181e8f8 Mon Sep 17 00:00:00 2001 From: Kurtis Rader <krader@skepticism.us> Date: Sat, 29 Jul 2017 21:50:39 -0700 Subject: [PATCH 18/18] implement `complete -k` as a no-op Fixes #4270 --- src/builtin_complete.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index c05c532fa..8ab13214d 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -129,7 +129,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wcstring_list_t path; wcstring_list_t wrap_targets; - static const wchar_t *short_options = L":a:c:p:s:l:o:d:frxeuAn:C::w:h"; + static const wchar_t *short_options = L":a:c:kp:s:l:o:d:frxeuAn:C::w:h"; static const struct woption long_options[] = {{L"exclusive", no_argument, NULL, 'x'}, {L"no-files", no_argument, NULL, 'f'}, {L"require-parameter", no_argument, NULL, 'r'}, @@ -147,6 +147,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { {L"wraps", required_argument, NULL, 'w'}, {L"do-complete", optional_argument, NULL, 'C'}, {L"help", no_argument, NULL, 'h'}, + {L"keep-order", no_argument, NULL, 'k'}, {NULL, 0, NULL, 0}}; int opt; @@ -165,6 +166,11 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { result_mode |= NO_COMMON; break; } + case 'k': { + // This is a no-op in fish 2.7. It is implemented in fish 3.0. We want it to be + // silently ignored if someone happens to use a completion that uses this flag. + break; + } case 'p': case 'c': { wcstring tmp;