Merge pull request #6128 from ncfavier/fix-completion

Shell completion improvements
This commit is contained in:
Eelco Dolstra 2022-04-19 13:45:33 +02:00 committed by GitHub
commit 51712bf012
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 27 deletions

View file

@ -15,7 +15,7 @@ function _complete_nix {
else else
COMPREPLY+=("$completion") COMPREPLY+=("$completion")
fi fi
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null) done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}" 2>/dev/null)
__ltrim_colon_completions "$cur" __ltrim_colon_completions "$cur"
} }

View file

@ -135,7 +135,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
std::optional<FlakeRef> getFlakeRefForCompletion() override std::optional<FlakeRef> getFlakeRefForCompletion() override
{ {
return parseFlakeRef(_installable, absPath(".")); return parseFlakeRefWithFragment(_installable, absPath(".")).first;
} }
private: private:

View file

@ -7,6 +7,7 @@
#include "registry.hh" #include "registry.hh"
#include "flake/flakeref.hh" #include "flake/flakeref.hh"
#include "store-api.hh" #include "store-api.hh"
#include "command.hh"
namespace nix { namespace nix {
@ -59,6 +60,9 @@ MixEvalArgs::MixEvalArgs()
fetchers::Attrs extraAttrs; fetchers::Attrs extraAttrs;
if (to.subdir != "") extraAttrs["dir"] = to.subdir; if (to.subdir != "") extraAttrs["dir"] = to.subdir;
fetchers::overrideRegistry(from.input, to.input, extraAttrs); fetchers::overrideRegistry(from.input, to.input, extraAttrs);
}},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(openStore(), prefix);
}} }}
}); });

View file

@ -1,5 +1,6 @@
#include "globals.hh" #include "globals.hh"
#include "installables.hh" #include "installables.hh"
#include "util.hh"
#include "command.hh" #include "command.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "common-eval-args.hh" #include "common-eval-args.hh"
@ -100,6 +101,14 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.inputOverrides.insert_or_assign( lockFlags.inputOverrides.insert_or_assign(
flake::parseInputPath(inputPath), flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath("."), true)); 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) {
completeFlakeRef(getEvalState()->store, prefix);
}
}} }}
}); });
@ -194,6 +203,8 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
void SourceExprCommand::completeInstallable(std::string_view prefix) void SourceExprCommand::completeInstallable(std::string_view prefix)
{ {
if (file) { if (file) {
completionType = ctAttrs;
evalSettings.pureEval = false; evalSettings.pureEval = false;
auto state = getEvalState(); auto state = getEvalState();
Expr *e = state->parseExprFromFile( Expr *e = state->parseExprFromFile(
@ -222,13 +233,14 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
Value v2; Value v2;
state->autoCallFunction(*autoArgs, v1, v2); state->autoCallFunction(*autoArgs, v1, v2);
completionType = ctAttrs;
if (v2.type() == nAttrs) { if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) { for (auto & i : *v2.attrs) {
std::string name = i.name; std::string name = i.name;
if (name.find(searchWord) == 0) { if (name.find(searchWord) == 0) {
completions->add(i.name); if (prefix_ == "")
completions->add(name);
else
completions->add(prefix_ + "." + name);
} }
} }
} }
@ -256,10 +268,11 @@ void completeFlakeRefWithFragment(
if (hash == std::string::npos) { if (hash == std::string::npos) {
completeFlakeRef(evalState->store, prefix); completeFlakeRef(evalState->store, prefix);
} else { } else {
completionType = ctAttrs;
auto fragment = prefix.substr(hash + 1); auto fragment = prefix.substr(hash + 1);
auto flakeRefS = std::string(prefix.substr(0, hash)); auto flakeRefS = std::string(prefix.substr(0, hash));
// FIXME: do tilde expansion. auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
auto evalCache = openEvalCache(*evalState, auto evalCache = openEvalCache(*evalState,
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags))); std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
@ -271,8 +284,6 @@ void completeFlakeRefWithFragment(
flake. */ flake. */
attrPathPrefixes.push_back(""); attrPathPrefixes.push_back("");
completionType = ctAttrs;
for (auto & attrPathPrefixS : attrPathPrefixes) { for (auto & attrPathPrefixS : attrPathPrefixes) {
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
auto attrPathS = attrPathPrefixS + std::string(fragment); auto attrPathS = attrPathPrefixS + std::string(fragment);
@ -969,10 +980,10 @@ std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
{ {
if (_installables.empty()) { if (_installables.empty()) {
if (useDefaultInstallables()) if (useDefaultInstallables())
return parseFlakeRef(".", absPath(".")); return parseFlakeRefWithFragment(".", absPath(".")).first;
return {}; return {};
} }
return parseFlakeRef(_installables.front(), absPath(".")); return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
} }
InstallableCommand::InstallableCommand(bool supportReadOnlyMode) InstallableCommand::InstallableCommand(bool supportReadOnlyMode)

View file

@ -127,11 +127,11 @@ 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)) {
if (auto prefix = needsCompletion(*pos)) { anyCompleted = true;
anyCompleted = true; if (flag.completer)
flag.completer(n, *prefix); flag.completer(n, *prefix);
} }
args.push_back(*pos++); args.push_back(*pos++);
} }
if (!anyCompleted) if (!anyCompleted)
@ -146,6 +146,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
&& hasPrefix(name, std::string(*prefix, 2))) && hasPrefix(name, std::string(*prefix, 2)))
completions->add("--" + name, flag->description); completions->add("--" + name, flag->description);
} }
return false;
} }
auto i = longFlags.find(std::string(*pos, 2)); auto i = longFlags.find(std::string(*pos, 2));
if (i == longFlags.end()) return false; if (i == longFlags.end()) return false;
@ -187,10 +188,12 @@ bool Args::processArgs(const Strings & args, bool finish)
{ {
std::vector<std::string> ss; std::vector<std::string> ss;
for (const auto &[n, s] : enumerate(args)) { for (const auto &[n, s] : enumerate(args)) {
ss.push_back(s); if (auto prefix = needsCompletion(s)) {
if (exp.completer) ss.push_back(*prefix);
if (auto prefix = needsCompletion(s)) if (exp.completer)
exp.completer(n, *prefix); exp.completer(n, *prefix);
} else
ss.push_back(s);
} }
exp.handler.fun(ss); exp.handler.fun(ss);
expectedArgs.pop_front(); expectedArgs.pop_front();
@ -279,21 +282,22 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
{ {
completionType = ctFilenames; completionType = ctFilenames;
glob_t globbuf; glob_t globbuf;
int flags = GLOB_NOESCAPE | GLOB_TILDE; int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR #ifdef GLOB_ONLYDIR
if (onlyDirs) if (onlyDirs)
flags |= GLOB_ONLYDIR; flags |= GLOB_ONLYDIR;
#endif #endif
if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { // using expandTilde here instead of GLOB_TILDE(_CHECK) so that ~<Tab> expands to /home/user/
if (glob((expandTilde(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) {
for (size_t i = 0; i < globbuf.gl_pathc; ++i) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) {
if (onlyDirs) { if (onlyDirs) {
auto st = lstat(globbuf.gl_pathv[i]); auto st = stat(globbuf.gl_pathv[i]);
if (!S_ISDIR(st.st_mode)) continue; if (!S_ISDIR(st.st_mode)) continue;
} }
completions->add(globbuf.gl_pathv[i]); completions->add(globbuf.gl_pathv[i]);
} }
globfree(&globbuf);
} }
globfree(&globbuf);
} }
void completePath(size_t, std::string_view prefix) void completePath(size_t, std::string_view prefix)
@ -322,11 +326,6 @@ MultiCommand::MultiCommand(const Commands & commands_)
.optional = true, .optional = true,
.handler = {[=](std::string s) { .handler = {[=](std::string s) {
assert(!command); assert(!command);
if (auto prefix = needsCompletion(s)) {
for (auto & [name, command] : commands)
if (hasPrefix(name, *prefix))
completions->add(name);
}
auto i = commands.find(s); auto i = commands.find(s);
if (i == commands.end()) { if (i == commands.end()) {
std::set<std::string> commandNames; std::set<std::string> commandNames;
@ -337,6 +336,11 @@ MultiCommand::MultiCommand(const Commands & commands_)
} }
command = {s, i->second()}; command = {s, i->second()};
command->second->parent = this; command->second->parent = this;
}},
.completer = {[&](size_t, std::string_view prefix) {
for (auto & [name, command] : commands)
if (hasPrefix(name, prefix))
completions->add(name);
}} }}
}); });

