aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2020-05-12 18:26:13 +0200
committerGitHub <noreply@github.com>2020-05-12 18:26:13 +0200
commit215f09d7656b394f8bcf45c9aa3a4c2b0287469d (patch)
tree777d0d22fe0b66882efb0784b2e95b7eb9c3d145
parentfbade0b7cc123f9e952115e14456f77fdd4fedf1 (diff)
parentb8b2dbf27204bc6ce5f57fa5fc5c76f9265fcee1 (diff)
Merge pull request #3587 from NixOS/bash-completion
Generic shell completion support for the 'nix' command
-rw-r--r--Makefile1
-rw-r--r--misc/bash/completion.sh19
-rw-r--r--misc/bash/local.mk1
-rw-r--r--src/libfetchers/registry.cc2
-rw-r--r--src/libmain/common-args.cc12
-rw-r--r--src/libutil/args.cc118
-rw-r--r--src/libutil/args.hh129
-rw-r--r--src/libutil/hash.cc3
-rw-r--r--src/libutil/hash.hh2
-rw-r--r--src/libutil/util.cc2
-rw-r--r--src/libutil/util.hh2
-rw-r--r--src/nix/build.cc1
-rw-r--r--src/nix/cat.cc12
-rw-r--r--src/nix/command.cc1
-rw-r--r--src/nix/command.hh14
-rw-r--r--src/nix/flake.cc9
-rw-r--r--src/nix/hash.cc6
-rw-r--r--src/nix/installables.cc120
-rw-r--r--src/nix/ls.cc12
-rw-r--r--src/nix/main.cc21
-rw-r--r--src/nix/repl.cc6
-rw-r--r--src/nix/run.cc6
-rw-r--r--src/nix/sigs.cc3
23 files changed, 411 insertions, 91 deletions
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<Registry> getGlobalRegistry(ref<Store> 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<std::string, Config::SettingInfo> 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 <glob.h>
+
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<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)
{
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<decltype(completions)::element_type>();
+ 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<std::string> 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<std::string> 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<size_t>::max();
+ struct Handler
+ {
+ std::function<void(std::vector<std::string>)> fun;
+ size_t arity;
+
+ Handler() {}
+
+ Handler(std::function<void(std::vector<std::string>)> && fun)
+ : fun(std::move(fun))
+ , arity(ArityAny)
+ { }
+
+ Handler(std::function<void()> && handler)
+ : fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); })
+ , arity(0)
+ { }
+
+ Handler(std::function<void(std::string)> && handler)
+ : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
+ handler(std::move(ss[0]));
+ })
+ , arity(1)
+ { }
+
+ Handler(std::function<void(std::string, std::string)> && handler)
+ : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
+ handler(std::move(ss[0]), std::move(ss[1]));
+ })
+ , arity(2)
+ { }
+
+ Handler(std::vector<std::string> * dest)
+ : fun([=](std::vector<std::string> ss) { *dest = ss; })
+ , arity(ArityAny)
+ { }
+
+ template<class T>
+ Handler(T * dest)
+ : fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
+ , arity(1)
+ { }
+
+ template<class T>
+ Handler(T * dest, const T & val)
+ : fun([=](std::vector<std::string> ss) { *dest = val; })
+ , arity(0)
+ { }
+ };
+
/* Flags. */
struct Flag
{
typedef std::shared_ptr<Flag> ptr;
- struct Handler
- {
- std::function<void(std::vector<std::string>)> fun;
- size_t arity;
-
- Handler() {}
-
- Handler(std::function<void(std::vector<std::string>)> && fun)
- : fun(std::move(fun))
- , arity(ArityAny)
- { }
-
- Handler(std::function<void()> && handler)
- : fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); })
- , arity(0)
- { }
-
- Handler(std::function<void(std::string)> && handler)
- : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
- handler(std::move(ss[0]));
- })
- , arity(1)
- { }
-
- Handler(std::function<void(std::string, std::string)> && handler)
- : fun([handler{std::move(handler)}](std::vector<std::string> ss) {
- handler(std::move(ss[0]), std::move(ss[1]));
- })
- , arity(2)
- { }
-
- template<class T>
- Handler(T * dest)
- : fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
- , arity(1)
- { }
-
- template<class T>
- Handler(T * dest, const T & val)
- : fun([=](std::vector<std::string> ss) { *dest = val; })
- , arity(0)
- { }
- };
-
std::string longName;
char shortName = 0;
std::string description;
std::string category;
Strings labels;
Handler handler;
+ std::function<void(size_t, std::string_view)> 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<void(std::vector<std::string>)> handler;
+ bool optional = false;
+ Handler handler;
+ std::function<void(size_t, std::string_view)> completer;
};
std::list<ExpectedArg> 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<std::string> ss) {
- *dest = ss[0];
- }});
+ expectArgs({
+ .label = label,
+ .optional = true,
+ .handler = {dest}
+ });
}
/* Expect 0 or more arguments. */
void expectArgs(const std::string & label, std::vector<std::string> * dest)
{
- expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector<std::string> ss) {
- *dest = std::move(ss);
- }});
+ expectArgs({
+ .label = label,
+ .handler = {dest}
+ });
}
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);
+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);
+
}
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<std::string> 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<std::string> 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<class N> 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<EvalState> getEvalState();
std::shared_ptr<EvalState> 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<std::shared_ptr<Installable>> installables;
- InstallablesCommand()
- {
- expectArgs("installables", &_installables);
- }
+ InstallablesCommand();
void prepare() override;
@@ -92,10 +93,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
{
std::shared_ptr<Installable> 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 <regex>
#include <queue>
@@ -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<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()
{
if (!evalState)
@@ -112,6 +184,29 @@ ref<EvalState> EvalCommand::getEvalState()
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()
{
auto buildables = toBuildables();
@@ -551,6 +646,17 @@ StorePathSet toDerivations(ref<Store> 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
});
}