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 lspserver::Range EditRange,
145 const AttrPathCompleteParams &Params,
146 std::vector<CompletionItem> &Items) {
147 std::binary_semaphore Ready(0);
148 std::vector<std::string> Names;
149 auto OnReply = [&Ready,
150 &Names](llvm::Expected<AttrPathCompleteResponse> Resp) {
160 NixpkgsClient.attrpathComplete(Params, std::move(OnReply));
163 for (
const auto &Name : Names) {
164 if (Name.starts_with(Params.
Prefix)) {
165 addItem(Items, CompletionItem{
167 .kind = CompletionItemKind::Field,
168 .textEdit = lspserver::TextEdit{.range = EditRange,
170 .data = llvm::formatv(
"{0}",
toJSON(Params)),
178class OptionCompletionProvider {
179 AttrSetClient &OptionClient;
182 std::string ModuleOrigin;
185 bool ClientSupportSnippet;
187 static std::string escapeCharacters(
const std::set<char> &Charset,
188 const std::string &Origin) {
191 Ret.reserve(Origin.size());
192 for (
const auto Ch : Origin) {
193 if (Charset.contains(Ch)) {
203 void fillInsertText(CompletionItem &Item,
const std::string &Name,
204 const std::string &
Value)
const {
205 if (!ClientSupportSnippet) {
212 "${1:" + escapeCharacters({
'\\',
'$',
'}'},
Value) +
"}" +
217 OptionCompletionProvider(AttrSetClient &OptionClient,
218 std::string ModuleOrigin,
bool ClientSupportSnippet)
219 : OptionClient(OptionClient), ModuleOrigin(std::move(ModuleOrigin)),
220 ClientSupportSnippet(ClientSupportSnippet) {}
222 void completeOptions(
const lspserver::Range EditRange,
223 std::vector<std::string> Scope, std::string Prefix,
224 std::vector<CompletionItem> &Items) {
225 std::binary_semaphore Ready(0);
227 auto OnReply = [&Ready,
228 &Names](llvm::Expected<OptionCompleteResponse> Resp) {
238 AttrPathCompleteParams Params{std::move(Scope), std::move(Prefix)};
239 OptionClient.optionComplete(Params, std::move(OnReply));
246 bool HasPrefix = !Params.
Prefix.empty();
248 [&](llvm::StringRef NewText) -> std::optional<lspserver::TextEdit> {
251 return lspserver::TextEdit{.range = EditRange, .newText = NewText.str()};
253 for (
const nixd::OptionField &
Field : Names) {
254 if (!
Field.Description) {
255 addItem(Items, CompletionItem{
257 .kind = OptionAttrKind,
258 .detail = ModuleOrigin,
259 .textEdit = MkTextEdit(
Field.Name),
264 const OptionDescription &Desc = *
Field.Description;
271 std::string TypeDetail = ModuleOrigin +
" | ";
273 std::string TypeName = Desc.
Type->Name.value_or(
"");
274 std::string TypeDesc = Desc.
Type->Description.value_or(
"");
275 TypeDetail += llvm::formatv(
"{0} ({1})", TypeName, TypeDesc);
277 TypeDetail +=
"? (missing type)";
280 .kind = MarkupKind::Markdown,
286 bool HasExample = Desc.
Example.has_value();
291 bool HasBoth = HasExample && HasDefault;
293 auto emit = [&](
const std::string &
Value, llvm::StringRef Source,
294 llvm::StringRef SortPrefix) {
299 HasBoth ? llvm::formatv(
"{0} ({1})",
Field.Name, Source).str()
302 .label = std::move(Label),
304 .detail = TypeDetail,
305 .documentation = Doc,
306 .sortText = (SortPrefix +
Field.Name).str(),
307 .filterText =
Field.Name,
311 addItem(Items, std::move(Item));
314 bool Emitted =
false;
316 emit(*Desc.
Example,
"example",
"0");
320 emit(*Desc.
Default,
"default",
"1");
329 .detail = TypeDetail,
330 .documentation = Doc,
332 fillInsertText(Item,
Field.Name,
"");
334 addItem(Items, std::move(Item));
341 const std::vector<std::string> &Scope,
342 const std::string &Prefix,
344 std::vector<CompletionItem> &List) {
345 for (
const auto &[Name, Provider] : Options) {
347 if (!Client) [[unlikely]] {
348 elog(
"skipped client {0} as it is dead", Name);
351 OptionCompletionProvider OCP(*Client, Name, CompletionSnippets);
352 OCP.completeOptions(EditRange, Scope, Prefix, List);
359 std::vector<lspserver::CompletionItem> &Items) {
360 std::vector<std::string> Scope;
363 if (R == PathResult::OK) {
365 std::string Prefix = Scope.back();
368 std::lock_guard
_(OptionsLock);
369 completeAttrName(EditRange, Scope, Prefix, Options, Snippets, Items);
375 if (IsComplete || Sel.empty()) {
377 .Scope = std::move(Sel),
381 std::string Back = std::move(Sel.back());
385 .Prefix = std::move(Back),
389#define DBG DBGPREFIX ": "
395#define DBGPREFIX "completion/var"
397 VLACompletionProvider VLAP(VLA);
398 VLAP.complete(N, List, PM);
409 NixpkgsCompletionProvider NCP(Client);
411 NCP.completePackages(EditRange, mkParams(Sel,
false), List);
412 }
catch (ExceedSizeError &) {
415 }
catch (std::exception &E) {
416 return log(
DBG "skipped, reason: {0}", E.what());
432 std::vector<CompletionItem> &List) {
433#define DBGPREFIX "completion/select"
440 if (BaseExpr.
kind() != Node::NK_ExprVar) {
444 const auto &Var =
static_cast<const nixf::ExprVar &
>(BaseExpr);
446 NixpkgsCompletionProvider NCP(Client);
451 NCP.completePackages(EditRange, mkParams(Sel, IsComplete), List);
452 }
catch (ExceedSizeError &) {
455 }
catch (std::exception &E) {
456 return log(
DBG "skipped, reason: {0}", E.what());
466 using CheckTy = CompletionList;
467 auto Action = [Reply = std::move(Reply), URI = Params.
textDocument.
uri,
469 const auto File = URI.file().str();
470 return Reply([&]() -> llvm::Expected<CompletionList> {
474 const auto *Desc = AST->descend({Pos, Pos});
477 const auto &N = *Desc;
478 const auto &PM = *TU->parentMap();
482 if (N.
kind() == Node::NK_Dot) {
489 const VariableLookupAnalysis &VLA = *TU->variableLookup();
491 switch (UpExpr.kind()) {
493 case Node::NK_ExprVar: {
494 completeVarName(EditRange, VLA, PM,
495 static_cast<const nixf::ExprVar &
>(UpExpr),
496 *nixpkgsClient(), List.
items);
503 case Node::NK_ExprSelect: {
504 const auto &Select =
static_cast<const nixf::ExprSelect &
>(UpExpr);
505 completeSelect(EditRange, Select, *nixpkgsClient(), VLA, PM,
509 case Node::NK_ExprAttrs: {
510 completeAttrPath(EditRange, N, PM, OptionsLock, Options,
511 ClientCaps.CompletionSnippets, List.
items);
517 }
catch (ExceedSizeError &Err) {
524 boost::asio::post(Pool, std::move(Action));
527void Controller::onCompletionItemResolve(
const CompletionItem &Params,
530 auto Action = [Params, Reply = std::move(Reply),
this]()
mutable {
531 if (Params.
data.empty()) {
535 AttrPathCompleteParams Req;
536 auto EV = llvm::json::parse(Params.
data);
539 Reply(EV.takeError());
543 llvm::json::Path::Root Root;
547 NixpkgsCompletionProvider NCP(*nixpkgsClient());
548 CompletionItem Resp = Params;
549 NCP.resolvePackage(Req.
Scope, Params.
label, Resp);
551 Reply(std::move(Resp));
553 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
LexerCursorRange range() 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...
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
std::vector< OptionField > OptionCompleteResponse
std::optional< TextEdit > textEdit
InsertTextFormat insertTextFormat
std::optional< MarkupContent > documentation
A human-readable string that represents a doc-comment.
std::vector< CompletionItem > items
The completion items.
Position start
The range's start position.
Position end
The range's end position.
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