16#include <boost/asio/post.hpp>
21#include <unordered_set>
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));
51class VLACompletionProvider {
56 return CompletionItemKind::Keyword;
58 return CompletionItemKind::Variable;
62 void collectDef(std::vector<CompletionItem> &Items,
const EnvNode *Env,
63 const std::string &Prefix) {
66 collectDef(Items, Env->
parent(), Prefix);
67 for (
const auto &[Name, Def] : Env->defs()) {
72 if (Name.starts_with(Prefix)) {
75 .kind = getCompletionItemKind(*Def),
85 void complete(
const nixf::ExprVar &Desc, std::vector<CompletionItem> &Items,
87 std::string Prefix = Desc.
id().
name();
88 collectDef(Items,
upEnv(Desc, VLA, PM), Prefix);
99class NixpkgsCompletionProvider {
105 : NixpkgsClient(NixpkgsClient) {}
107 void resolvePackage(std::vector<std::string> Scope, std::string Name,
109 std::binary_semaphore Ready(0);
111 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
116 Scope.emplace_back(std::move(Name));
122 .
kind = MarkupKind::Markdown,
131 std::vector<CompletionItem> &Items) {
132 std::binary_semaphore Ready(0);
133 std::vector<std::string> Names;
134 auto OnReply = [&Ready,
135 &Names](llvm::Expected<AttrPathCompleteResponse> Resp) {
148 for (
const auto &Name : Names) {
149 if (Name.starts_with(Params.
Prefix)) {
152 .kind = CompletionItemKind::Field,
153 .data = llvm::formatv(
"{0}",
toJSON(Params)),
161class OptionCompletionProvider {
165 std::string ModuleOrigin;
168 bool ClientSupportSnippet;
170 static std::string escapeCharacters(
const std::set<char> &Charset,
171 const std::string &Origin) {
174 Ret.reserve(Origin.size());
175 for (
const auto Ch : Origin) {
176 if (Charset.contains(Ch)) {
186 void fillInsertText(
CompletionItem &Item,
const std::string &Name,
188 if (!ClientSupportSnippet) {
196 "${1:" + escapeCharacters({
'\\',
'$',
'}'}, Desc.
Example.value_or(
"")) +
202 std::string ModuleOrigin,
bool ClientSupportSnippet)
203 : OptionClient(OptionClient), ModuleOrigin(std::move(ModuleOrigin)),
204 ClientSupportSnippet(ClientSupportSnippet) {}
206 void completeOptions(std::vector<std::string> Scope, std::string Prefix,
207 std::vector<CompletionItem> &Items) {
208 std::binary_semaphore Ready(0);
210 auto OnReply = [&Ready,
211 &Names](llvm::Expected<OptionCompleteResponse> Resp) {
229 Item.
detail = ModuleOrigin;
231 if (
Field.Description) {
233 Item.
kind = OptionKind;
234 fillInsertText(Item,
Field.Name, Desc);
236 .
kind = MarkupKind::Markdown,
241 std::string TypeName = Desc.
Type->Name.value_or(
"");
242 std::string TypeDesc = Desc.
Type->Description.value_or(
"");
243 Item.
detail += llvm::formatv(
"{0} ({1})", TypeName, TypeDesc);
245 Item.
detail +=
"? (missing type)";
247 addItem(Items, std::move(Item));
249 Item.
kind = OptionAttrKind;
250 addItem(Items, std::move(Item));
256void completeAttrName(
const std::vector<std::string> &Scope,
257 const std::string &Prefix,
259 std::vector<CompletionItem> &List) {
260 for (
const auto &[Name, Provider] : Options) {
262 if (!Client) [[unlikely]] {
263 elog(
"skipped client {0} as it is dead", Name);
266 OptionCompletionProvider OCP(*Client, Name, CompletionSnippets);
267 OCP.completeOptions(Scope, Prefix, List);
274 std::vector<lspserver::CompletionItem> &Items) {
275 std::vector<std::string> Scope;
278 if (R == PathResult::OK) {
280 std::string Prefix = Scope.back();
283 std::lock_guard
_(OptionsLock);
284 completeAttrName(Scope, Prefix, Options, Snippets, Items);
290 if (IsComplete || Sel.empty()) {
292 .
Scope = std::move(Sel),
296 std::string Back = std::move(Sel.back());
300 .Prefix = std::move(Back),
304#define DBG DBGPREFIX ": "
309#define DBGPREFIX "completion/var"
311 VLACompletionProvider VLAP(VLA);
312 VLAP.complete(N, List, PM);
323 NixpkgsCompletionProvider NCP(Client);
325 NCP.completePackages(mkParams(Sel,
false), List);
326 }
catch (ExceedSizeError &) {
329 }
catch (std::exception &E) {
330 return log(
DBG "skipped, reason: {0}", E.what());
345 std::vector<CompletionItem> &List) {
346#define DBGPREFIX "completion/select"
353 if (BaseExpr.
kind() != Node::NK_ExprVar) {
357 const auto &Var =
static_cast<const nixf::ExprVar &
>(BaseExpr);
359 NixpkgsCompletionProvider NCP(Client);
364 NCP.completePackages(mkParams(Sel, IsComplete), List);
365 }
catch (ExceedSizeError &) {
368 }
catch (std::exception &E) {
369 return log(
DBG "skipped, reason: {0}", E.what());
382 if (std::shared_ptr<NixTU> TU = getTU(
File, Reply)) [[likely]] {
383 if (std::shared_ptr<nixf::Node> AST = getAST(*TU, Reply)) [[likely]] {
384 const nixf::Node *Desc = AST->descend({Pos, Pos});
386 Reply(
error(
"cannot find corresponding node on given position"));
403 const Node &UpExpr = *MaybeUpExpr;
408 switch (UpExpr.
kind()) {
410 case Node::NK_ExprVar: {
411 completeVarName(VLA, PM,
412 static_cast<const nixf::ExprVar &>(UpExpr),
413 *nixpkgsClient(), List.items);
420 case Node::NK_ExprSelect: {
423 completeSelect(Select, *nixpkgsClient(), VLA, PM,
427 case Node::NK_ExprAttrs: {
428 completeAttrPath(N, PM, OptionsLock, Options,
435 }
catch (ExceedSizeError &Err) {
443 boost::asio::post(Pool, std::move(Action));
446void Controller::onCompletionItemResolve(
const CompletionItem &Params,
449 auto Action = [Params, Reply = std::move(Reply),
this]()
mutable {
450 if (Params.
data.empty()) {
455 auto EV = llvm::json::parse(Params.
data);
458 Reply(EV.takeError());
462 llvm::json::Path::Root Root;
466 NixpkgsCompletionProvider NCP(*nixpkgsClient());
468 NCP.resolvePackage(Req.
Scope, Params.
label, Resp);
470 Reply(std::move(Resp));
472 boost::asio::post(Pool, std::move(Action));
This file declares some common analysis (tree walk) on the AST.
Types used in nixpkgs provider.
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 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
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&...Vals)
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