From 91ddee6bf045b1c6144d14233abdb96127186ec3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 20:32:21 +0200 Subject: [PATCH 01/11] nix: Implement basic bash completion --- Makefile | 1 + misc/bash/completion.sh | 7 +++++ misc/bash/local.mk | 1 + src/libmain/common-args.cc | 10 ++++++- src/libutil/args.cc | 55 ++++++++++++++++++++++++++++++++++++-- src/libutil/args.hh | 4 +++ src/libutil/hash.cc | 3 +++ src/libutil/hash.hh | 2 ++ src/nix/main.cc | 16 ++++++++++- 9 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 misc/bash/completion.sh create mode 100644 misc/bash/local.mk diff --git a/Makefile b/Makefile index e3057c36c..545397b23 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ makefiles = \ src/resolve-system-dependencies/local.mk \ scripts/local.mk \ corepkgs/local.mk \ + misc/bash/local.mk \ misc/systemd/local.mk \ misc/launchd/local.mk \ misc/upstart/local.mk \ diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh new file mode 100644 index 000000000..097353b50 --- /dev/null +++ b/misc/bash/completion.sh @@ -0,0 +1,7 @@ +function _complete_nix { + while IFS= read -r line; do + COMPREPLY+=("$line") + done < <(NIX_GET_COMPLETIONS=$COMP_CWORD "${COMP_WORDS[@]}") +} + +complete -F _complete_nix nix diff --git a/misc/bash/local.mk b/misc/bash/local.mk new file mode 100644 index 000000000..99ada5108 --- /dev/null +++ b/misc/bash/local.mk @@ -0,0 +1 @@ +$(eval $(call install-file-as, $(d)/completion.sh, $(datarootdir)/bash-completion/completions/_nix3, 0644)) diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc index 51e199ea5..f3f508ff4 100644 --- a/src/libmain/common-args.cc +++ b/src/libmain/common-args.cc @@ -31,9 +31,17 @@ MixCommonArgs::MixCommonArgs(const string & programName) .labels = {"name", "value"}, .handler = {[](std::string name, std::string value) { try { + if (auto prefix = needsCompletion(name)) { + std::map settings; + globalConfig.getSettings(settings); + for (auto & s : settings) + if (hasPrefix(s.first, *prefix)) + completions->insert(s.first); + } globalConfig.set(name, value); } catch (UsageError & e) { - warn(e.what()); + if (!completions) + warn(e.what()); } }}, }); diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f829415d1..ceaabcfca 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -13,6 +13,19 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +std::shared_ptr> completions; + +std::string completionMarker = "___COMPLETE___"; + +std::optional needsCompletion(std::string_view s) +{ + if (!completions) return {}; + auto i = s.find(completionMarker); + if (i != std::string::npos) + return std::string(s.begin(), i); + return {}; +} + void Args::parseCmdline(const Strings & _cmdline) { Strings pendingArgs; @@ -20,6 +33,13 @@ void Args::parseCmdline(const Strings & _cmdline) Strings cmdline(_cmdline); + if (auto s = getEnv("NIX_GET_COMPLETIONS")) { + size_t n = std::stoi(*s); + assert(n > 0 && n <= cmdline.size()); + *std::next(cmdline.begin(), n - 1) += completionMarker; + completions = std::make_shared(); + } + for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { auto arg = *pos; @@ -99,18 +119,32 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; + bool anyNeedsCompletion = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; - throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); + if (anyNeedsCompletion) + args.push_back(""); + else + throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); + } else { + if (needsCompletion(*pos)) + anyNeedsCompletion = true; + args.push_back(*pos++); } - args.push_back(*pos++); } flag.handler.fun(std::move(args)); return true; }; if (string(*pos, 0, 2) == "--") { + if (auto prefix = needsCompletion(*pos)) { + for (auto & [name, flag] : longFlags) { + if (!hiddenCategories.count(flag->category) + && hasPrefix(name, std::string(*prefix, 2))) + completions->insert("--" + name); + } + } auto i = longFlags.find(string(*pos, 2)); if (i == longFlags.end()) return false; return process("--" + i->first, *i->second); @@ -123,6 +157,14 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) return process(std::string("-") + c, *i->second); } + if (auto prefix = needsCompletion(*pos)) { + if (prefix == "-") { + completions->insert("--"); + for (auto & [flag, _] : shortFlags) + completions->insert(std::string("-") + flag); + } + } + return false; } @@ -161,6 +203,10 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')", .labels = {"hash-algo"}, .handler = {[ht](std::string s) { + if (auto prefix = needsCompletion(s)) + for (auto & type : hashTypes) + if (hasPrefix(type, *prefix)) + completions->insert(type); *ht = parseHashType(s); if (*ht == htUnknown) throw UsageError("unknown hash type '%1%'", s); @@ -217,6 +263,11 @@ MultiCommand::MultiCommand(const Commands & commands) { expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { assert(!command); + if (auto prefix = needsCompletion(ss[0])) { + for (auto & [name, command] : commands) + if (hasPrefix(name, *prefix)) + completions->insert(name); + } auto i = commands.find(ss[0]); if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 1932e6a8a..ae2875e72 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -256,4 +256,8 @@ typedef std::vector> Table2; void printTable(std::ostream & out, const Table2 & table); +extern std::shared_ptr> completions; + +std::optional needsCompletion(std::string_view s); + } diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 7caee1da7..606c78ed7 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -16,6 +16,9 @@ namespace nix { +std::set hashTypes = { "md5", "sha1", "sha256", "sha512" }; + + void Hash::init() { if (type == htMD5) hashSize = md5HashSize; diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index ea9fca3e7..e1a16ba22 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -18,6 +18,8 @@ const int sha1HashSize = 20; const int sha256HashSize = 32; const int sha512HashSize = 64; +extern std::set hashTypes; + extern const string base32Chars; enum Base : int { Base64, Base32, Base16, SRI }; diff --git a/src/nix/main.cc b/src/nix/main.cc index 3915a4896..c491bc264 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -166,7 +166,21 @@ void mainWrapped(int argc, char * * argv) NixArgs args; - args.parseCmdline(argvToStrings(argc, argv)); + Finally printCompletions([&]() + { + if (completions) { + for (auto & s : *completions) + std::cout << s << "\n"; + } + }); + + try { + args.parseCmdline(argvToStrings(argc, argv)); + } catch (UsageError &) { + if (!completions) throw; + } + + if (completions) return; settings.requireExperimentalFeature("nix-command"); From e0c19ee620c53b52ca7cf69c19d414d782338be1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:35:07 +0200 Subject: [PATCH 02/11] Add completion for paths --- misc/bash/completion.sh | 10 ++++++++- src/libutil/args.cc | 48 ++++++++++++++++++++++++++++++++++++++++- src/libutil/args.hh | 12 +++++++++-- src/nix/build.cc | 1 + src/nix/cat.cc | 4 ++-- src/nix/command.cc | 1 + src/nix/hash.cc | 2 +- src/nix/installables.cc | 3 ++- src/nix/ls.cc | 4 ++-- src/nix/main.cc | 5 +++-- src/nix/repl.cc | 2 +- src/nix/run.cc | 2 +- src/nix/sigs.cc | 3 ++- 13 files changed, 82 insertions(+), 15 deletions(-) diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh index 097353b50..93298c369 100644 --- a/misc/bash/completion.sh +++ b/misc/bash/completion.sh @@ -1,6 +1,14 @@ function _complete_nix { + local have_type while IFS= read -r line; do - COMPREPLY+=("$line") + if [[ -z $have_type ]]; then + have_type=1 + if [[ $line = filenames ]]; then + compopt -o filenames + fi + else + COMPREPLY+=("$line") + fi done < <(NIX_GET_COMPLETIONS=$COMP_CWORD "${COMP_WORDS[@]}") } diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ceaabcfca..320b1b4b2 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -1,6 +1,8 @@ #include "args.hh" #include "hash.hh" +#include + namespace nix { void Args::addFlag(Flag && flag_) @@ -13,6 +15,7 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +bool pathCompletions = false; std::shared_ptr> completions; std::string completionMarker = "___COMPLETE___"; @@ -128,8 +131,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) else throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } else { - if (needsCompletion(*pos)) + if (needsCompletion(*pos)) { + if (flag.completer) + flag.completer(n, *pos); anyNeedsCompletion = true; + } args.push_back(*pos++); } } @@ -214,6 +220,46 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } +void completePath(size_t, std::string_view s) +{ + if (auto prefix = needsCompletion(s)) { + pathCompletions = true; + glob_t globbuf; + if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) + completions->insert(globbuf.gl_pathv[i]); + globfree(&globbuf); + } + } +} + +void Args::expectPathArg(const std::string & label, string * dest, bool optional) +{ + expectedArgs.push_back({ + .label = label, + .arity = 1, + .optional = optional, + .handler = {[=](std::vector ss) { + completePath(0, ss[0]); + *dest = ss[0]; + }} + }); +} + +void Args::expectPathArgs(const std::string & label, std::vector * dest) +{ + expectedArgs.push_back({ + .label = label, + .arity = 0, + .optional = false, + .handler = {[=](std::vector ss) { + for (auto & s : ss) + completePath(0, s); + *dest = std::move(ss); + }} + }); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; diff --git a/src/libutil/args.hh b/src/libutil/args.hh index ae2875e72..f93459c96 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -83,6 +83,7 @@ protected: std::string category; Strings labels; Handler handler; + std::function completer; static Flag mkHashTypeFlag(std::string && longName, HashType * ht); }; @@ -98,8 +99,8 @@ protected: struct ExpectedArg { std::string label; - size_t arity; // 0 = any - bool optional; + size_t arity = 0; // 0 = any + bool optional = false; std::function)> handler; }; @@ -182,6 +183,8 @@ public: }}); } + void expectPathArg(const std::string & label, string * dest, bool optional = false); + /* Expect 0 or more arguments. */ void expectArgs(const std::string & label, std::vector * dest) { @@ -190,6 +193,8 @@ public: }}); } + void expectPathArgs(const std::string & label, std::vector * dest); + friend class MultiCommand; }; @@ -257,7 +262,10 @@ typedef std::vector> Table2; void printTable(std::ostream & out, const Table2 & table); extern std::shared_ptr> completions; +extern bool pathCompletions; std::optional needsCompletion(std::string_view s); +void completePath(size_t, std::string_view s); + } diff --git a/src/nix/build.cc b/src/nix/build.cc index 83d47acd4..474337208 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -18,6 +18,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile .description = "path of the symlink to the build result", .labels = {"path"}, .handler = {&outLink}, + .completer = completePath }); addFlag({ diff --git a/src/nix/cat.cc b/src/nix/cat.cc index fd91f2036..eeee1e529 100644 --- a/src/nix/cat.cc +++ b/src/nix/cat.cc @@ -25,7 +25,7 @@ struct CmdCatStore : StoreCommand, MixCat { CmdCatStore() { - expectArg("path", &path); + expectPathArg("path", &path); } std::string description() override @@ -47,7 +47,7 @@ struct CmdCatNar : StoreCommand, MixCat CmdCatNar() { - expectArg("nar", &narPath); + expectPathArg("nar", &narPath); expectArg("path", &path); } diff --git a/src/nix/command.cc b/src/nix/command.cc index 71b027719..803a36e84 100644 --- a/src/nix/command.cc +++ b/src/nix/command.cc @@ -108,6 +108,7 @@ MixProfile::MixProfile() .description = "profile to update", .labels = {"path"}, .handler = {&profile}, + .completer = completePath }); } diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 366314227..0f460c668 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -31,7 +31,7 @@ struct CmdHash : Command .labels({"modulus"}) .dest(&modulus); #endif - expectArgs("paths", &paths); + expectPathArgs("paths", &paths); } std::string description() override diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 57060e9b1..c144a7e70 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -77,7 +77,8 @@ SourceExprCommand::SourceExprCommand() .shortName = 'f', .description = "evaluate FILE rather than the default", .labels = {"file"}, - .handler = {&file} + .handler = {&file}, + .completer = completePath }); addFlag({ diff --git a/src/nix/ls.cc b/src/nix/ls.cc index b9716a6a1..aac082422 100644 --- a/src/nix/ls.cc +++ b/src/nix/ls.cc @@ -85,7 +85,7 @@ struct CmdLsStore : StoreCommand, MixLs { CmdLsStore() { - expectArg("path", &path); + expectPathArg("path", &path); } Examples examples() override @@ -117,7 +117,7 @@ struct CmdLsNar : Command, MixLs CmdLsNar() { - expectArg("nar", &narPath); + expectPathArg("nar", &narPath); expectArg("path", &path); } diff --git a/src/nix/main.cc b/src/nix/main.cc index c491bc264..fffdeab90 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -68,7 +68,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs addFlag({ .longName = "help", .description = "show usage information", - .handler = {[&]() { showHelpAndExit(); }}, + .handler = {[&]() { if (!completions) showHelpAndExit(); }}, }); addFlag({ @@ -96,7 +96,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs addFlag({ .longName = "version", .description = "show version information", - .handler = {[&]() { printVersion(programName); }}, + .handler = {[&]() { if (!completions) printVersion(programName); }}, }); addFlag({ @@ -169,6 +169,7 @@ void mainWrapped(int argc, char * * argv) Finally printCompletions([&]() { if (completions) { + std::cout << (pathCompletions ? "filenames\n" : "no-filenames\n"); for (auto & s : *completions) std::cout << s << "\n"; } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 0a6a7ab19..2e7b14d08 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -767,7 +767,7 @@ struct CmdRepl : StoreCommand, MixEvalArgs CmdRepl() { - expectArgs("files", &files); + expectPathArgs("files", &files); } std::string description() override diff --git a/src/nix/run.cc b/src/nix/run.cc index 3e2c8b4f3..3701ffe3d 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -149,7 +149,7 @@ struct CmdRun : InstallableCommand, RunCommon CmdRun() { - expectArgs("args", &args); + expectPathArgs("args", &args); } std::string description() override diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc index 6c9b9a792..7821a5432 100644 --- a/src/nix/sigs.cc +++ b/src/nix/sigs.cc @@ -105,7 +105,8 @@ struct CmdSignPaths : StorePathsCommand .shortName = 'k', .description = "file containing the secret signing key", .labels = {"file"}, - .handler = {&secretKeyFile} + .handler = {&secretKeyFile}, + .completer = completePath }); } From 0884f180f5ca8a864e6db5256eaa10646e87d671 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:50:32 +0200 Subject: [PATCH 03/11] Simplify --- src/libfetchers/registry.cc | 2 +- src/libmain/common-args.cc | 16 +++++++++------- src/libutil/args.cc | 28 +++++++++++----------------- src/libutil/util.cc | 2 +- src/libutil/util.hh | 2 +- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/libfetchers/registry.cc b/src/libfetchers/registry.cc index 77d3b3378..f6760d2d0 100644 --- a/src/libfetchers/registry.cc +++ b/src/libfetchers/registry.cc @@ -155,7 +155,7 @@ void overrideRegistry( static std::shared_ptr getGlobalRegistry(ref store) { static auto reg = [&]() { - auto path = settings.flakeRegistry; + auto path = settings.flakeRegistry.get(); if (!hasPrefix(path, "/")) { auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath; diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc index f3f508ff4..51a61f1ca 100644 --- a/src/libmain/common-args.cc +++ b/src/libmain/common-args.cc @@ -31,19 +31,21 @@ MixCommonArgs::MixCommonArgs(const string & programName) .labels = {"name", "value"}, .handler = {[](std::string name, std::string value) { try { - if (auto prefix = needsCompletion(name)) { - std::map settings; - globalConfig.getSettings(settings); - for (auto & s : settings) - if (hasPrefix(s.first, *prefix)) - completions->insert(s.first); - } globalConfig.set(name, value); } catch (UsageError & e) { if (!completions) warn(e.what()); } }}, + .completer = [](size_t index, std::string_view prefix) { + if (index == 0) { + std::map settings; + globalConfig.getSettings(settings); + for (auto & s : settings) + if (hasPrefix(s.first, prefix)) + completions->insert(s.first); + } + } }); addFlag({ diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 320b1b4b2..257b4b6c4 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -122,22 +122,15 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; - bool anyNeedsCompletion = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; - if (anyNeedsCompletion) - args.push_back(""); - else - throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); - } else { - if (needsCompletion(*pos)) { - if (flag.completer) - flag.completer(n, *pos); - anyNeedsCompletion = true; - } - args.push_back(*pos++); + throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } + if (auto prefix = needsCompletion(*pos)) + if (flag.completer) + flag.completer(n, *prefix); + args.push_back(*pos++); } flag.handler.fun(std::move(args)); return true; @@ -209,14 +202,15 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')", .labels = {"hash-algo"}, .handler = {[ht](std::string s) { - if (auto prefix = needsCompletion(s)) - for (auto & type : hashTypes) - if (hasPrefix(type, *prefix)) - completions->insert(type); *ht = parseHashType(s); if (*ht == htUnknown) throw UsageError("unknown hash type '%1%'", s); - }} + }}, + .completer = [](size_t index, std::string_view prefix) { + for (auto & type : hashTypes) + if (hasPrefix(type, prefix)) + completions->insert(type); + } }; } diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 4c8e2b26d..f2782ce69 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1330,7 +1330,7 @@ bool statusOk(int status) } -bool hasPrefix(const string & s, const string & prefix) +bool hasPrefix(std::string_view s, std::string_view prefix) { return s.compare(0, prefix.size(), prefix) == 0; } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 4be1d4580..a861d5aa6 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -431,7 +431,7 @@ template bool string2Float(const string & s, N & n) /* Return true iff `s' starts with `prefix'. */ -bool hasPrefix(const string & s, const string & prefix); +bool hasPrefix(std::string_view s, std::string_view prefix); /* Return true iff `s' ends in `suffix'. */ From 4c3c638a05e52cdc3bd96255873b711a28630288 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 15:46:18 +0200 Subject: [PATCH 04/11] Cleanup --- src/libutil/args.cc | 90 ++++++++++++----------------- src/libutil/args.hh | 123 +++++++++++++++++++++------------------- src/nix/cat.cc | 12 +++- src/nix/command.hh | 5 +- src/nix/hash.cc | 6 +- src/nix/installables.cc | 8 +++ src/nix/ls.cc | 12 +++- src/nix/repl.cc | 6 +- src/nix/run.cc | 6 +- 9 files changed, 145 insertions(+), 123 deletions(-) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 257b4b6c4..c389090ae 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -86,7 +86,7 @@ void Args::printHelp(const string & programName, std::ostream & out) for (auto & exp : expectedArgs) { std::cout << renderLabels({exp.label}); // FIXME: handle arity > 1 - if (exp.arity == 0) std::cout << "..."; + if (exp.handler.arity == ArityAny) std::cout << "..."; if (exp.optional) std::cout << "?"; } std::cout << "\n"; @@ -127,8 +127,8 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) if (flag.handler.arity == ArityAny) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } - if (auto prefix = needsCompletion(*pos)) - if (flag.completer) + if (flag.completer) + if (auto prefix = needsCompletion(*pos)) flag.completer(n, *prefix); args.push_back(*pos++); } @@ -179,12 +179,17 @@ bool Args::processArgs(const Strings & args, bool finish) bool res = false; - if ((exp.arity == 0 && finish) || - (exp.arity > 0 && args.size() == exp.arity)) + if ((exp.handler.arity == ArityAny && finish) || + (exp.handler.arity != ArityAny && args.size() == exp.handler.arity)) { std::vector ss; - for (auto & s : args) ss.push_back(s); - exp.handler(std::move(ss)); + for (const auto &[n, s] : enumerate(args)) { + ss.push_back(s); + if (exp.completer) + if (auto prefix = needsCompletion(s)) + exp.completer(n, *prefix); + } + exp.handler.fun(ss); expectedArgs.pop_front(); res = true; } @@ -214,46 +219,17 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -void completePath(size_t, std::string_view s) +void completePath(size_t, std::string_view prefix) { - if (auto prefix = needsCompletion(s)) { - pathCompletions = true; - glob_t globbuf; - if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { - for (size_t i = 0; i < globbuf.gl_pathc; ++i) - completions->insert(globbuf.gl_pathv[i]); - globfree(&globbuf); - } + pathCompletions = true; + glob_t globbuf; + if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) + completions->insert(globbuf.gl_pathv[i]); + globfree(&globbuf); } } -void Args::expectPathArg(const std::string & label, string * dest, bool optional) -{ - expectedArgs.push_back({ - .label = label, - .arity = 1, - .optional = optional, - .handler = {[=](std::vector ss) { - completePath(0, ss[0]); - *dest = ss[0]; - }} - }); -} - -void Args::expectPathArgs(const std::string & label, std::vector * dest) -{ - expectedArgs.push_back({ - .label = label, - .arity = 0, - .optional = false, - .handler = {[=](std::vector ss) { - for (auto & s : ss) - completePath(0, s); - *dest = std::move(ss); - }} - }); -} - Strings argvToStrings(int argc, char * * argv) { Strings args; @@ -301,18 +277,22 @@ void Command::printHelp(const string & programName, std::ostream & out) MultiCommand::MultiCommand(const Commands & commands) : commands(commands) { - expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { - assert(!command); - if (auto prefix = needsCompletion(ss[0])) { - for (auto & [name, command] : commands) - if (hasPrefix(name, *prefix)) - completions->insert(name); - } - auto i = commands.find(ss[0]); - if (i == commands.end()) - throw UsageError("'%s' is not a recognised command", ss[0]); - command = {ss[0], i->second()}; - }}); + expectArgs({ + .label = "command", + .optional = true, + .handler = {[=](std::string s) { + assert(!command); + if (auto prefix = needsCompletion(s)) { + for (auto & [name, command] : commands) + if (hasPrefix(name, *prefix)) + completions->insert(name); + } + auto i = commands.find(s); + if (i == commands.end()) + throw UsageError("'%s' is not a recognised command", s); + command = {s, i->second()}; + }} + }); categories[Command::catDefault] = "Available commands"; } diff --git a/src/libutil/args.hh b/src/libutil/args.hh index f93459c96..250244162 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -28,55 +28,60 @@ protected: static const size_t ArityAny = std::numeric_limits::max(); + struct Handler + { + std::function)> fun; + size_t arity; + + Handler() {} + + Handler(std::function)> && fun) + : fun(std::move(fun)) + , arity(ArityAny) + { } + + Handler(std::function && handler) + : fun([handler{std::move(handler)}](std::vector) { handler(); }) + , arity(0) + { } + + Handler(std::function && handler) + : fun([handler{std::move(handler)}](std::vector ss) { + handler(std::move(ss[0])); + }) + , arity(1) + { } + + Handler(std::function && handler) + : fun([handler{std::move(handler)}](std::vector ss) { + handler(std::move(ss[0]), std::move(ss[1])); + }) + , arity(2) + { } + + Handler(std::vector * dest) + : fun([=](std::vector ss) { *dest = ss; }) + , arity(ArityAny) + { } + + template + Handler(T * dest) + : fun([=](std::vector ss) { *dest = ss[0]; }) + , arity(1) + { } + + template + Handler(T * dest, const T & val) + : fun([=](std::vector ss) { *dest = val; }) + , arity(0) + { } + }; + /* Flags. */ struct Flag { typedef std::shared_ptr ptr; - struct Handler - { - std::function)> fun; - size_t arity; - - Handler() {} - - Handler(std::function)> && fun) - : fun(std::move(fun)) - , arity(ArityAny) - { } - - Handler(std::function && handler) - : fun([handler{std::move(handler)}](std::vector) { handler(); }) - , arity(0) - { } - - Handler(std::function && handler) - : fun([handler{std::move(handler)}](std::vector ss) { - handler(std::move(ss[0])); - }) - , arity(1) - { } - - Handler(std::function && handler) - : fun([handler{std::move(handler)}](std::vector ss) { - handler(std::move(ss[0]), std::move(ss[1])); - }) - , arity(2) - { } - - template - Handler(T * dest) - : fun([=](std::vector ss) { *dest = ss[0]; }) - , arity(1) - { } - - template - Handler(T * dest, const T & val) - : fun([=](std::vector ss) { *dest = val; }) - , arity(0) - { } - }; - std::string longName; char shortName = 0; std::string description; @@ -99,9 +104,9 @@ protected: struct ExpectedArg { std::string label; - size_t arity = 0; // 0 = any bool optional = false; - std::function)> handler; + Handler handler; + std::function completer; }; std::list expectedArgs; @@ -175,26 +180,30 @@ public: }); } + void expectArgs(ExpectedArg && arg) + { + expectedArgs.emplace_back(std::move(arg)); + } + /* Expect a string argument. */ void expectArg(const std::string & label, string * dest, bool optional = false) { - expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](std::vector ss) { - *dest = ss[0]; - }}); + expectArgs({ + .label = label, + .optional = true, + .handler = {dest} + }); } - void expectPathArg(const std::string & label, string * dest, bool optional = false); - /* Expect 0 or more arguments. */ void expectArgs(const std::string & label, std::vector * dest) { - expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector ss) { - *dest = std::move(ss); - }}); + expectArgs({ + .label = label, + .handler = {dest} + }); } - void expectPathArgs(const std::string & label, std::vector * dest); - friend class MultiCommand; }; @@ -266,6 +275,6 @@ extern bool pathCompletions; std::optional needsCompletion(std::string_view s); -void completePath(size_t, std::string_view s); +void completePath(size_t, std::string_view prefix); } diff --git a/src/nix/cat.cc b/src/nix/cat.cc index eeee1e529..b528a0507 100644 --- a/src/nix/cat.cc +++ b/src/nix/cat.cc @@ -25,7 +25,11 @@ struct CmdCatStore : StoreCommand, MixCat { CmdCatStore() { - expectPathArg("path", &path); + expectArgs({ + .label = "path", + .handler = {&path}, + .completer = completePath + }); } std::string description() override @@ -47,7 +51,11 @@ struct CmdCatNar : StoreCommand, MixCat CmdCatNar() { - expectPathArg("nar", &narPath); + expectArgs({ + .label = "nar", + .handler = {&narPath}, + .completer = completePath + }); expectArg("path", &path); } diff --git a/src/nix/command.hh b/src/nix/command.hh index 6b4781303..0738c0f91 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -73,10 +73,7 @@ struct InstallablesCommand : virtual Args, SourceExprCommand { std::vector> installables; - InstallablesCommand() - { - expectArgs("installables", &_installables); - } + InstallablesCommand(); void prepare() override; diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 0f460c668..d5636eb47 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -31,7 +31,11 @@ struct CmdHash : Command .labels({"modulus"}) .dest(&modulus); #endif - expectPathArgs("paths", &paths); + expectArgs({ + .label = "paths", + .handler = {&paths}, + .completer = completePath + }); } std::string description() override diff --git a/src/nix/installables.cc b/src/nix/installables.cc index c144a7e70..abc642e11 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -571,6 +571,14 @@ StorePathSet toDerivations(ref store, return drvPaths; } +InstallablesCommand::InstallablesCommand() +{ + expectArgs({ + .label = "installables", + .handler = {&_installables}, + }); +} + void InstallablesCommand::prepare() { if (_installables.empty() && useDefaultInstallables()) diff --git a/src/nix/ls.cc b/src/nix/ls.cc index aac082422..dc7e370b9 100644 --- a/src/nix/ls.cc +++ b/src/nix/ls.cc @@ -85,7 +85,11 @@ struct CmdLsStore : StoreCommand, MixLs { CmdLsStore() { - expectPathArg("path", &path); + expectArgs({ + .label = "path", + .handler = {&path}, + .completer = completePath + }); } Examples examples() override @@ -117,7 +121,11 @@ struct CmdLsNar : Command, MixLs CmdLsNar() { - expectPathArg("nar", &narPath); + expectArgs({ + .label = "nar", + .handler = {&narPath}, + .completer = completePath + }); expectArg("path", &path); } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 2e7b14d08..c936f9cc2 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -767,7 +767,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs CmdRepl() { - expectPathArgs("files", &files); + expectArgs({ + .label = "files", + .handler = {&files}, + .completer = completePath + }); } std::string description() override diff --git a/src/nix/run.cc b/src/nix/run.cc index 3701ffe3d..f9b1298f1 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -149,7 +149,11 @@ struct CmdRun : InstallableCommand, RunCommon CmdRun() { - expectPathArgs("args", &args); + expectArgs({ + .label = "args", + .handler = {&args}, + .completer = completePath + }); } std::string description() override From da310fac622612b90f8e511b85b8594b56ec8cdf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 21:37:53 +0200 Subject: [PATCH 05/11] Bash completion: Don't break on ':' --- misc/bash/completion.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh index 93298c369..bc184edd6 100644 --- a/misc/bash/completion.sh +++ b/misc/bash/completion.sh @@ -1,4 +1,7 @@ function _complete_nix { + local -a words + local cword cur + _get_comp_words_by_ref -n ':=&' words cword cur local have_type while IFS= read -r line; do if [[ -z $have_type ]]; then @@ -9,7 +12,8 @@ function _complete_nix { else COMPREPLY+=("$line") fi - done < <(NIX_GET_COMPLETIONS=$COMP_CWORD "${COMP_WORDS[@]}") + done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}") + __ltrim_colon_completions "$cur" } complete -F _complete_nix nix From e917332d6396fe9808519e7a87916f789b71392a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 21:38:17 +0200 Subject: [PATCH 06/11] Shut up warnings while running completers --- src/libutil/args.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index c389090ae..ee0c99c2a 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -41,6 +41,7 @@ void Args::parseCmdline(const Strings & _cmdline) assert(n > 0 && n <= cmdline.size()); *std::next(cmdline.begin(), n - 1) += completionMarker; completions = std::make_shared(); + verbosity = lvlError; } for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { From 259ff74bdeeaac5a987a4eb88654ba80d9553543 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 21:49:02 +0200 Subject: [PATCH 07/11] Add completion for installables This completes flakerefs using the registry (e.g. 'nix' => 'nix nixpkgs') and flake output attributes by evaluating the flake (e.g. 'dwarffs#nix' => 'dwarffs#nixosModules'). --- src/nix/command.hh | 7 ++- src/nix/installables.cc | 100 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/nix/command.hh b/src/nix/command.hh index 0738c0f91..6a6c3fed9 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -63,6 +63,8 @@ struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions virtual Strings getDefaultFlakeAttrPaths(); virtual Strings getDefaultFlakeAttrPathPrefixes(); + + void completeInstallable(std::string_view prefix); }; enum RealiseMode { Build, NoBuild, DryRun }; @@ -89,10 +91,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand { std::shared_ptr installable; - InstallableCommand() - { - expectArg("installable", &_installable, true); - } + InstallableCommand(); void prepare() override; diff --git a/src/nix/installables.cc b/src/nix/installables.cc index abc642e11..ade8f83f9 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -11,6 +11,7 @@ #include "flake/flake.hh" #include "eval-cache.hh" #include "url.hh" +#include "registry.hh" #include #include @@ -106,6 +107,91 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() }; } +void SourceExprCommand::completeInstallable(std::string_view prefix) +{ + completePath(0, prefix); + + if (file) return; // FIXME + + /* Look for flake output attributes that match the + prefix. */ + try { + auto hash = prefix.find('#'); + if (hash != std::string::npos) { + auto fragment = prefix.substr(hash + 1); + auto flakeRefS = std::string(prefix.substr(0, hash)); + // FIXME: do tilde expansion. + auto flakeRef = parseFlakeRef(flakeRefS, absPath(".")); + + auto state = getEvalState(); + + auto evalCache = openEvalCache(*state, + std::make_shared(lockFlake(*state, flakeRef, lockFlags)), + true); + + auto root = evalCache->getRoot(); + + /* Complete 'fragment' relative to all the + attrpath prefixes as well as the root of the + flake. */ + auto attrPathPrefixes = getDefaultFlakeAttrPathPrefixes(); + attrPathPrefixes.push_back(""); + + for (auto & attrPathPrefixS : attrPathPrefixes) { + auto attrPathPrefix = parseAttrPath(*state, attrPathPrefixS); + auto attrPathS = attrPathPrefixS + std::string(fragment); + auto attrPath = parseAttrPath(*state, attrPathS); + + std::string lastAttr; + if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { + lastAttr = attrPath.back(); + attrPath.pop_back(); + } + + auto attr = root->findAlongAttrPath(attrPath); + if (!attr) continue; + + auto attrs = attr->getAttrs(); + for (auto & attr2 : attrs) { + if (hasPrefix(attr2, lastAttr)) { + auto attrPath2 = attr->getAttrPath(attr2); + /* Strip the attrpath prefix. */ + attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); + completions->insert(flakeRefS + "#" + concatStringsSep(".", attrPath2)); + } + } + } + + /* And add an empty completion for the default + attrpaths. */ + if (fragment.empty()) { + for (auto & attrPath : getDefaultFlakeAttrPaths()) { + auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath)); + if (!attr) continue; + completions->insert(flakeRefS + "#"); + } + } + } + } catch (Error & e) { + warn(e.msg()); + } + + /* Look for registry entries that match the prefix. */ + for (auto & registry : fetchers::getRegistries(getStore())) { + for (auto & entry : registry->entries) { + auto from = entry.from->to_string(); + if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) { + std::string from2(from, 6); + if (hasPrefix(from2, prefix)) + completions->insert(from2); + } else { + if (hasPrefix(from, prefix)) + completions->insert(from); + } + } + } +} + ref EvalCommand::getEvalState() { if (!evalState) @@ -576,6 +662,9 @@ InstallablesCommand::InstallablesCommand() expectArgs({ .label = "installables", .handler = {&_installables}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} }); } @@ -588,6 +677,17 @@ void InstallablesCommand::prepare() installables = parseInstallables(getStore(), _installables); } +InstallableCommand::InstallableCommand() +{ + expectArgs({ + .label = "installable", + .handler = {&_installable}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); +} + void InstallableCommand::prepare() { installable = parseInstallable(getStore(), _installable); From 27d34ef770356f86823ca832f278e72bb0a07982 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 22:04:13 +0200 Subject: [PATCH 08/11] When completing flakerefs, only return directories --- src/libutil/args.cc | 14 ++++++++++++-- src/libutil/args.hh | 2 ++ src/nix/installables.cc | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ee0c99c2a..f6740e076 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -220,17 +220,27 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -void completePath(size_t, std::string_view prefix) +static void completePath(std::string_view prefix, int flags) { pathCompletions = true; glob_t globbuf; - if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE | flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) completions->insert(globbuf.gl_pathv[i]); globfree(&globbuf); } } +void completePath(size_t, std::string_view prefix) +{ + completePath(prefix, 0); +} + +void completeDir(size_t, std::string_view prefix) +{ + completePath(prefix, GLOB_ONLYDIR); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 250244162..405ec3d47 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -277,4 +277,6 @@ std::optional needsCompletion(std::string_view s); void completePath(size_t, std::string_view prefix); +void completeDir(size_t, std::string_view prefix); + } diff --git a/src/nix/installables.cc b/src/nix/installables.cc index ade8f83f9..5ba48d69a 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -109,7 +109,7 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() void SourceExprCommand::completeInstallable(std::string_view prefix) { - completePath(0, prefix); + completeDir(0, prefix); if (file) return; // FIXME From 649c2db308f16ce4b2cbefe4a8760577541cfb47 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 22:10:33 +0200 Subject: [PATCH 09/11] nix flake: Add completion support --- src/nix/command.hh | 2 ++ src/nix/flake.cc | 9 ++++++++- src/nix/installables.cc | 26 +++++++++++++++++--------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/nix/command.hh b/src/nix/command.hh index 6a6c3fed9..faa19c8ea 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -38,6 +38,8 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs ref getEvalState(); std::shared_ptr evalState; + + void completeFlakeRef(std::string_view prefix); }; struct MixFlakeOptions : virtual Args diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 6eee781aa..b6cc7eb54 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -28,7 +28,14 @@ public: FlakeCommand() { - expectArg("flake-url", &flakeUrl, true); + expectArgs({ + .label = "flake-url", + .optional = true, + .handler = {&flakeUrl}, + .completer = {[&](size_t, std::string_view prefix) { + completeFlakeRef(prefix); + }} + }); } FlakeRef getFlakeRef() diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 5ba48d69a..2d23b33b7 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -109,8 +109,6 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() void SourceExprCommand::completeInstallable(std::string_view prefix) { - completeDir(0, prefix); - if (file) return; // FIXME /* Look for flake output attributes that match the @@ -176,6 +174,23 @@ void SourceExprCommand::completeInstallable(std::string_view prefix) warn(e.msg()); } + completeFlakeRef(prefix); +} + +ref EvalCommand::getEvalState() +{ + if (!evalState) + evalState = std::make_shared(searchPath, getStore()); + return ref(evalState); +} + +void EvalCommand::completeFlakeRef(std::string_view prefix) +{ + if (prefix == "") + completions->insert("."); + + completeDir(0, prefix); + /* Look for registry entries that match the prefix. */ for (auto & registry : fetchers::getRegistries(getStore())) { for (auto & entry : registry->entries) { @@ -192,13 +207,6 @@ void SourceExprCommand::completeInstallable(std::string_view prefix) } } -ref EvalCommand::getEvalState() -{ - if (!evalState) - evalState = std::make_shared(searchPath, getStore()); - return ref(evalState); -} - Buildable Installable::toBuildable() { auto buildables = toBuildables(); From 437614b479dd30200dcdc1950f8d4fdddfef7a61 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 May 2020 11:08:59 +0200 Subject: [PATCH 10/11] Fix macOS build macOS doesn't have GLOB_ONLYDIR. --- src/libutil/args.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f6740e076..8667bd450 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -220,25 +220,35 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -static void completePath(std::string_view prefix, int flags) +static void completePath(std::string_view prefix, bool onlyDirs) { pathCompletions = true; glob_t globbuf; - if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE | flags, nullptr, &globbuf) == 0) { - for (size_t i = 0; i < globbuf.gl_pathc; ++i) + int flags = GLOB_NOESCAPE | GLOB_TILDE; + #ifdef GLOB_ONLYDIR + if (onlyDirs) + flags |= GLOB_ONLYDIR; + #endif + if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) { + if (onlyDirs) { + auto st = lstat(globbuf.gl_pathv[i]); + if (!S_ISDIR(st.st_mode)) continue; + } completions->insert(globbuf.gl_pathv[i]); + } globfree(&globbuf); } } void completePath(size_t, std::string_view prefix) { - completePath(prefix, 0); + completePath(prefix, false); } void completeDir(size_t, std::string_view prefix) { - completePath(prefix, GLOB_ONLYDIR); + completePath(prefix, true); } Strings argvToStrings(int argc, char * * argv) From b8b2dbf27204bc6ce5f57fa5fc5c76f9265fcee1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 May 2020 11:53:32 +0200 Subject: [PATCH 11/11] Fix InstallableCommand --- src/nix/installables.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 2d23b33b7..5385484df 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -689,6 +689,7 @@ InstallableCommand::InstallableCommand() { expectArgs({ .label = "installable", + .optional = true, .handler = {&_installable}, .completer = {[&](size_t, std::string_view prefix) { completeInstallable(prefix);