17#include <boost/asio/post.hpp>
33constexpr int MaxCompletionSize = 30;
38struct ExceedSizeError : std::exception {
39 [[nodiscard]]
const char *what() const noexcept
override {
40 return "Size exceeded";
44void addItem(std::vector<CompletionItem> &Items,
CompletionItem Item) {
45 if (Items.size() >= MaxCompletionSize) {
46 throw ExceedSizeError();
48 Items.emplace_back(std::move(Item));
51bool hasConcreteChild(
const Node &N) {
59bool canCompleteAt(
const Node &N) {
60 if (!hasConcreteChild(N))
62 return N.
kind() == Node::NK_Binds || N.
kind() == Node::NK_ExprAttrs;
65class VLACompletionProvider {
66 const VariableLookupAnalysis &VLA;
70 return CompletionItemKind::Keyword;
72 return CompletionItemKind::Variable;
76 void collectDef(std::vector<CompletionItem> &Items,
const EnvNode *Env,
77 const std::string &Prefix) {
80 collectDef(Items, Env->
parent(), Prefix);
81 for (
const auto &[Name, Def] : Env->
defs()) {
86 if (Name.starts_with(Prefix)) {
87 addItem(Items, CompletionItem{
89 .kind = getCompletionItemKind(*Def),
96 VLACompletionProvider(
const VariableLookupAnalysis &VLA) : VLA(VLA) {}
99 void complete(
const nixf::ExprVar &Desc, std::vector<CompletionItem> &Items,
100 const ParentMapAnalysis &PM) {
101 std::string Prefix = Desc.
id().
name();
102 collectDef(Items,
upEnv(Desc, VLA, PM), Prefix);
113class NixpkgsCompletionProvider {
115 AttrSetClient &NixpkgsClient;
118 NixpkgsCompletionProvider(AttrSetClient &NixpkgsClient)
119 : NixpkgsClient(NixpkgsClient) {}
121 void resolvePackage(std::vector<std::string> Scope, std::string Name,
122 CompletionItem &Item) {
123 std::binary_semaphore Ready(0);
124 AttrPathInfoResponse Desc;
125 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
130 Scope.emplace_back(std::move(Name));
131 NixpkgsClient.attrpathInfo(Scope, std::move(OnReply));
136 .kind = MarkupKind::Markdown,
144 void completePackages(
const AttrPathCompleteParams &Params,
145 std::vector<CompletionItem> &Items) {
146 std::binary_semaphore Ready(0);
147 std::vector<std::string> Names;
148 auto OnReply = [&Ready,
149 &Names](llvm::Expected<AttrPathCompleteResponse> Resp) {
159 NixpkgsClient.attrpathComplete(Params, std::move(OnReply));
162 for (
const auto &Name : Names) {
163 if (Name.starts_with(Params.
Prefix)) {
164 addItem(Items, CompletionItem{
166 .kind = CompletionItemKind::Field,
167 .data = llvm::formatv(
"{0}",
toJSON(Params)),
175class OptionCompletionProvider {
176 AttrSetClient &OptionClient;
179 std::string ModuleOrigin;
182 bool ClientSupportSnippet;
184 static std::string escapeCharacters(
const std::set<char> &Charset,
185 const std::string &Origin) {
188 Ret.reserve(Origin.size());
189 for (
const auto Ch : Origin) {
190 if (Charset.contains(Ch)) {
200 void fillInsertText(CompletionItem &Item,
const std::string &Name,
201 const std::string &
Value)
const {
202 if (!ClientSupportSnippet) {
209 "${1:" + escapeCharacters({
'\\',
'$',
'}'},
Value) +
"}" +
214 OptionCompletionProvider(AttrSetClient &OptionClient,
215 std::string ModuleOrigin,
bool ClientSupportSnippet)
216 : OptionClient(OptionClient), ModuleOrigin(std::move(ModuleOrigin)),
217 ClientSupportSnippet(ClientSupportSnippet) {}
219 void completeOptions(std::vector<std::string> Scope, std::string Prefix,
220 std::vector<CompletionItem> &Items) {
221 std::binary_semaphore Ready(0);
223 auto OnReply = [&Ready,
224 &Names](llvm::Expected<OptionCompleteResponse> Resp) {
234 AttrPathCompleteParams Params{std::move(Scope), std::move(Prefix)};
235 OptionClient.optionComplete(Params, std::move(OnReply));
238 for (
const nixd::OptionField &
Field : Names) {
239 if (!
Field.Description) {
242 Item.
detail = ModuleOrigin;
243 Item.
kind = OptionAttrKind;
244 addItem(Items, std::move(Item));
248 const OptionDescription &Desc = *
Field.Description;
255 std::string TypeDetail = ModuleOrigin +
" | ";
257 std::string TypeName = Desc.
Type->Name.value_or(
"");
258 std::string TypeDesc = Desc.
Type->Description.value_or(
"");
259 TypeDetail += llvm::formatv(
"{0} ({1})", TypeName, TypeDesc);
261 TypeDetail +=
"? (missing type)";
264 .kind = MarkupKind::Markdown,
270 bool HasExample = Desc.
Example.has_value();
275 bool HasBoth = HasExample && HasDefault;
277 auto emit = [&](
const std::string &
Value, llvm::StringRef Source,
278 llvm::StringRef SortPrefix) {
284 Item.
label = llvm::formatv(
"{0} ({1})",
Field.Name, Source);
289 Item.
kind = OptionKind;
293 addItem(Items, std::move(Item));
296 bool Emitted =
false;
298 emit(*Desc.
Example,
"example",
"0");
302 emit(*Desc.
Default,
"default",
"1");
310 Item.
kind = OptionKind;
313 fillInsertText(Item,
Field.Name,
"");
314 addItem(Items, std::move(Item));
320void completeAttrName(
const std::vector<std::string> &Scope,
321 const std::string &Prefix,
323 std::vector<CompletionItem> &List) {
324 for (
const auto &[Name, Provider] : Options) {
326 if (!Client) [[unlikely]] {
327 elog(
"skipped client {0} as it is dead", Name);
330 OptionCompletionProvider OCP(*Client, Name, CompletionSnippets);
331 OCP.completeOptions(Scope, Prefix, List);
338 std::vector<lspserver::CompletionItem> &Items) {
339 std::vector<std::string> Scope;
342 if (R == PathResult::OK) {
344 std::string Prefix = Scope.back();
347 std::lock_guard
_(OptionsLock);
348 completeAttrName(Scope, Prefix, Options, Snippets, Items);
354 if (IsComplete || Sel.empty()) {
356 .Scope = std::move(Sel),
360 std::string Back = std::move(Sel.back());
364 .Prefix = std::move(Back),
368#define DBG DBGPREFIX ": "
373#define DBGPREFIX "completion/var"
375 VLACompletionProvider VLAP(VLA);
376 VLAP.complete(N, List, PM);
387 NixpkgsCompletionProvider NCP(Client);
389 NCP.completePackages(mkParams(Sel,
false), List);
390 }
catch (ExceedSizeError &) {
393 }
catch (std::exception &E) {
394 return log(
DBG "skipped, reason: {0}", E.what());
409 std::vector<CompletionItem> &List) {
410#define DBGPREFIX "completion/select"
417 if (BaseExpr.
kind() != Node::NK_ExprVar) {
421 const auto &Var =
static_cast<const nixf::ExprVar &
>(BaseExpr);
423 NixpkgsCompletionProvider NCP(Client);
428 NCP.completePackages(mkParams(Sel, IsComplete), List);
429 }
catch (ExceedSizeError &) {
432 }
catch (std::exception &E) {
433 return log(
DBG "skipped, reason: {0}", E.what());
443 using CheckTy = CompletionList;
444 auto Action = [Reply = std::move(Reply), URI = Params.
textDocument.
uri,
446 const auto File = URI.file().str();
447 return Reply([&]() -> llvm::Expected<CompletionList> {
451 const auto *Desc = AST->descend({Pos, Pos});
454 const auto &N = *Desc;
455 const auto &PM = *TU->parentMap();
460 const VariableLookupAnalysis &VLA = *TU->variableLookup();
462 switch (UpExpr.kind()) {
464 case Node::NK_ExprVar: {
465 completeVarName(VLA, PM,
static_cast<const nixf::ExprVar &
>(UpExpr),
466 *nixpkgsClient(), List.
items);
473 case Node::NK_ExprSelect: {
474 const auto &Select =
static_cast<const nixf::ExprSelect &
>(UpExpr);
475 completeSelect(Select, *nixpkgsClient(), VLA, PM,
479 case Node::NK_ExprAttrs: {
480 completeAttrPath(N, PM, OptionsLock, Options,
481 ClientCaps.CompletionSnippets, List.
items);
487 }
catch (ExceedSizeError &Err) {
494 boost::asio::post(Pool, std::move(Action));
497void Controller::onCompletionItemResolve(
const CompletionItem &Params,
500 auto Action = [Params, Reply = std::move(Reply),
this]()
mutable {
501 if (Params.
data.empty()) {
505 AttrPathCompleteParams Req;
506 auto EV = llvm::json::parse(Params.
data);
509 Reply(EV.takeError());
513 llvm::json::Path::Root Root;
517 NixpkgsCompletionProvider NCP(*nixpkgsClient());
518 CompletionItem Resp = Params;
519 NCP.resolvePackage(Req.
Scope, Params.
label, Resp);
521 Reply(std::move(Resp));
523 boost::asio::post(Pool, std::move(Action));
This file declares some common analysis (tree walk) on the AST.
Types used in nixpkgs provider.
#define CheckDefault(x)
Variant of CheckReturn, but returns default constructed CheckTy.
Convert between LSP and nixf types.
Lookup variable names, from it's parent scope.
std::map< std::string, std::unique_ptr< AttrSetClientProc > > OptionMapTy
const DefMap & defs() const
const Identifier & id() const
const std::string & name() const
virtual ChildVector children() const =0
const Node * upExpr(const Node &N) const
Search up until the node becomes a concrete expression. a ^<--— ID -> ExprVar.
Whether current platform treats paths case insensitively.
llvm::unique_function< void(llvm::Expected< T >)> Callback
void elog(const char *Fmt, Ts &&...Vals)
CompletionItemKind
The kind of a completion entry.
void log(const char *Fmt, Ts &&...Vals)
Selector mkVarSelector(const nixf::ExprVar &Var, const nixf::VariableLookupAnalysis &VLA, const nixf::ParentMapAnalysis &PM)
Construct a nixd::Selector from Var.
Selector mkSelector(const nixf::AttrPath &AP, Selector BaseSelector)
Construct a nixd::Selector from AP.
bool fromJSON(const llvm::json::Value &Params, Configuration::Diagnostic &R, llvm::json::Path P)
llvm::json::Value toJSON(const PackageDescription &Params)
const nixf::EnvNode * upEnv(const nixf::Node &Desc, const nixf::VariableLookupAnalysis &VLA, const nixf::ParentMapAnalysis &PM)
Search up until there are some node associated with "EnvNode".
std::vector< std::string > Selector
A list of strings that "select"s into a attribute set.
nixf::Position toNixfPosition(const lspserver::Position &P)
FindAttrPathResult findAttrPathForOptions(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, std::vector< std::string > &Path)
Heuristically find attrpath suitable for "attrpath" completion. Strips "config." from the start to su...
std::vector< OptionField > OptionCompleteResponse
InsertTextFormat insertTextFormat
std::optional< MarkupContent > documentation
A human-readable string that represents a doc-comment.
std::vector< CompletionItem > items
The completion items.
URIForFile uri
The text document's URI.
Position position
The position inside the text document.
TextDocumentIdentifier textDocument
The text document.
std::string Prefix
Search for packages prefixed with this "prefix".
PackageDescription PackageDesc
Package description of the attribute path, if available.
std::optional< std::string > Description
std::optional< std::string > Example
std::optional< OptionType > Type
std::optional< std::string > Default
std::optional< std::string > Version
std::optional< std::string > Description
std::optional< std::string > LongDescription