diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2020-05-12 18:26:13 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-12 18:26:13 +0200 |
commit | 215f09d7656b394f8bcf45c9aa3a4c2b0287469d (patch) | |
tree | 777d0d22fe0b66882efb0784b2e95b7eb9c3d145 /src/nix | |
parent | fbade0b7cc123f9e952115e14456f77fdd4fedf1 (diff) | |
parent | b8b2dbf27204bc6ce5f57fa5fc5c76f9265fcee1 (diff) |
Merge pull request #3587 from NixOS/bash-completion
Generic shell completion support for the 'nix' command
Diffstat (limited to 'src/nix')
-rw-r--r-- | src/nix/build.cc | 1 | ||||
-rw-r--r-- | src/nix/cat.cc | 12 | ||||
-rw-r--r-- | src/nix/command.cc | 1 | ||||
-rw-r--r-- | src/nix/command.hh | 14 | ||||
-rw-r--r-- | src/nix/flake.cc | 9 | ||||
-rw-r--r-- | src/nix/hash.cc | 6 | ||||
-rw-r--r-- | src/nix/installables.cc | 120 | ||||
-rw-r--r-- | src/nix/ls.cc | 12 | ||||
-rw-r--r-- | src/nix/main.cc | 21 | ||||
-rw-r--r-- | src/nix/repl.cc | 6 | ||||
-rw-r--r-- | src/nix/run.cc | 6 | ||||
-rw-r--r-- | src/nix/sigs.cc | 3 |
12 files changed, 190 insertions, 21 deletions
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 }); } |