nixd
Loading...
Searching...
No Matches
Hover.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of [Hover Request].
3/// [Hover Request]:
4/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
5
6#include "AST.h"
7#include "CheckReturn.h"
8#include "Convert.h"
9
12
13#include <boost/asio/post.hpp>
14
15#include <llvm/Support/Error.h>
16
17#include <semaphore>
18#include <sstream>
19
20using namespace nixd;
21using namespace llvm::json;
22using namespace nixf;
23using namespace lspserver;
24
25namespace {
26
27class OptionsHoverProvider {
28 AttrSetClient &Client;
29
30public:
31 OptionsHoverProvider(AttrSetClient &Client) : Client(Client) {}
32 std::optional<OptionDescription>
33 resolveHover(const std::vector<std::string> &Scope) {
34 std::binary_semaphore Ready(0);
35 std::optional<OptionDescription> Desc;
36 auto OnReply = [&Ready, &Desc](llvm::Expected<OptionInfoResponse> Resp) {
37 if (Resp)
38 Desc = *Resp;
39 else
40 elog("options hover: {0}", Resp.takeError());
41 Ready.release();
42 };
43
44 Client.optionInfo(Scope, std::move(OnReply));
45 Ready.acquire();
46
47 return Desc;
48 }
49};
50
51/// \brief Provide package information, library information ... , from nixpkgs.
52class NixpkgsHoverProvider {
53 AttrSetClient &NixpkgsClient;
54
55 /// \brief Make markdown documentation by package description
56 ///
57 /// FIXME: there are many markdown generation in language server.
58 /// Maybe we can add structured generating first?
59 static std::string mkMarkdown(const PackageDescription &Package) {
60 std::ostringstream OS;
61 // Make each field a new section
62
63 if (Package.Name) {
64 OS << "`" << *Package.Name << "`";
65 OS << "\n";
66 }
67
68 // Make links to homepage.
69 if (Package.Homepage) {
70 OS << "[homepage](" << *Package.Homepage << ")";
71 OS << "\n";
72 }
73
74 if (Package.Description) {
75 OS << "## Description"
76 << "\n\n";
77 OS << *Package.Description;
78 OS << "\n\n";
79
80 if (Package.LongDescription) {
81 OS << "\n\n";
82 OS << *Package.LongDescription;
83 OS << "\n\n";
84 }
85 }
86
87 return OS.str();
88 }
89
90 /// \brief Make markdown including both package and value description
91 static std::string mkMarkdown(const AttrPathInfoResponse &Info) {
92 std::ostringstream OS;
93 // Package section (if available)
94 OS << mkMarkdown(Info.PackageDesc);
95
96 // Value description section
97 if (Info.ValueDesc) {
98 const auto &VD = *Info.ValueDesc;
99 if (!OS.str().empty())
100 OS << "\n";
101 if (!VD.Doc.empty()) {
102 OS << VD.Doc << "\n\n";
103 }
104 if (VD.Arity != 0) {
105 OS << "**Arity:** " << VD.Arity << "\n";
106 }
107 if (!VD.Args.empty()) {
108 OS << "**Args:** ";
109 for (size_t Idx = 0; Idx < VD.Args.size(); ++Idx) {
110 OS << "`" << VD.Args[Idx] << "`";
111 if (Idx + 1 < VD.Args.size())
112 OS << ", ";
113 }
114 OS << "\n";
115 }
116 }
117
118 return OS.str();
119 }
120
121public:
122 NixpkgsHoverProvider(AttrSetClient &NixpkgsClient)
123 : NixpkgsClient(NixpkgsClient) {}
124
125 std::optional<std::string> resolveSelector(const nixd::Selector &Sel) {
126 std::binary_semaphore Ready(0);
127 std::optional<AttrPathInfoResponse> Info;
128 auto OnReply = [&Ready, &Info](llvm::Expected<AttrPathInfoResponse> Resp) {
129 if (Resp)
130 Info = *Resp;
131 else
132 elog("nixpkgs provider: {0}", Resp.takeError());
133 Ready.release();
134 };
135 NixpkgsClient.attrpathInfo(Sel, std::move(OnReply));
136 Ready.acquire();
137
138 if (!Info)
139 return std::nullopt;
140
141 auto Md = mkMarkdown(*Info);
142 if (Md.empty())
143 return std::nullopt;
144
145 return Md;
146 }
147};
148
149/// \brief Get nixpkgs hover info from a selector.
150std::optional<Hover> hoverNixpkgsSelector(const Selector &Sel,
151 const nixf::Node &N,
152 const VariableLookupAnalysis &VLA,
153 const ParentMapAnalysis &PM,
154 AttrSetClient &NixpkgsClient,
155 llvm::StringRef Src) {
156 try {
157 // Ask nixpkgs provider information about this selector.
158 NixpkgsHoverProvider NHP(NixpkgsClient);
159 if (std::optional<std::string> Doc = NHP.resolveSelector(Sel)) {
160 return Hover{
161 .contents =
163 .kind = MarkupKind::Markdown,
164 .value = std::move(*Doc),
165 },
166 .range = toLSPRange(Src, N.range()),
167 };
168 }
169 } catch (std::exception &E) {
170 elog("hover/idiom: {0}", E.what());
171 }
172 return std::nullopt;
173}
174
175/// \brief Get hover info for ExprVar.
176std::optional<Hover> hoverVar(const ExprVar &Var,
177 const VariableLookupAnalysis &VLA,
178 const ParentMapAnalysis &PM,
179 AttrSetClient &NixpkgsClient,
180 llvm::StringRef Src) {
181 try {
182 Selector Sel = idioms::mkVarSelector(Var, VLA, PM);
183 return hoverNixpkgsSelector(Sel, Var, VLA, PM, NixpkgsClient, Src);
184 } catch (std::exception &E) {
185 elog("hover/idiom/selector: {0}", E.what());
186 }
187 return std::nullopt;
188}
189
190/// \brief Get hover info for ExprSelect.
191std::optional<Hover> hoverSelect(const ExprSelect &Sel,
192 const VariableLookupAnalysis &VLA,
193 const ParentMapAnalysis &PM,
194 AttrSetClient &NixpkgsClient,
195 llvm::StringRef Src) {
196 try {
197 Selector S = idioms::mkSelector(Sel, VLA, PM);
198 return hoverNixpkgsSelector(S, Sel, VLA, PM, NixpkgsClient, Src);
199 } catch (std::exception &E) {
200 elog("hover/idiom/selector: {0}", E.what());
201 }
202 return std::nullopt;
203}
204
205} // namespace
206
207void Controller::onHover(const TextDocumentPositionParams &Params,
208 Callback<std::optional<Hover>> Reply) {
209 using CheckTy = std::optional<Hover>;
210 auto Action = [Reply = std::move(Reply),
211 File = std::string(Params.textDocument.uri.file()),
212 RawPos = Params.position, this]() mutable {
213 return Reply([&]() -> llvm::Expected<CheckTy> {
214 const auto TU = CheckDefault(getTU(File));
215 const auto AST = CheckDefault(getAST(*TU));
216 const auto Pos = nixf::Position{RawPos.line, RawPos.character};
217 const auto &N = *CheckDefault(AST->descend({Pos, Pos}));
218
219 const auto Name = std::string(N.name());
220 const auto &VLA = *TU->variableLookup();
221 const auto &PM = *TU->parentMap();
222
223 const auto &UpExpr = *CheckDefault(PM.upExpr(N));
224
225 // Try to get hover info from nixpkgs.
226 if (auto *Client = nixpkgsClient(); Client) {
227 switch (UpExpr.kind()) {
228 case Node::NK_ExprVar: {
229 const auto &Var = static_cast<const ExprVar &>(UpExpr);
230 if (auto H = hoverVar(Var, VLA, PM, *Client, TU->src()))
231 return *H;
232 break;
233 }
234 case Node::NK_ExprSelect: {
235 const auto &Sel = static_cast<const ExprSelect &>(UpExpr);
236 if (auto H = hoverSelect(Sel, VLA, PM, *Client, TU->src()))
237 return *H;
238 break;
239 }
240 case Node::NK_ExprAttrs: {
241 // Try to get hover info from options.
242 auto Scope = std::vector<std::string>();
243 const auto R = findAttrPathForOptions(N, PM, Scope);
244 if (R == FindAttrPathResult::OK) {
245 std::lock_guard _(OptionsLock);
246 for (const auto &[_, Client] : Options) {
247 if (AttrSetClient *C = Client->client()) {
248 OptionsHoverProvider OHP(*C);
249 std::optional<OptionDescription> Desc = OHP.resolveHover(Scope);
250 std::string Docs;
251 if (Desc) {
252 if (Desc->Type) {
253 std::string TypeName = Desc->Type->Name.value_or("");
254 std::string TypeDesc = Desc->Type->Description.value_or("");
255 Docs += llvm::formatv("{0} ({1})", TypeName, TypeDesc);
256 } else {
257 Docs += "? (missing type)";
258 }
259 if (Desc->Description) {
260 Docs += "\n\n" + Desc->Description.value_or("");
261 }
262 return Hover{
263 .contents =
264 MarkupContent{
265 .kind = MarkupKind::Markdown,
266 .value = std::move(Docs),
267 },
268 .range = toLSPRange(TU->src(), N.range()),
269 };
270 }
271 }
272 }
273 }
274 break;
275 }
276 default:
277 break;
278 }
279 }
280
281 return std::nullopt;
282 }());
283 };
284 boost::asio::post(Pool, std::move(Action));
285}
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.
Definition CheckReturn.h:16
Convert between LSP and nixf types.
static const char * name(NodeKind Kind)
Definition Nodes.cpp:35
LexerCursorRange range() const
Definition Basic.h:35
const Node * upExpr(const Node &N) const
Search up until the node becomes a concrete expression. a ^<--— ID -> ExprVar.
Definition ParentMap.cpp:17
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::unique_function< void(llvm::Expected< T >)> Callback
Definition Function.h:14
void elog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:52
Selector mkVarSelector(const nixf::ExprVar &Var, const nixf::VariableLookupAnalysis &VLA, const nixf::ParentMapAnalysis &PM)
Construct a nixd::Selector from Var.
Definition AST.cpp:199
Selector mkSelector(const nixf::AttrPath &AP, Selector BaseSelector)
Construct a nixd::Selector from AP.
Definition AST.cpp:249
std::vector< std::string > Selector
A list of strings that "select"s into a attribute set.
Definition AttrSet.h:43
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...
Definition AST.cpp:332
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
Definition Convert.cpp:40
Position position
The position inside the text document.
TextDocumentIdentifier textDocument
The text document.
llvm::StringRef file() const
Retrieves absolute path to the file.