Merge pull request #3587 from NixOS/bash-completion

Generic shell completion support for the 'nix' command
This commit is contained in:
Eelco Dolstra 2020-05-12 18:26:13 +02:00 committed by GitHub
commit 215f09d765
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 411 additions and 91 deletions

View file

@ -11,6 +11,7 @@ makefiles = \
src/resolve-system-dependencies/local.mk \ src/resolve-system-dependencies/local.mk \
scripts/local.mk \ scripts/local.mk \
corepkgs/local.mk \ corepkgs/local.mk \
misc/bash/local.mk \
misc/systemd/local.mk \ misc/systemd/local.mk \
misc/launchd/local.mk \ misc/launchd/local.mk \
misc/upstart/local.mk \ misc/upstart/local.mk \

19
misc/bash/completion.sh Normal file
View file

@ -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

1
misc/bash/local.mk Normal file
View file

@ -0,0 +1 @@
$(eval $(call install-file-as, $(d)/completion.sh, $(datarootdir)/bash-completion/completions/_nix3, 0644))

View file

@ -155,7 +155,7 @@ void overrideRegistry(
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store) static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
{ {
static auto reg = [&]() { static auto reg = [&]() {
auto path = settings.flakeRegistry; auto path = settings.flakeRegistry.get();
if (!hasPrefix(path, "/")) { if (!hasPrefix(path, "/")) {
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath; auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;

View file

@ -33,9 +33,19 @@ MixCommonArgs::MixCommonArgs(const string & programName)
try { try {
globalConfig.set(name, value); globalConfig.set(name, value);
} catch (UsageError & e) { } catch (UsageError & e) {
if (!completions)
warn(e.what()); warn(e.what());
} }
}}, }},
.completer = [](size_t index, std::string_view prefix) {
if (index == 0) {
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
for (auto & s : settings)
if (hasPrefix(s.first, prefix))
completions->insert(s.first);
}
}
}); });
addFlag({ addFlag({

View file

@ -1,6 +1,8 @@
#include "args.hh" #include "args.hh"
#include "hash.hh" #include "hash.hh"
#include <glob.h>
namespace nix { namespace nix {
void Args::addFlag(Flag && flag_) void Args::addFlag(Flag && flag_)
@ -13,6 +15,20 @@ void Args::addFlag(Flag && flag_)
if (flag->shortName) shortFlags[flag->shortName] = flag; if (flag->shortName) shortFlags[flag->shortName] = flag;
} }
bool pathCompletions = false;
std::shared_ptr<std::set<std::string>> completions;
std::string completionMarker = "___COMPLETE___";
std::optional<std::string> 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) void Args::parseCmdline(const Strings & _cmdline)
{ {
Strings pendingArgs; Strings pendingArgs;
@ -20,6 +36,14 @@ void Args::parseCmdline(const Strings & _cmdline)
Strings cmdline(_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<decltype(completions)::element_type>();
verbosity = lvlError;
}
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
auto arg = *pos; auto arg = *pos;
@ -63,7 +87,7 @@ void Args::printHelp(const string & programName, std::ostream & out)
for (auto & exp : expectedArgs) { for (auto & exp : expectedArgs) {
std::cout << renderLabels({exp.label}); std::cout << renderLabels({exp.label});
// FIXME: handle arity > 1 // FIXME: handle arity > 1
if (exp.arity == 0) std::cout << "..."; if (exp.handler.arity == ArityAny) std::cout << "...";
if (exp.optional) std::cout << "?"; if (exp.optional) std::cout << "?";
} }
std::cout << "\n"; std::cout << "\n";
@ -104,6 +128,9 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (flag.handler.arity == ArityAny) break; if (flag.handler.arity == ArityAny) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); 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++); args.push_back(*pos++);
} }
flag.handler.fun(std::move(args)); 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 (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)); auto i = longFlags.find(string(*pos, 2));
if (i == longFlags.end()) return false; if (i == longFlags.end()) return false;
return process("--" + i->first, *i->second); 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); 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; return false;
} }
@ -138,12 +180,17 @@ bool Args::processArgs(const Strings & args, bool finish)
bool res = false; bool res = false;
if ((exp.arity == 0 && finish) || if ((exp.handler.arity == ArityAny && finish) ||
(exp.arity > 0 && args.size() == exp.arity)) (exp.handler.arity != ArityAny && args.size() == exp.handler.arity))
{ {
std::vector<std::string> ss; std::vector<std::string> ss;
for (auto & s : args) ss.push_back(s); for (const auto &[n, s] : enumerate(args)) {
exp.handler(std::move(ss)); ss.push_back(s);
if (exp.completer)
if (auto prefix = needsCompletion(s))
exp.completer(n, *prefix);
}
exp.handler.fun(ss);
expectedArgs.pop_front(); expectedArgs.pop_front();
res = true; res = true;
} }
@ -164,10 +211,46 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
*ht = parseHashType(s); *ht = parseHashType(s);
if (*ht == htUnknown) if (*ht == htUnknown)
throw UsageError("unknown hash type '%1%'", s); 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 argvToStrings(int argc, char * * argv)
{ {
Strings args; Strings args;
@ -215,13 +298,22 @@ void Command::printHelp(const string & programName, std::ostream & out)
MultiCommand::MultiCommand(const Commands & commands) MultiCommand::MultiCommand(const Commands & commands)
: commands(commands) : commands(commands)
{ {
expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) { expectArgs({
.label = "command",
.optional = true,
.handler = {[=](std::string s) {
assert(!command); assert(!command);
auto i = commands.find(ss[0]); 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()) if (i == commands.end())
throw UsageError("'%s' is not a recognised command", ss[0]); throw UsageError("'%s' is not a recognised command", s);
command = {ss[0], i->second()}; command = {s, i->second()};
}}); }}
});
categories[Command::catDefault] = "Available commands"; categories[Command::catDefault] = "Available commands";
} }

