Skip to content

Commit

Permalink
nixd/Controller: support select in nested with expression
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Jul 21, 2024
1 parent 334da32 commit a1395af
Show file tree
Hide file tree
Showing 18 changed files with 272 additions and 119 deletions.
95 changes: 95 additions & 0 deletions nixd/lib/Controller/AST.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#include "AST.h"

#include "nixd/Protocol/AttrSet.h"

#include <unordered_set>

using namespace nixf;

namespace {
Expand Down Expand Up @@ -130,6 +134,97 @@ bool nixd::havePackageScope(const Node &N, const VariableLookupAnalysis &VLA,
return false;
}

// Idioms.
namespace {

using IdiomSetT = std::unordered_set<std::string_view>;

IdiomSetT IdiomSet{nixd::idioms::Pkgs, nixd::idioms::Lib};

auto ItLib = IdiomSet.find(nixd::idioms::Lib);
auto ItPkgs = IdiomSet.find(nixd::idioms::Pkgs);

nixd::Selector idiomItSelector(IdiomSetT::iterator It) {
// Unknown name, cannot deal with it.
if (It == IdiomSet.end())
throw nixd::NotAnIdiomException();

return [&]() -> nixd::Selector {
if (It == ItLib) {
return {std::string(nixd::idioms::Lib)};
}
if (It == ItPkgs) {
return {};
}
assert(false && "Unhandled idiom iterator?");
__builtin_unreachable();
return {};
}();
}

IdiomSetT::iterator varIdiomIt(const nixf::ExprVar &Var) {
return IdiomSet.find(Var.id().name());
};

nixd::Selector varSelector(const nixf::ExprVar &Var) {
return idiomItSelector(varIdiomIt(Var));
};

nixd::Selector withSelector(const nixf::ExprWith &With) {
if (!With.with() || With.with()->kind() != Node::NK_ExprVar)
throw nixd::NotAnIdiomException();
return varSelector(static_cast<const nixf::ExprVar &>(*With.with()));
}

} // namespace

nixd::Selector nixd::mkIdiomSelector(const nixf::ExprVar &Var,
const nixf::VariableLookupAnalysis &VLA,
const nixf::ParentMapAnalysis &PM) {
// Only check if the variable can be recogonized by some idiom.

using ResultKind = VariableLookupAnalysis::LookupResultKind;
auto Result = VLA.query(Var);
switch (Result.Kind) {
case ResultKind::Undefined:
case ResultKind::Defined:
return varSelector(Var);
case ResultKind::FromWith: {
assert(Result.Def && "FromWith variables should contains definition");
const nixf::Definition &Def = *Result.Def;
if (!Def.syntax())
throw NotAnIdiomException();

// The syntax
//
// with pkgs; with lib; [ ]
//
// does provide both "pkgs" + "lib" scopes.
//
// However, in current implementation we will only consider nested "with".
// That is, only "lib" variables will be considered.
const nixf::Node &Syntax = *Def.syntax();
const nixf::Node *With = PM.query(Syntax);
assert(With && "parent of kwWith should be the with expression");
assert(With->kind() == nixf::Node::NK_ExprWith);
Selector WithSelector =
withSelector(static_cast<const nixf::ExprWith &>(*With));

// Append variable name after "with" expression selector.
// e.g.
//
// with pkgs; [ fo ]
// ^
// The result will be {pkgs, fo}
WithSelector.emplace_back(Var.id().name());

return WithSelector;
}
case ResultKind::NoSuchVar:
throw NoSuchVarException();
}
}

nixd::Selector nixd::mkSelector(const nixf::AttrPath &AP,
Selector BaseSelector) {
const auto &Names = AP.names();
Expand Down
35 changes: 34 additions & 1 deletion nixd/lib/Controller/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <nixf/Sema/ParentMap.h>
#include <nixf/Sema/VariableLookup.h>

#include <exception>

namespace nixd {

namespace idioms {
Expand Down Expand Up @@ -47,9 +49,40 @@ upEnv(const nixf::Node &Desc, const nixf::VariableLookupAnalysis &VLA,
std::pair<std::vector<std::string>, std::string>
getScopeAndPrefix(const nixf::Node &N, const nixf::ParentMapAnalysis &PM);

/// \brief Exceptions scoped in nixd::mkIdiomSelector
struct IdiomSelectorException : std::exception {};

/// \brief The pattern of this variable cannot be recognized by known idioms.
struct NotAnIdiomException : IdiomSelectorException {
[[nodiscard]] const char *what() const noexcept override {
return "not an idiom";
}
};

/// \brief No such variable.
struct NoSuchVarException : IdiomSelectorException {
[[nodiscard]] const char *what() const noexcept override {
return "no such variable";
}
};

/// \brief Construct a nixd::Selector from \p Var.
///
/// Try to heuristically find a selector of a variable, based on some known
/// idioms.
Selector mkIdiomSelector(const nixf::ExprVar &Var,
const nixf::VariableLookupAnalysis &VLA,
const nixf::ParentMapAnalysis &PM);

struct SelectorException : std::exception {};

/// \brief The attrpath has a dynamic name, thus it cannot be trivially
/// transformed to "static" selector.
struct DynamicNameException : std::exception {};
struct DynamicNameException : SelectorException {
[[nodiscard]] const char *what() const noexcept override {
return "dynamic attribute path encountered";
}
};

/// \brief Construct a nixd::Selector from \p AP.
Selector mkSelector(const nixf::AttrPath &AP, Selector BaseSelector);
Expand Down
93 changes: 51 additions & 42 deletions nixd/lib/Controller/Completion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

#include "nixd/Controller/Controller.h"

#include <nixf/Sema/VariableLookup.h>

#include <boost/asio/post.hpp>

#include <semaphore>
Expand Down Expand Up @@ -78,11 +80,9 @@ class VLACompletionProvider {
VLACompletionProvider(const VariableLookupAnalysis &VLA) : VLA(VLA) {}

/// Perform code completion right after this node.
void complete(const nixf::Node &Desc, std::vector<CompletionItem> &Items,
void complete(const nixf::ExprVar &Desc, std::vector<CompletionItem> &Items,
const ParentMapAnalysis &PM) {
std::string Prefix; // empty string, accept all prefixes
if (Desc.kind() == Node::NK_Identifer)
Prefix = static_cast<const Identifier &>(Desc).name();
std::string Prefix = Desc.id().name();
collectDef(Items, upEnv(Desc, VLA, PM), Prefix);
}
};
Expand Down Expand Up @@ -283,20 +283,6 @@ void completeAttrPath(const Node &N, const ParentMapAnalysis &PM,
}
}

void completeVarName(const VariableLookupAnalysis &VLA,
const ParentMapAnalysis &PM, const nixf::Node &N,
AttrSetClient &Client, std::vector<CompletionItem> &List) {
VLACompletionProvider VLAP(VLA);
VLAP.complete(N, List, PM);
if (havePackageScope(N, VLA, PM)) {
// Append it with nixpkgs completion
// FIXME: handle null nixpkgsClient()
NixpkgsCompletionProvider NCP(Client);
auto [Scope, Prefix] = getScopeAndPrefix(N, PM);
NCP.completePackages({Scope, Prefix}, List);
}
}

AttrPathCompleteParams mkParams(nixd::Selector Sel, bool IsComplete) {
if (IsComplete) {
return {
Expand All @@ -312,14 +298,43 @@ AttrPathCompleteParams mkParams(nixd::Selector Sel, bool IsComplete) {
};
}

#define DBG DBGPREFIX ": "

void completeVarName(const VariableLookupAnalysis &VLA,
const ParentMapAnalysis &PM, const nixf::ExprVar &N,
AttrSetClient &Client, std::vector<CompletionItem> &List) {
#define DBGPREFIX "completion/var"

VLACompletionProvider VLAP(VLA);
VLAP.complete(N, List, PM);

// Try to complete the name by known idioms.
try {
Selector Sel = mkIdiomSelector(N, VLA, PM);
NixpkgsCompletionProvider NCP(Client);

// Invoke nixpkgs provider to get the completion list.
// Variable names are always incomplete.
NCP.completePackages(mkParams(Sel, /*IsComplete=*/false), List);
} catch (IdiomSelectorException &E) {
log(DBG "skipped, reason: {0}", E.what());
return;
}

#undef DBGPREFIX
}

/// \brief Complete a "select" expression.
/// \param IsComplete Whether or not the last element of the selector is
/// effectively incomplete.
/// e.g.
/// - incomplete: `lib.gen|`
/// - complete: `lib.attrset.|`
void completeSelect(const nixf::ExprSelect &Select, AttrSetClient &Client,
bool IsComplete, std::vector<CompletionItem> &List) {
const nixf::VariableLookupAnalysis &VLA,
const nixf::ParentMapAnalysis &PM, bool IsComplete,
std::vector<CompletionItem> &List) {
#define DBGPREFIX "completion/select"
// The base expr for selecting.
const nixf::Expr &BaseExpr = Select.expr();

Expand All @@ -331,30 +346,24 @@ void completeSelect(const nixf::ExprSelect &Select, AttrSetClient &Client,
}

const auto &Var = static_cast<const nixf::ExprVar &>(BaseExpr);

// See if the variable matches some idioms name we alreay know.
std::unordered_set<std::string_view> S{idioms::Pkgs, idioms::Lib};
auto It = S.find(Var.id().name());

// Unknown name, cannot deal with it.
if (It == S.end())
return;

// Ask nixpkgs provider to get idioms completion.
NixpkgsCompletionProvider NCP(Client);

// Construct the scope & prefix suitable for this "select".
Selector BaseSelector = It == S.find(idioms::Lib)
? Selector{std::string(idioms::Lib)}
: Selector{};
const auto Handler = [](std::exception &E) noexcept {
log(DBG "skipped, reason: {0}", E.what());
};
try {
Selector Sel = mkSelector(Select, std::move(BaseSelector));
Selector Sel = mkSelector(Select, mkIdiomSelector(Var, VLA, PM));
NCP.completePackages(mkParams(Sel, IsComplete), List);
} catch (DynamicNameException &DE) {
lspserver::log(
"completion/select: skip because attribute path contains dynamic attr");
} catch (IdiomSelectorException &E) {
Handler(E);
return;
} catch (SelectorException &E) {
Handler(E);
return;
}

#undef DBGPREFIX
}

} // namespace
Expand Down Expand Up @@ -388,14 +397,14 @@ void Controller::onCompletion(const CompletionParams &Params,
const Node &UpExpr = *MaybeUpExpr;
Reply([&]() -> CompletionList {
CompletionList List;
const VariableLookupAnalysis &VLA = *TU->variableLookup();
try {
switch (UpExpr.kind()) {
// In these cases, assume the cursor have "variable" scoping.
case Node::NK_ExprWith:
case Node::NK_ExprList:
case Node::NK_ExprVar: {
const VariableLookupAnalysis &VLA = *TU->variableLookup();
completeVarName(VLA, PM, N, *nixpkgsClient(), List.items);
completeVarName(VLA, PM,
static_cast<const nixf::ExprVar &>(UpExpr),
*nixpkgsClient(), List.items);
break;
}
// A "select" expression. e.g.
Expand All @@ -405,8 +414,8 @@ void Controller::onCompletion(const CompletionParams &Params,
case Node::NK_ExprSelect: {
const auto &Select =
static_cast<const nixf::ExprSelect &>(UpExpr);
completeSelect(Select, *nixpkgsClient(), N.kind() == Node::NK_Dot,
List.items);
completeSelect(Select, *nixpkgsClient(), VLA, PM,
N.kind() == Node::NK_Dot, List.items);
break;
}
case Node::NK_ExprAttrs: {
Expand Down
74 changes: 0 additions & 74 deletions nixd/tools/nixd/test/completion-nixpkgs.md

This file was deleted.

Loading

0 comments on commit a1395af

Please sign in to comment.