forked from lix-project/lix
Merge pull request #6128 from ncfavier/fix-completion
Shell completion improvements
This commit is contained in:
commit
51712bf012
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
Loading…
Reference in a new issue