View file

@ -28,11 +28,6 @@ protected:
static const size_t ArityAny = std::numeric_limits<size_t>::max(); static const size_t ArityAny = std::numeric_limits<size_t>::max();
/* Flags. */
struct Flag
{
typedef std::shared_ptr<Flag> ptr;
struct Handler struct Handler
{ {
std::function<void(std::vector<std::string>)> fun; std::function<void(std::vector<std::string>)> fun;
@ -64,6 +59,11 @@ protected:
, arity(2) , arity(2)
{ } { }
Handler(std::vector<std::string> * dest)
: fun([=](std::vector<std::string> ss) { *dest = ss; })
, arity(ArityAny)
{ }
template<class T> template<class T>
Handler(T * dest) Handler(T * dest)
: fun([=](std::vector<std::string> ss) { *dest = ss[0]; }) : fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
@ -77,12 +77,18 @@ protected:
{ } { }
}; };
/* Flags. */
struct Flag
{
typedef std::shared_ptr<Flag> ptr;
std::string longName; std::string longName;
char shortName = 0; char shortName = 0;
std::string description; std::string description;
std::string category; std::string category;
Strings labels; Strings labels;
Handler handler; Handler handler;
std::function<void(size_t, std::string_view)> completer;
static Flag mkHashTypeFlag(std::string && longName, HashType * ht); static Flag mkHashTypeFlag(std::string && longName, HashType * ht);
}; };
@ -98,9 +104,9 @@ protected:
struct ExpectedArg struct ExpectedArg
{ {
std::string label; std::string label;
size_t arity; // 0 = any bool optional = false;
bool optional; Handler handler;
std::function<void(std::vector<std::string>)> handler; std::function<void(size_t, std::string_view)> completer;
}; };
std::list<ExpectedArg> expectedArgs; std::list<ExpectedArg> expectedArgs;
@ -174,20 +180,28 @@ public:
}); });
} }
void expectArgs(ExpectedArg && arg)
{
expectedArgs.emplace_back(std::move(arg));
}
/* Expect a string argument. */ /* Expect a string argument. */
void expectArg(const std::string & label, string * dest, bool optional = false) void expectArg(const std::string & label, string * dest, bool optional = false)
{ {
expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](std::vector<std::string> ss) { expectArgs({
*dest = ss[0]; .label = label,
}}); .optional = true,
.handler = {dest}
});
} }
/* Expect 0 or more arguments. */ /* Expect 0 or more arguments. */
void expectArgs(const std::string & label, std::vector<std::string> * dest) void expectArgs(const std::string & label, std::vector<std::string> * dest)
{ {
expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector<std::string> ss) { expectArgs({
*dest = std::move(ss); .label = label,
}}); .handler = {dest}
});
} }
friend class MultiCommand; friend class MultiCommand;
@ -256,4 +270,13 @@ typedef std::vector<std::pair<std::string, std::string>> Table2;
void printTable(std::ostream & out, const Table2 & table); void printTable(std::ostream & out, const Table2 & table);
extern std::shared_ptr<std::set<std::string>> completions;
extern bool pathCompletions;
std::optional<std::string> needsCompletion(std::string_view s);
void completePath(size_t, std::string_view prefix);
void completeDir(size_t, std::string_view prefix);
} }

