mirror of
https://github.com/NixOS/nix
synced 2025-06-28 13:41:15 +02:00
As I complained in https://github.com/NixOS/nix/pull/6784#issuecomment-1421777030 (a comment on the wrong PR, sorry again!), #6693 introduced a second completions mechanism to fix a bug. Having two completion mechanisms isn't so nice. As @thufschmitt also pointed out, it was a bummer to go from `FlakeRef` to `std::string` when collecting flake refs. Now it is `FlakeRefs` again. The underlying issue that sought to work around was that completion of arguments not at the end can still benefit from the information from latter arguments. To fix this better, we rip out that change and simply defer all completion processing until after all the (regular, already-complete) arguments have been passed. In addition, I noticed the original completion logic used some global variables. I do not like global variables, because even if they save lines of code, they also obfuscate the architecture of the code. I got rid of them moved them to a new `RootArgs` class, which now has `parseCmdline` instead of `Args`. The idea is that we have many argument parsers from subcommands and what-not, but only one root args that owns the other per actual parsing invocation. The state that was global is now part of the root args instead. This did, admittedly, add a bunch of new code. And I do feel bad about that. So I went and added a lot of API docs to try to at least make the current state of things clear to the next person. -- This is needed for RFC 134 (tracking issue #7868). It was very hard to modularize `Installable` parsing when there were two completion arguments. I wouldn't go as far as to say it is *easy* now, but at least it is less hard (and the completions test finally passed). Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
This commit is contained in:
parent
955bbe53c5
commit
b461cac21a
14 changed files with 329 additions and 181 deletions
|
@ -1,4 +1,5 @@
|
|||
#include "args.hh"
|
||||
#include "args/root.hh"
|
||||
#include "hash.hh"
|
||||
#include "json-utils.hh"
|
||||
|
||||
|
@ -26,6 +27,11 @@ void Args::removeFlag(const std::string & longName)
|
|||
longFlags.erase(flag);
|
||||
}
|
||||
|
||||
void Completions::setType(AddCompletions::Type t)
|
||||
{
|
||||
type = t;
|
||||
}
|
||||
|
||||
void Completions::add(std::string completion, std::string description)
|
||||
{
|
||||
description = trim(description);
|
||||
|
@ -37,7 +43,7 @@ void Completions::add(std::string completion, std::string description)
|
|||
if (needs_ellipsis)
|
||||
description.append(" [...]");
|
||||
}
|
||||
insert(Completion {
|
||||
completions.insert(Completion {
|
||||
.completion = completion,
|
||||
.description = description
|
||||
});
|
||||
|
@ -46,12 +52,20 @@ void Completions::add(std::string completion, std::string description)
|
|||
bool Completion::operator<(const Completion & other) const
|
||||
{ return completion < other.completion || (completion == other.completion && description < other.description); }
|
||||
|
||||
CompletionType completionType = ctNormal;
|
||||
std::shared_ptr<Completions> completions;
|
||||
|
||||
std::string completionMarker = "___COMPLETE___";
|
||||
|
||||
static std::optional<std::string> needsCompletion(std::string_view s)
|
||||
RootArgs & Args::getRoot()
|
||||
{
|
||||
Args * p = this;
|
||||
while (p->parent)
|
||||
p = p->parent;
|
||||
|
||||
auto * res = dynamic_cast<RootArgs *>(p);
|
||||
assert(res);
|
||||
return *res;
|
||||
}
|
||||
|
||||
std::optional<std::string> RootArgs::needsCompletion(std::string_view s)
|
||||
{
|
||||
if (!completions) return {};
|
||||
auto i = s.find(completionMarker);
|
||||
|
@ -60,7 +74,7 @@ static std::optional<std::string> needsCompletion(std::string_view s)
|
|||
return {};
|
||||
}
|
||||
|
||||
void Args::parseCmdline(const Strings & _cmdline)
|
||||
void RootArgs::parseCmdline(const Strings & _cmdline)
|
||||
{
|
||||
Strings pendingArgs;
|
||||
bool dashDash = false;
|
||||
|
@ -71,7 +85,7 @@ void Args::parseCmdline(const Strings & _cmdline)
|
|||
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>();
|
||||
completions = std::make_shared<Completions>();
|
||||
verbosity = lvlError;
|
||||
}
|
||||
|
||||
|
@ -125,17 +139,23 @@ void Args::parseCmdline(const Strings & _cmdline)
|
|||
for (auto & f : flagExperimentalFeatures)
|
||||
experimentalFeatureSettings.require(f);
|
||||
|
||||
/* Now that all the other args are processed, run the deferred completions.
|
||||
*/
|
||||
for (auto d : deferredCompletions)
|
||||
d.completer(*completions, d.n, d.prefix);
|
||||
}
|
||||
|
||||
bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
||||
{
|
||||
assert(pos != end);
|
||||
|
||||
auto & rootArgs = getRoot();
|
||||
|
||||
auto process = [&](const std::string & name, const Flag & flag) -> bool {
|
||||
++pos;
|
||||
|
||||
if (auto & f = flag.experimentalFeature)
|
||||
flagExperimentalFeatures.insert(*f);
|
||||
rootArgs.flagExperimentalFeatures.insert(*f);
|
||||
|
||||
std::vector<std::string> args;
|
||||
bool anyCompleted = false;
|
||||
|
@ -146,10 +166,15 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
|||
"flag '%s' requires %d argument(s), but only %d were given",
|
||||
name, flag.handler.arity, n);
|
||||
}
|
||||
if (auto prefix = needsCompletion(*pos)) {
|
||||
if (auto prefix = rootArgs.needsCompletion(*pos)) {
|
||||
anyCompleted = true;
|
||||
if (flag.completer)
|
||||
flag.completer(n, *prefix);
|
||||
if (flag.completer) {
|
||||
rootArgs.deferredCompletions.push_back({
|
||||
.completer = flag.completer,
|
||||
.n = n,
|
||||
.prefix = *prefix,
|
||||
});
|
||||
}
|
||||
}
|
||||
args.push_back(*pos++);
|
||||
}
|
||||
|
@ -159,14 +184,14 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
|||
};
|
||||
|
||||
if (std::string(*pos, 0, 2) == "--") {
|
||||
if (auto prefix = needsCompletion(*pos)) {
|
||||
if (auto prefix = rootArgs.needsCompletion(*pos)) {
|
||||
for (auto & [name, flag] : longFlags) {
|
||||
if (!hiddenCategories.count(flag->category)
|
||||
&& hasPrefix(name, std::string(*prefix, 2)))
|
||||
{
|
||||
if (auto & f = flag->experimentalFeature)
|
||||
flagExperimentalFeatures.insert(*f);
|
||||
completions->add("--" + name, flag->description);
|
||||
rootArgs.flagExperimentalFeatures.insert(*f);
|
||||
rootArgs.completions->add("--" + name, flag->description);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -183,12 +208,12 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
|||
return process(std::string("-") + c, *i->second);
|
||||
}
|
||||
|
||||
if (auto prefix = needsCompletion(*pos)) {
|
||||
if (auto prefix = rootArgs.needsCompletion(*pos)) {
|
||||
if (prefix == "-") {
|
||||
completions->add("--");
|
||||
rootArgs.completions->add("--");
|
||||
for (auto & [flagName, flag] : shortFlags)
|
||||
if (experimentalFeatureSettings.isEnabled(flag->experimentalFeature))
|
||||
completions->add(std::string("-") + flagName, flag->description);
|
||||
rootArgs.completions->add(std::string("-") + flagName, flag->description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,6 +228,8 @@ bool Args::processArgs(const Strings & args, bool finish)
|
|||
return true;
|
||||
}
|
||||
|
||||
auto & rootArgs = getRoot();
|
||||
|
||||
auto & exp = expectedArgs.front();
|
||||
|
||||
bool res = false;
|
||||
|
@ -211,15 +238,23 @@ bool Args::processArgs(const Strings & args, bool finish)
|
|||
(exp.handler.arity != ArityAny && args.size() == exp.handler.arity))
|
||||
{
|
||||
std::vector<std::string> ss;
|
||||
bool anyCompleted = false;
|
||||
for (const auto &[n, s] : enumerate(args)) {
|
||||
if (auto prefix = needsCompletion(s)) {
|
||||
if (auto prefix = rootArgs.needsCompletion(s)) {
|
||||
anyCompleted = true;
|
||||
ss.push_back(*prefix);
|
||||
if (exp.completer)
|
||||
exp.completer(n, *prefix);
|
||||
if (exp.completer) {
|
||||
rootArgs.deferredCompletions.push_back({
|
||||
.completer = exp.completer,
|
||||
.n = n,
|
||||
.prefix = *prefix,
|
||||
});
|
||||
}
|
||||
} else
|
||||
ss.push_back(s);
|
||||
}
|
||||
exp.handler.fun(ss);
|
||||
if (!anyCompleted)
|
||||
exp.handler.fun(ss);
|
||||
expectedArgs.pop_front();
|
||||
res = true;
|
||||
}
|
||||
|
@ -271,11 +306,11 @@ nlohmann::json Args::toJSON()
|
|||
return res;
|
||||
}
|
||||
|
||||
static void hashTypeCompleter(size_t index, std::string_view prefix)
|
||||
static void hashTypeCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
|
||||
{
|
||||
for (auto & type : hashTypes)
|
||||
if (hasPrefix(type, prefix))
|
||||
completions->add(type);
|
||||
completions.add(type);
|
||||
}
|
||||
|
||||
Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
|
||||
|
@ -287,7 +322,7 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
|
|||
.handler = {[ht](std::string s) {
|
||||
*ht = parseHashType(s);
|
||||
}},
|
||||
.completer = hashTypeCompleter
|
||||
.completer = hashTypeCompleter,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -300,13 +335,13 @@ Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional<
|
|||
.handler = {[oht](std::string s) {
|
||||
*oht = std::optional<HashType> { parseHashType(s) };
|
||||
}},
|
||||
.completer = hashTypeCompleter
|
||||
.completer = hashTypeCompleter,
|
||||
};
|
||||
}
|
||||
|
||||
static void _completePath(std::string_view prefix, bool onlyDirs)
|
||||
static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs)
|
||||
{
|
||||
completionType = ctFilenames;
|
||||
completions.setType(Completions::Type::Filenames);
|
||||
glob_t globbuf;
|
||||
int flags = GLOB_NOESCAPE;
|
||||
#ifdef GLOB_ONLYDIR
|
||||
|
@ -320,20 +355,20 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
|
|||
auto st = stat(globbuf.gl_pathv[i]);
|
||||
if (!S_ISDIR(st.st_mode)) continue;
|
||||
}
|
||||
completions->add(globbuf.gl_pathv[i]);
|
||||
completions.add(globbuf.gl_pathv[i]);
|
||||
}
|
||||
}
|
||||
globfree(&globbuf);
|
||||
}
|
||||
|
||||
void completePath(size_t, std::string_view prefix)
|
||||
void Args::completePath(AddCompletions & completions, size_t, std::string_view prefix)
|
||||
{
|
||||
_completePath(prefix, false);
|
||||
_completePath(completions, prefix, false);
|
||||
}
|
||||
|
||||
void completeDir(size_t, std::string_view prefix)
|
||||
void Args::completeDir(AddCompletions & completions, size_t, std::string_view prefix)
|
||||
{
|
||||
_completePath(prefix, true);
|
||||
_completePath(completions, prefix, true);
|
||||
}
|
||||
|
||||
Strings argvToStrings(int argc, char * * argv)
|
||||
|
@ -368,10 +403,10 @@ MultiCommand::MultiCommand(const Commands & commands_)
|
|||
command = {s, i->second()};
|
||||
command->second->parent = this;
|
||||
}},
|
||||
.completer = {[&](size_t, std::string_view prefix) {
|
||||
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
||||
for (auto & [name, command] : commands)
|
||||
if (hasPrefix(name, prefix))
|
||||
completions->add(name);
|
||||
completions.add(name);
|
||||
}}
|
||||
});
|
||||
|
||||
|
@ -393,14 +428,6 @@ 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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue