From d6d0e781bbade76f6ea3f310cb36973f4013826d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Sun, 19 Jun 2022 17:54:27 +0200 Subject: [PATCH 1/2] Complete flake inputs for all given flakes Allow `nix build flake1 flake2 --update-input ` to complete the inputs of both flakes. Also do tilde expansion so that `nix build ~/flake --update-input ` works. --- src/libcmd/command.hh | 10 ++++++---- src/libcmd/installables.cc | 34 ++++++++++++++++------------------ src/nix/flake.cc | 4 ++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 8982f21d0..cab379b84 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -79,8 +79,10 @@ struct MixFlakeOptions : virtual Args, EvalCommand MixFlakeOptions(); - virtual std::optional getFlakeRefForCompletion() + virtual std::vector getFlakesForCompletion() { return {}; } + + void completeFlakeInput(std::string_view prefix); }; struct SourceExprCommand : virtual Args, MixFlakeOptions @@ -119,7 +121,7 @@ struct InstallablesCommand : virtual Args, SourceExprCommand virtual bool useDefaultInstallables() { return true; } - std::optional getFlakeRefForCompletion() override; + std::vector getFlakesForCompletion() override; private: @@ -135,9 +137,9 @@ struct InstallableCommand : virtual Args, SourceExprCommand void prepare() override; - std::optional getFlakeRefForCompletion() override + std::vector getFlakesForCompletion() override { - return parseFlakeRefWithFragment(_installable, absPath(".")).first; + return {_installable}; } private: diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index ffc25135e..1bcef4172 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -23,15 +23,16 @@ namespace nix { -void completeFlakeInputPath( - ref evalState, - const FlakeRef & flakeRef, - std::string_view prefix) +void MixFlakeOptions::completeFlakeInput(std::string_view prefix) { - auto flake = flake::getFlake(*evalState, flakeRef, true); - for (auto & input : flake.inputs) - if (hasPrefix(input.first, prefix)) - completions->add(input.first); + auto evalState = getEvalState(); + for (auto & flakeRefS : getFlakesForCompletion()) { + auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first; + auto flake = flake::getFlake(*evalState, flakeRef, true); + for (auto & input : flake.inputs) + if (hasPrefix(input.first, prefix)) + completions->add(input.first); + } } MixFlakeOptions::MixFlakeOptions() @@ -86,8 +87,7 @@ MixFlakeOptions::MixFlakeOptions() lockFlags.inputUpdates.insert(flake::parseInputPath(s)); }}, .completer = {[&](size_t, std::string_view prefix) { - if (auto flakeRef = getFlakeRefForCompletion()) - completeFlakeInputPath(getEvalState(), *flakeRef, prefix); + completeFlakeInput(prefix); }} }); @@ -103,12 +103,10 @@ MixFlakeOptions::MixFlakeOptions() parseFlakeRef(flakeRef, absPath("."), true)); }}, .completer = {[&](size_t n, std::string_view prefix) { - if (n == 0) { - if (auto flakeRef = getFlakeRefForCompletion()) - completeFlakeInputPath(getEvalState(), *flakeRef, prefix); - } else if (n == 1) { + if (n == 0) + completeFlakeInput(prefix); + else if (n == 1) completeFlakeRef(getEvalState()->store, prefix); - } }} }); @@ -1043,14 +1041,14 @@ void InstallablesCommand::prepare() installables = parseInstallables(getStore(), _installables); } -std::optional InstallablesCommand::getFlakeRefForCompletion() +std::vector InstallablesCommand::getFlakesForCompletion() { if (_installables.empty()) { if (useDefaultInstallables()) - return parseFlakeRefWithFragment(".", absPath(".")).first; + return {"."}; return {}; } - return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first; + return _installables; } InstallableCommand::InstallableCommand(bool supportReadOnlyMode) diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 8370b8dcf..439eb53ba 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -50,9 +50,9 @@ public: return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags); } - std::optional getFlakeRefForCompletion() override + std::vector getFlakesForCompletion() override { - return getFlakeRef(); + return {flakeUrl}; } }; From 711b2e1f48316d80853635408c518e3562a1fa37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Mon, 20 Jun 2022 04:15:38 +0200 Subject: [PATCH 2/2] Fix flake input completion for `InstallablesCommand`s Defers completion of flake inputs until the whole command line is parsed so that we know what flakes we need to complete the inputs of. Previously, `nix build flake --update-input ` always behaved like `nix build . --update-input `. --- src/libcmd/command.hh | 4 ++++ src/libcmd/installables.cc | 34 ++++++++++++++++++++-------------- src/libutil/args.cc | 10 +++++++++- src/libutil/args.hh | 7 +++++++ src/nix/main.cc | 5 ++++- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index cab379b84..ffa8e784f 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -77,12 +77,16 @@ struct MixFlakeOptions : virtual Args, EvalCommand { flake::LockFlags lockFlags; + std::optional needsFlakeInputCompletion = {}; + MixFlakeOptions(); virtual std::vector getFlakesForCompletion() { return {}; } void completeFlakeInput(std::string_view prefix); + + void completionHook() override; }; struct SourceExprCommand : virtual Args, MixFlakeOptions diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 1bcef4172..81c8dd062 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -23,18 +23,6 @@ namespace nix { -void MixFlakeOptions::completeFlakeInput(std::string_view prefix) -{ - auto evalState = getEvalState(); - for (auto & flakeRefS : getFlakesForCompletion()) { - auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first; - auto flake = flake::getFlake(*evalState, flakeRef, true); - for (auto & input : flake.inputs) - if (hasPrefix(input.first, prefix)) - completions->add(input.first); - } -} - MixFlakeOptions::MixFlakeOptions() { auto category = "Common flake-related options"; @@ -87,7 +75,7 @@ MixFlakeOptions::MixFlakeOptions() lockFlags.inputUpdates.insert(flake::parseInputPath(s)); }}, .completer = {[&](size_t, std::string_view prefix) { - completeFlakeInput(prefix); + needsFlakeInputCompletion = {std::string(prefix)}; }} }); @@ -104,7 +92,7 @@ MixFlakeOptions::MixFlakeOptions() }}, .completer = {[&](size_t n, std::string_view prefix) { if (n == 0) - completeFlakeInput(prefix); + needsFlakeInputCompletion = {std::string(prefix)}; else if (n == 1) completeFlakeRef(getEvalState()->store, prefix); }} @@ -137,6 +125,24 @@ MixFlakeOptions::MixFlakeOptions() }); } +void MixFlakeOptions::completeFlakeInput(std::string_view prefix) +{ + auto evalState = getEvalState(); + for (auto & flakeRefS : getFlakesForCompletion()) { + auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first; + auto flake = flake::getFlake(*evalState, flakeRef, true); + for (auto & input : flake.inputs) + if (hasPrefix(input.first, prefix)) + completions->add(input.first); + } +} + +void MixFlakeOptions::completionHook() +{ + if (auto & prefix = needsFlakeInputCompletion) + completeFlakeInput(*prefix); +} + SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) { addFlag({ diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 4b8c55686..44b63f0f6 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -124,7 +124,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) bool anyCompleted = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { - if (flag.handler.arity == ArityAny) break; + if (flag.handler.arity == ArityAny || anyCompleted) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } if (auto prefix = needsCompletion(*pos)) { @@ -362,6 +362,14 @@ bool MultiCommand::processArgs(const Strings & args, bool finish) return Args::processArgs(args, finish); } +void MultiCommand::completionHook() +{ + if (command) + return command->second->completionHook(); + else + return Args::completionHook(); +} + nlohmann::json MultiCommand::toJSON() { auto cmds = nlohmann::json::object(); diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 07c017719..84866f12b 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -148,6 +148,11 @@ protected: argument (if any) have been processed. */ virtual void initialFlagsProcessed() {} + /* Called after the command line has been processed if we need to generate + completions. Useful for commands that need to know the whole command line + in order to know what completions to generate. */ + virtual void completionHook() { } + public: void addFlag(Flag && flag); @@ -223,6 +228,8 @@ public: bool processArgs(const Strings & args, bool finish) override; + void completionHook() override; + nlohmann::json toJSON() override; }; diff --git a/src/nix/main.cc b/src/nix/main.cc index f398e3118..f6138cbe6 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -342,7 +342,10 @@ void mainWrapped(int argc, char * * argv) if (!completions) throw; } - if (completions) return; + if (completions) { + args.completionHook(); + return; + } if (args.showVersion) { printVersion(programName);