View file

@ -16,6 +16,9 @@
namespace nix { namespace nix {
std::set<std::string> hashTypes = { "md5", "sha1", "sha256", "sha512" };
void Hash::init() void Hash::init()
{ {
if (type == htMD5) hashSize = md5HashSize; if (type == htMD5) hashSize = md5HashSize;

View file

@ -18,6 +18,8 @@ const int sha1HashSize = 20;
const int sha256HashSize = 32; const int sha256HashSize = 32;
const int sha512HashSize = 64; const int sha512HashSize = 64;
extern std::set<std::string> hashTypes;
extern const string base32Chars; extern const string base32Chars;
enum Base : int { Base64, Base32, Base16, SRI }; enum Base : int { Base64, Base32, Base16, SRI };

View file

@ -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; return s.compare(0, prefix.size(), prefix) == 0;
} }

View file

@ -431,7 +431,7 @@ template<class N> bool string2Float(const string & s, N & n)
/* Return true iff `s' starts with `prefix'. */ /* 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'. */ /* Return true iff `s' ends in `suffix'. */

View file

@ -18,6 +18,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
.description = "path of the symlink to the build result", .description = "path of the symlink to the build result",
.labels = {"path"}, .labels = {"path"},
.handler = {&outLink}, .handler = {&outLink},
.completer = completePath
}); });
addFlag({ addFlag({

View file

@ -25,7 +25,11 @@ struct CmdCatStore : StoreCommand, MixCat
{ {
CmdCatStore() CmdCatStore()
{ {
expectArg("path", &path); expectArgs({
.label = "path",
.handler = {&path},
.completer = completePath
});
} }
std::string description() override std::string description() override
@ -47,7 +51,11 @@ struct CmdCatNar : StoreCommand, MixCat
CmdCatNar() CmdCatNar()
{ {
expectArg("nar", &narPath); expectArgs({
.label = "nar",
.handler = {&narPath},
.completer = completePath
});
expectArg("path", &path); expectArg("path", &path);
} }

View file

@ -108,6 +108,7 @@ MixProfile::MixProfile()
.description = "profile to update", .description = "profile to update",
.labels = {"path"}, .labels = {"path"},
.handler = {&profile}, .handler = {&profile},
.completer = completePath
}); });
} }

View file

