17#include <boost/asio/post.hpp>
22#include <unordered_set>
34constexpr int MaxCompletionSize = 30;
39struct ExceedSizeError : std::exception {
40 [[nodiscard]]
const char *what() const noexcept
override {
41 return "Size exceeded";
45void addItem(std::vector<CompletionItem> &Items,
CompletionItem Item) {
46 if (Items.size() >= MaxCompletionSize) {
47 throw ExceedSizeError();
49 Items.emplace_back(std::move(Item));
52class VLACompletionProvider {
57 return CompletionItemKind::Keyword;
59 return CompletionItemKind::Variable;
63 void collectDef(std::vector<CompletionItem> &Items,
const EnvNode *Env,
64 const std::string &Prefix) {
67 collectDef(Items, Env->
parent(), Prefix);
68 for (
const auto &[Name, Def] : Env->
defs()) {
73 if (Name.starts_with(Prefix)) {
76 .kind = getCompletionItemKind(*Def),
86 void complete(
const nixf::ExprVar &Desc, std::vector<CompletionItem> &Items,
88 std::string Prefix = Desc.
id().
name();
89 collectDef(Items,
upEnv(Desc, VLA, PM), Prefix);
100class NixpkgsCompletionProvider {
106 : NixpkgsClient(NixpkgsClient) {}
108 void resolvePackage(std::vector<std::string> Scope, std::string Name,
110 std::binary_semaphore Ready(0);
112 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
117 Scope.emplace_back(std::move(Name));
123 .kind = MarkupKind::Markdown,
132 std::vector<CompletionItem> &Items) {
133 std::binary_semaphore Ready(0);
134 std::vector<std::string> Names;
135 auto OnReply = [&Ready,
136 &Names](llvm::Expected<AttrPathCompleteResponse> Resp) {
149 for (
const auto &Name : Names) {
150 if (Name.starts_with(Params.
Prefix)) {
153 .kind = CompletionItemKind::Field,
154 .data = llvm::formatv(
"{0}",
toJSON(Params)),
162class OptionCompletionProvider {
166 std::string ModuleOrigin;
169 bool ClientSupportSnippet;
171 static std::string escapeCharacters(
const std::set<char> &Charset,
172 const std::string &Origin) {
175 Ret.reserve(Origin.size());
176 for (
const auto Ch : Origin) {
177 if (Charset.contains(Ch)) {
187 void fillInsertText(
CompletionItem &Item,
const std::string &Name,
189 if (!ClientSupportSnippet) {
197 "${1:" + escapeCharacters({
'\\',
'$',
'}'}, Desc.
Example.value_or(
"")) +
203 std::string ModuleOrigin,
bool ClientSupportSnippet)
204 : OptionClient(OptionClient), ModuleOrigin(std::move(ModuleOrigin)),
205 ClientSupportSnippet(ClientSupportSnippet) {}
207 void completeOptions(std::vector<std::string> Scope, std::string Prefix,
208 std::vector<CompletionItem> &Items) {
209 std::binary_semaphore Ready(0);
211 auto OnReply = [&Ready,
212 &Names](llvm::Expected<OptionCompleteResponse> Resp) {
230 Item.
detail = ModuleOrigin;
232 if (
Field.Description) {
234 Item.
kind = OptionKind;
235 fillInsertText(Item,
Field.Name, Desc);
237 .kind = MarkupKind::Markdown,
242 std::string TypeName = Desc.
Type->Name.value_or(
"");
243 std::string TypeDesc = Desc.
Type->Description.value_or(
"");
244 Item.
detail += llvm::formatv(
"{0} ({1})", TypeName, TypeDesc);
246 Item.
detail +=
"? (missing type)";
248 addItem(Items, std::move(Item));
250 Item.
kind = OptionAttrKind;
251 addItem(Items, std::move(Item));
257void completeAttrName(
const std::vector<std::string> &Scope,
258 const std::string &Prefix,
260 std::vector<CompletionItem> &List) {
261 for (
const auto &[Name, Provider] : Options) {
263 if (!Client) [[unlikely]] {
264 elog(
"skipped client {0} as it is dead", Name);
267 OptionCompletionProvider OCP(*Client, Name, CompletionSnippets);
268 OCP.completeOptions(Scope, Prefix, List);
275 std::vector<lspserver::CompletionItem> &Items) {
276 std::vector<std::string> Scope;
279 if (R == PathResult::OK) {
281 std::string Prefix = Scope.back();
284 std::lock_guard
_(OptionsLock);
285 completeAttrName(Scope, Prefix, Options, Snippets, Items);
291 if (IsComplete || Sel.empty()) {
293 .Scope = std::move(Sel),
297 std::string Back = std::move(Sel.back());
301 .Prefix = std::move(Back),
305#define DBG DBGPREFIX ": "
310#define DBGPREFIX "completion/var"
312 VLACompletionProvider VLAP(VLA);
313 VLAP.complete(N, List, PM);
324 NixpkgsCompletionProvider NCP(Client);
326 NCP.completePackages(mkParams(Sel,
false), List);
327 }
catch (ExceedSizeError &) {
330 }
catch (std::exception &E) {
331 return log(
DBG "skipped, reason: {0}", E.what());
346 std::vector<CompletionItem> &List) {
347#define DBGPREFIX "completion/select"
354 if (BaseExpr.
kind() != Node::NK_ExprVar) {
358 const auto &Var =
static_cast<const nixf::ExprVar &
>(BaseExpr);
360 NixpkgsCompletionProvider NCP(Client);
365 NCP.completePackages(mkParams(Sel, IsComplete), List);
366 }
catch (ExceedSizeError &) {
369 }
catch (std::exception &E) {
370 return log(
DBG "skipped, reason: {0}", E.what());
383 const auto File =
URI.file().str();
384 return Reply([&]() -> llvm::Expected<CompletionList> {
388 const auto *Desc = AST->descend({Pos, Pos});
391 const auto &N = *Desc;
392 const auto &PM = *TU->parentMap();
399 switch (UpExpr.kind()) {
401 case Node::NK_ExprVar: {
402 completeVarName(VLA, PM,
static_cast<const nixf::ExprVar &
>(UpExpr),
403 *nixpkgsClient(), List.
items);
410 case Node::NK_ExprSelect: {
412 completeSelect(Select, *nixpkgsClient(), VLA, PM,
416 case Node::NK_ExprAttrs: {
417 completeAttrPath(N, PM, OptionsLock, Options,
424 }
catch (ExceedSizeError &Err) {
431 boost::asio::post(Pool, std::move(Action));
434void Controller::onCompletionItemResolve(
const CompletionItem &Params,
437 auto Action = [Params, Reply = std::move(Reply),
this]()
mutable {
438 if (Params.
data.empty()) {
443 auto EV = llvm::json::parse(Params.
data);
446 Reply(EV.takeError());
450 llvm::json::Path::Root Root;
454 NixpkgsCompletionProvider NCP(*nixpkgsClient());
456 NCP.resolvePackage(Req.
Scope, Params.
label, Resp);
458 Reply(std::move(Resp));
460 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.
void attrpathInfo(const AttrPathInfoParams &Params, lspserver::Callback< AttrPathInfoResponse > Reply)
void attrpathComplete(const AttrPathCompleteParams &Params, lspserver::Callback< AttrPathCompleteResponse > Reply)
void optionComplete(const AttrPathCompleteParams &Params, lspserver::Callback< OptionCompleteResponse > Reply)
std::map< std::string, std::unique_ptr< AttrSetClientProc > > OptionMapTy
A set of variable definitions, which may inherit parent environment.
const DefMap & defs() const
const Identifier & id() const
const std::string & name() const
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)
FindAttrPathResult findAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, std::vector< std::string > &Path)
Heuristically find attrpath suitable for "attrpath" completion.
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)
std::vector< OptionField > OptionCompleteResponse
InsertTextFormat insertTextFormat
std::optional< MarkupContent > documentation
A human-readable string that represents a doc-comment.
Represents a collection of completion items to be presented in the editor.
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 > Version
std::optional< std::string > Description
std::optional< std::string > LongDescription