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..bc184edd6 --- /dev/null +++ b/misc/bash/completion.sh @@ -0,0 +1,19 @@ +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 + have_type=1 + if [[ $line = filenames ]]; then + compopt -o filenames + fi + else + COMPREPLY+=("$line") + fi + done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}") + __ltrim_colon_completions "$cur" +} + +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/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 51e199ea5..51a61f1ca 100644 --- a/src/libmain/common-args.cc +++ b/src/libmain/common-args.cc @@ -33,9 +33,19 @@ MixCommonArgs::MixCommonArgs(const string & programName) try { globalConfig.set(name, value); } catch (UsageError & e) { - warn(e.what()); + 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 f829415d1..8667bd450 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,20 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +bool pathCompletions = false; +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 +36,14 @@ 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(); + verbosity = lvlError; + } + for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { auto arg = *pos; @@ -63,7 +87,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"; @@ -104,6 +128,9 @@ 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 (flag.completer) + if (auto prefix = needsCompletion(*pos)) + flag.completer(n, *prefix); args.push_back(*pos++); } flag.handler.fun(std::move(args)); @@ -111,6 +138,13 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) }; 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; } @@ -138,12 +180,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; } @@ -164,10 +211,46 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) *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); + } }; } +static void completePath(std::string_view prefix, bool onlyDirs) +{ + pathCompletions = true; + glob_t globbuf; + 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, false); +} + +void completeDir(size_t, std::string_view prefix) +{ + completePath(prefix, true); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; @@ -215,13 +298,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); - 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 1932e6a8a..405ec3d47 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -28,61 +28,67 @@ 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; std::string category; Strings labels; Handler handler; + std::function completer; static Flag mkHashTypeFlag(std::string && longName, HashType * ht); }; @@ -98,9 +104,9 @@ protected: struct ExpectedArg { std::string label; - size_t arity; // 0 = any - bool optional; - std::function)> handler; + bool optional = false; + Handler handler; + std::function completer; }; std::list expectedArgs; @@ -174,20 +180,28 @@ 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} + }); } /* 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} + }); } friend class MultiCommand; @@ -256,4 +270,13 @@ 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 prefix); + +void completeDir(size_t, std::string_view prefix); + } 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/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'. */ 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..b528a0507 100644 --- a/src/nix/cat.cc +++ b/src/nix/cat.cc @@ -25,7 +25,11 @@ struct CmdCatStore : StoreCommand, MixCat { CmdCatStore() { - expectArg("path", &path); + expectArgs({ + .label = "path", + .handler = {&path}, + .completer = completePath + }); } std::string description() override @@ -47,7 +51,11 @@ struct CmdCatNar : StoreCommand, MixCat CmdCatNar() { - expectArg("nar", &narPath); + expectArgs({ + .label = "nar", + .handler = {&narPath}, + .completer = completePath + }); 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/command.hh b/src/nix/command.hh index 6b4781303..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 @@ -63,6 +65,8 @@ struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions virtual Strings getDefaultFlakeAttrPaths(); virtual Strings getDefaultFlakeAttrPathPrefixes(); + + void completeInstallable(std::string_view prefix); }; enum RealiseMode { Build, NoBuild, DryRun }; @@ -73,10 +77,7 @@ struct InstallablesCommand : virtual Args, SourceExprCommand { std::vector> installables; - InstallablesCommand() - { - expectArgs("installables", &_installables); - } + InstallablesCommand(); void prepare() override; @@ -92,10 +93,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand { std::shared_ptr installable; - InstallableCommand() - { - expectArg("installable", &_installable, true); - } + InstallableCommand(); void prepare() override; 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/hash.cc b/src/nix/hash.cc index 366314227..d5636eb47 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -31,7 +31,11 @@ struct CmdHash : Command .labels({"modulus"}) .dest(&modulus); #endif - expectArgs("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 a06022f8c..cae85b34e 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 @@ -77,7 +78,8 @@ SourceExprCommand::SourceExprCommand() .shortName = 'f', .description = "evaluate FILE rather than the default", .labels = {"file"}, - .handler = {&file} + .handler = {&file}, + .completer = completePath }); addFlag({ @@ -105,6 +107,76 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() }; } +void SourceExprCommand::completeInstallable(std::string_view 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()); + } + + completeFlakeRef(prefix); +} + ref EvalCommand::getEvalState() { if (!evalState) @@ -112,6 +184,29 @@ ref EvalCommand::getEvalState() 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) { + 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); + } + } + } +} + Buildable Installable::toBuildable() { auto buildables = toBuildables(); @@ -551,6 +646,17 @@ StorePathSet toDerivations(ref store, return drvPaths; } +InstallablesCommand::InstallablesCommand() +{ + expectArgs({ + .label = "installables", + .handler = {&_installables}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); +} + void InstallablesCommand::prepare() { if (_installables.empty() && useDefaultInstallables()) @@ -560,6 +666,18 @@ void InstallablesCommand::prepare() installables = parseInstallables(getStore(), _installables); } +InstallableCommand::InstallableCommand() +{ + expectArgs({ + .label = "installable", + .optional = true, + .handler = {&_installable}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); +} + void InstallableCommand::prepare() { installable = parseInstallable(getStore(), _installable); diff --git a/src/nix/ls.cc b/src/nix/ls.cc index b9716a6a1..dc7e370b9 100644 --- a/src/nix/ls.cc +++ b/src/nix/ls.cc @@ -85,7 +85,11 @@ struct CmdLsStore : StoreCommand, MixLs { CmdLsStore() { - expectArg("path", &path); + expectArgs({ + .label = "path", + .handler = {&path}, + .completer = completePath + }); } Examples examples() override @@ -117,7 +121,11 @@ struct CmdLsNar : Command, MixLs CmdLsNar() { - expectArg("nar", &narPath); + expectArgs({ + .label = "nar", + .handler = {&narPath}, + .completer = completePath + }); expectArg("path", &path); } diff --git a/src/nix/main.cc b/src/nix/main.cc index 3915a4896..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({ @@ -166,7 +166,22 @@ void mainWrapped(int argc, char * * argv) NixArgs args; - args.parseCmdline(argvToStrings(argc, argv)); + Finally printCompletions([&]() + { + if (completions) { + std::cout << (pathCompletions ? "filenames\n" : "no-filenames\n"); + 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"); diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 0a6a7ab19..c936f9cc2 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -767,7 +767,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs CmdRepl() { - expectArgs("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 3e2c8b4f3..f9b1298f1 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -149,7 +149,11 @@ struct CmdRun : InstallableCommand, RunCommon CmdRun() { - expectArgs("args", &args); + expectArgs({ + .label = "args", + .handler = {&args}, + .completer = completePath + }); } 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 }); }