From e0c19ee620c53b52ca7cf69c19d414d782338be1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:35:07 +0200 Subject: [PATCH] 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 }); }