@ -38,6 +38,8 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs
ref<EvalState> getEvalState(); ref<EvalState> getEvalState();
std::shared_ptr<EvalState> evalState; std::shared_ptr<EvalState> evalState;
void completeFlakeRef(std::string_view prefix);
}; };
struct MixFlakeOptions : virtual Args struct MixFlakeOptions : virtual Args
@ -63,6 +65,8 @@ struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
virtual Strings getDefaultFlakeAttrPaths(); virtual Strings getDefaultFlakeAttrPaths();
virtual Strings getDefaultFlakeAttrPathPrefixes(); virtual Strings getDefaultFlakeAttrPathPrefixes();
void completeInstallable(std::string_view prefix);
}; };
enum RealiseMode { Build, NoBuild, DryRun }; enum RealiseMode { Build, NoBuild, DryRun };
@ -73,10 +77,7 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
{ {
std::vector<std::shared_ptr<Installable>> installables; std::vector<std::shared_ptr<Installable>> installables;
InstallablesCommand() InstallablesCommand();
{
expectArgs("installables", &_installables);
}
void prepare() override; void prepare() override;
@ -92,10 +93,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
{ {
std::shared_ptr<Installable> installable; std::shared_ptr<Installable> installable;
InstallableCommand() InstallableCommand();
{
expectArg("installable", &_installable, true);
}
void prepare() override; void prepare() override;

View file

@ -28,7 +28,14 @@ public:
FlakeCommand() 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() FlakeRef getFlakeRef()

View file

@ -31,7 +31,11 @@ struct CmdHash : Command
.labels({"modulus"}) .labels({"modulus"})
.dest(&modulus); .dest(&modulus);
#endif #endif
expectArgs("paths", &paths); expectArgs({
.label = "paths",
.handler = {&paths},
.completer = completePath
});
} }
std::string description() override std::string description() override

View file

@ -11,6 +11,7 @@
#include "flake/flake.hh" #include "flake/flake.hh"
#include "eval-cache.hh" #include "eval-cache.hh"
#include "url.hh" #include "url.hh"
#include "registry.hh"
#include <regex> #include <regex>
#include <queue> #include <queue>
@ -77,7 +78,8 @@ SourceExprCommand::SourceExprCommand()
.shortName = 'f', .shortName = 'f',
.description = "evaluate FILE rather than the default", .description = "evaluate FILE rather than the default",
.labels = {"file"}, .labels = {"file"},
.handler = {&file} .handler = {&file},
.completer = completePath
}); });
addFlag({ 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<flake::LockedFlake>(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<EvalState> EvalCommand::getEvalState() ref<EvalState> EvalCommand::getEvalState()
{ {
if (!evalState) if (!evalState)
@ -112,6 +184,29 @@ ref<EvalState> EvalCommand::getEvalState()
return ref<EvalState>(evalState); return ref<EvalState>(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() Buildable Installable::toBuildable()
{ {
auto buildables = toBuildables(); auto buildables = toBuildables();
@ -551,6 +646,17 @@ StorePathSet toDerivations(ref<Store> store,
return drvPaths; return drvPaths;
} }
InstallablesCommand::InstallablesCommand()
{
expectArgs({
.label = "installables",
.handler = {&_installables},
.completer = {[&](size_t, std::string_view prefix) {
completeInstallable(prefix);
}}
});
}
void InstallablesCommand::prepare() void InstallablesCommand::prepare()
{ {
if (_installables.empty() && useDefaultInstallables()) if (_installables.empty() && useDefaultInstallables())
@ -560,6 +666,18 @@ void InstallablesCommand::prepare()
installables = parseInstallables(getStore(), _installables); 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() void InstallableCommand::prepare()
{ {
installable = parseInstallable(getStore(), _installable); installable = parseInstallable(getStore(), _installable);

View file

@ -85,7 +85,11 @@ struct CmdLsStore : StoreCommand, MixLs
{ {
CmdLsStore() CmdLsStore()
{ {
expectArg("path", &path); expectArgs({
.label = "path",
.handler = {&path},
.completer = completePath
});
} }
Examples examples() override Examples examples() override
@ -117,7 +121,11 @@ struct CmdLsNar : Command, MixLs
CmdLsNar() CmdLsNar()
{ {
expectArg("nar", &narPath); expectArgs({
.label = "nar",
.handler = {&narPath},
.completer = completePath
});
expectArg("path", &path); expectArg("path", &path);
} }

View file

@ -68,7 +68,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({ addFlag({
.longName = "help", .longName = "help",
.description = "show usage information", .description = "show usage information",
.handler = {[&]() { showHelpAndExit(); }}, .handler = {[&]() { if (!completions) showHelpAndExit(); }},
}); });
addFlag({ addFlag({
@ -96,7 +96,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({ addFlag({
.longName = "version", .longName = "version",
.description = "show version information", .description = "show version information",
.handler = {[&]() { printVersion(programName); }}, .handler = {[&]() { if (!completions) printVersion(programName); }},
}); });
addFlag({ addFlag({
@ -166,7 +166,22 @@ void mainWrapped(int argc, char * * argv)
NixArgs args; NixArgs args;
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)); args.parseCmdline(argvToStrings(argc, argv));
} catch (UsageError &) {
if (!completions) throw;
}
if (completions) return;
settings.requireExperimentalFeature("nix-command"); settings.requireExperimentalFeature("nix-command");

View file

@ -767,7 +767,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs
CmdRepl() CmdRepl()
{ {
expectArgs("files", &files); expectArgs({
.label = "files",
.handler = {&files},
.completer = completePath
});
} }
std::string description() override std::string description() override

View file

@ -149,7 +149,11 @@ struct CmdRun : InstallableCommand, RunCommon
CmdRun() CmdRun()
{ {
expectArgs("args", &args); expectArgs({
.label = "args",
.handler = {&args},
.completer = completePath
});
} }
std::string description() override std::string description() override

View file

@ -105,7 +105,8 @@ struct CmdSignPaths : StorePathsCommand
.shortName = 'k', .shortName = 'k',
.description = "file containing the secret signing key", .description = "file containing the secret signing key",
.labels = {"file"}, .labels = {"file"},
.handler = {&secretKeyFile} .handler = {&secretKeyFile},
.completer = completePath
}); });
} }