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;
81class VLACompletionProvider {
82 const VariableLookupAnalysis &VLA;
86 return CompletionItemKind::Keyword;
88 return CompletionItemKind::Variable;
92 void collectDef(std::vector<CompletionItem> &Items,
const EnvNode *Env,
93 const std::string &Prefix) {
96 collectDef(Items, Env->
parent(), Prefix);
97 for (
const auto &[Name, Def] : Env->
defs()) {
102 if (Name.starts_with(Prefix)) {
103 addItem(Items, CompletionItem{
105 .kind = getCompletionItemKind(*Def),
112 VLACompletionProvider(
const VariableLookupAnalysis &VLA) : VLA(VLA) {}
115 void complete(
const nixf::ExprVar &Desc, std::vector<CompletionItem> &Items,
116 const ParentMapAnalysis &PM) {
117 std::string Prefix = Desc.
id().
name();
118 collectDef(Items,
upEnv(Desc, VLA, PM), Prefix);
129class NixpkgsCompletionProvider {
131 AttrSetClient &NixpkgsClient;
134 NixpkgsCompletionProvider(AttrSetClient &NixpkgsClient)
135 : NixpkgsClient(NixpkgsClient) {}
137 void resolvePackage(std::vector<std::string> Scope, std::string Name,
138 CompletionItem &Item) {
139 std::binary_semaphore Ready(0);
140 AttrPathInfoResponse Desc;
141 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
146 Scope.emplace_back(std::move(Name));
147 NixpkgsClient.attrpathInfo(Scope, std::move(OnReply));
152 .kind = MarkupKind::Markdown,
160 bool completePackages(
const lspserver::Range EditRange,
161 const AttrPathCompleteParams &Params,
162 std::vector<CompletionItem> &Items) {
163 std::binary_semaphore Ready(0);
164 AttrPathCompleteResponse Names;
165 auto OnReply = [&Ready,
166 &Names](llvm::Expected<AttrPathCompleteResponse> Resp) {
176 NixpkgsClient.attrpathComplete(Params, std::move(OnReply));
179 for (
const auto &Name : Names.
Items) {
180 if (Name.starts_with(Params.
Prefix)) {
181 addItem(Items, CompletionItem{
183 .kind = CompletionItemKind::Field,
184 .textEdit = lspserver::TextEdit{.range = EditRange,
186 .data = llvm::formatv(
"{0}",
toJSON(Params)),
195class OptionCompletionProvider {
196 AttrSetClient &OptionClient;
199 std::string ModuleOrigin;
202 bool ClientSupportSnippet;
204 static std::string escapeCharacters(
const std::set<char> &Charset,
205 const std::string &Origin) {
208 Ret.reserve(Origin.size());
209 for (
const auto Ch : Origin) {
210 if (Charset.contains(Ch)) {
220 void fillInsertText(CompletionItem &Item,
const std::string &Name,
221 const std::string &
Value)
const {
222 if (!ClientSupportSnippet) {
229 "${1:" + escapeCharacters({
'\\',
'$',
'}'},
Value) +
"}" +
234 OptionCompletionProvider(AttrSetClient &OptionClient,
235 std::string ModuleOrigin,
bool ClientSupportSnippet)
236 : OptionClient(OptionClient), ModuleOrigin(std::move(ModuleOrigin)),
237 ClientSupportSnippet(ClientSupportSnippet) {}
239 bool completeOptions(
const lspserver::Range EditRange,
240 std::vector<std::string> Scope, std::string Prefix,
241 std::vector<CompletionItem> &Items) {
242 std::binary_semaphore Ready(0);
243 OptionCompleteResponse Names;
244 auto OnReply = [&Ready,
245 &Names](llvm::Expected<OptionCompleteResponse> Resp) {
255 AttrPathCompleteParams Params{std::move(Scope), std::move(Prefix)};
256 OptionClient.optionComplete(Params, std::move(OnReply));
263 bool HasPrefix = !Params.
Prefix.empty();
265 [&](llvm::StringRef NewText) -> std::optional<lspserver::TextEdit> {
268 return lspserver::TextEdit{.range = EditRange, .newText = NewText.str()};
270 for (
const nixd::OptionField &
Field : Names.
Items) {
271 if (!
Field.Description) {
272 addItem(Items, CompletionItem{
274 .kind = OptionAttrKind,
275 .detail = ModuleOrigin,
276 .textEdit = MkTextEdit(
Field.Name),
281 const OptionDescription &Desc = *
Field.Description;
288 std::string TypeDetail = ModuleOrigin +
" | ";
290 std::string TypeName = Desc.
Type->Name.value_or(
"");
291 std::string TypeDesc = Desc.
Type->Description.value_or(
"");
292 TypeDetail += llvm::formatv(
"{0} ({1})", TypeName, TypeDesc);
294 TypeDetail +=
"? (missing type)";
297 .kind = MarkupKind::Markdown,
303 bool HasExample = Desc.
Example.has_value();
308 bool HasBoth = HasExample && HasDefault;
310 auto emit = [&](
const std::string &
Value, llvm::StringRef Source,
311 llvm::StringRef SortPrefix) {
316 HasBoth ? llvm::formatv(
"{0} ({1})",
Field.Name, Source).str()
319 .label = std::move(Label),
321 .detail = TypeDetail,
322 .documentation = Doc,
323 .sortText = (SortPrefix +
Field.Name).str(),
324 .filterText =
Field.Name,
328 addItem(Items, std::move(Item));
331 bool Emitted =
false;
333 emit(*Desc.
Example,
"example",
"0");
337 emit(*Desc.
Default,
"default",
"1");
346 .detail = TypeDetail,
347 .documentation = Doc,
349 fillInsertText(Item,
Field.Name,
"");
351 addItem(Items, std::move(Item));
359 const std::vector<std::string> &Scope,
360 const std::string &Prefix,
362 std::vector<CompletionItem> &List) {
363 bool IsIncomplete =
false;
364 for (
const auto &[Name, Provider] : Options) {
366 if (!Client) [[unlikely]] {
367 elog(
"skipped client {0} as it is dead", Name);
370 OptionCompletionProvider OCP(*Client, Name, CompletionSnippets);
371 IsIncomplete |= OCP.completeOptions(EditRange, Scope, Prefix, List);
379 std::vector<lspserver::CompletionItem> &Items) {
380 std::vector<std::string> Scope;
383 if (R == PathResult::OK) {
385 std::string Prefix = Scope.back();
388 std::lock_guard
_(OptionsLock);
389 return completeAttrName(EditRange, Scope, Prefix, Options, Snippets,
397 if (IsComplete || Sel.empty()) {
399 .Scope = std::move(Sel),
403 std::string Back = std::move(Sel.back());
407 .Prefix = std::move(Back),
411#define DBG DBGPREFIX ": "
417#define DBGPREFIX "completion/var"
419 VLACompletionProvider VLAP(VLA);
420 VLAP.complete(N, List, PM);
431 NixpkgsCompletionProvider NCP(Client);
433 return NCP.completePackages(EditRange, mkParams(Sel,
false),
435 }
catch (ExceedSizeError &) {
438 }
catch (std::exception &E) {
439 log(
DBG "skipped, reason: {0}", E.what());
456 std::vector<CompletionItem> &List) {
457#define DBGPREFIX "completion/select"
464 if (BaseExpr.
kind() != Node::NK_ExprVar) {
468 const auto &Var =
static_cast<const nixf::ExprVar &
>(BaseExpr);
470 NixpkgsCompletionProvider NCP(Client);
475 return NCP.completePackages(EditRange, mkParams(Sel, IsComplete), List);
476 }
catch (ExceedSizeError &) {
479 }
catch (std::exception &E) {
480 log(
DBG "skipped, reason: {0}", E.what());
491 using CheckTy = CompletionList;
492 auto Action = [Reply = std::move(Reply), URI = Params.
textDocument.
uri,
494 Context = Params.
context,
this]()
mutable {
495 const auto File = URI.file().str();
496 bool KnownSelector =
false;
497 auto Result = [&]() -> llvm::Expected<CompletionList> {
500 const auto &PM = *TU->parentMap();
501 const VariableLookupAnalysis &VLA = *TU->variableLookup();
503 const auto *Desc = AST->descend({Pos, Pos});
505 KnownSelector = isKnownSelector(*Desc, VLA, PM);
508 const auto &N = *Desc;
512 if (N.
kind() == Node::NK_Dot) {
518 bool ProviderIncomplete =
false;
520 switch (UpExpr.kind()) {
522 case Node::NK_ExprVar: {
523 ProviderIncomplete = completeVarName(
524 EditRange, VLA, PM,
static_cast<const nixf::ExprVar &
>(UpExpr),
525 *nixpkgsClient(), List.
items);
532 case Node::NK_ExprSelect: {
533 const auto &Select =
static_cast<const nixf::ExprSelect &
>(UpExpr);
535 completeSelect(EditRange, Select, *nixpkgsClient(), VLA, PM,
539 case Node::NK_ExprAttrs: {
541 completeAttrPath(EditRange, N, PM, OptionsLock, Options,
542 ClientCaps.CompletionSnippets, List.
items);
548 }
catch (ExceedSizeError &Err) {
560 Context.triggerKind == CompletionTriggerKind::TriggerCharacter &&
561 Context.triggerCharacter ==
"." && KnownSelector &&
562 Result->items.empty())
563 Result->isIncomplete =
true;
564 return Reply(std::move(Result));
566 boost::asio::post(Pool, std::move(Action));
569void Controller::onCompletionItemResolve(
const CompletionItem &Params,
572 auto Action = [Params, Reply = std::move(Reply),
this]()
mutable {
573 if (Params.
data.empty()) {
577 AttrPathCompleteParams Req;
578 auto EV = llvm::json::parse(Params.
data);
581 Reply(EV.takeError());
585 llvm::json::Path::Root Root;
589 NixpkgsCompletionProvider NCP(*nixpkgsClient());
590 CompletionItem Resp = Params;
591 NCP.resolvePackage(Req.
Scope, Params.
label, Resp);
593 Reply(std::move(Resp));
595 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::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.
CompletionContext context
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".
std::vector< std::string > Items
PackageDescription PackageDesc
Package description of the attribute path, if available.
std::vector< OptionField > Items
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
Exceptions scoped in nixd::mkIdiomSelector.