View file

@ -198,6 +198,17 @@ std::string_view baseNameOf(std::string_view path)
} }
std::string expandTilde(std::string_view path)
{
// TODO: expand ~user ?
auto tilde = path.substr(0, 2);
if (tilde == "~/" || tilde == "~")
return getHome() + std::string(path.substr(1));
else
return std::string(path);
}
bool isInDir(std::string_view path, std::string_view dir) bool isInDir(std::string_view path, std::string_view dir)
{ {
return path.substr(0, 1) == "/" return path.substr(0, 1) == "/"
@ -213,6 +224,15 @@ bool isDirOrInDir(std::string_view path, std::string_view dir)
} }
struct stat stat(const Path & path)
{
struct stat st;
if (stat(path.c_str(), &st))
throw SysError("getting status of '%1%'", path);
return st;
}
struct stat lstat(const Path & path) struct stat lstat(const Path & path)
{ {
struct stat st; struct stat st;

View file

@ -68,6 +68,9 @@ Path dirOf(const PathView path);
following the final `/' (trailing slashes are removed). */ following the final `/' (trailing slashes are removed). */
std::string_view baseNameOf(std::string_view path); std::string_view baseNameOf(std::string_view path);
/* Perform tilde expansion on a path. */
std::string expandTilde(std::string_view path);
/* Check whether 'path' is a descendant of 'dir'. Both paths must be /* Check whether 'path' is a descendant of 'dir'. Both paths must be
canonicalized. */ canonicalized. */
bool isInDir(std::string_view path, std::string_view dir); bool isInDir(std::string_view path, std::string_view dir);
@ -77,6 +80,7 @@ bool isInDir(std::string_view path, std::string_view dir);
bool isDirOrInDir(std::string_view path, std::string_view dir); bool isDirOrInDir(std::string_view path, std::string_view dir);
/* Get status of `path'. */ /* Get status of `path'. */
struct stat stat(const Path & path);
struct stat lstat(const Path & path); struct stat lstat(const Path & path);
/* Return true iff the given path exists. */ /* Return true iff the given path exists. */