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 return mkMarkdown(*Info);
142 }
143};
144
145/// \brief Get nixpkgs hover info from a selector.
146std::optional<Hover> hoverNixpkgsSelector(const Selector &Sel,
147 const nixf::Node &N,
148 const VariableLookupAnalysis &VLA,
149 const ParentMapAnalysis &PM,
150 AttrSetClient &NixpkgsClient,
151 llvm::StringRef Src) {
152 try {
153 // Ask nixpkgs provider information about this selector.
154 NixpkgsHoverProvider NHP(NixpkgsClient);
155 if (std::optional<std::string> Doc = NHP.resolveSelector(Sel)) {
156 return Hover{
157 .contents =
159 .kind = MarkupKind::Markdown,
160 .value = std::move(*Doc),
161 },
162 .range = toLSPRange(Src, N.range()),
163 };
164 }
165 } catch (std::exception &E) {
166 elog("hover/idiom: {0}", E.what());
167 }
168 return std::nullopt;
169}
170
171/// \brief Get hover info for ExprVar.
172std::optional<Hover> hoverVar(const ExprVar &Var,
173 const VariableLookupAnalysis &VLA,
174 const ParentMapAnalysis &PM,
175 AttrSetClient &NixpkgsClient,
176 llvm::StringRef Src) {
177 try {
178 Selector Sel = idioms::mkVarSelector(Var, VLA, PM);
179 return hoverNixpkgsSelector(Sel, Var, VLA, PM, NixpkgsClient, Src);
180 } catch (std::exception &E) {
181 elog("hover/idiom/selector: {0}", E.what());
182 }
183 return std::nullopt;
184}
185
186/// \brief Get hover info for ExprSelect.
187std::optional<Hover> hoverSelect(const ExprSelect &Sel,
188 const VariableLookupAnalysis &VLA,
189 const ParentMapAnalysis &PM,
190 AttrSetClient &NixpkgsClient,
191 llvm::StringRef Src) {
192 try {
193 Selector S = idioms::mkSelector(Sel, VLA, PM);
194 return hoverNixpkgsSelector(S, Sel, VLA, PM, NixpkgsClient, Src);
195 } catch (std::exception &E) {
196 elog("hover/idiom/selector: {0}", E.what());
197 }
198 return std::nullopt;
199}
200
201} // namespace
202
203void Controller::onHover(const TextDocumentPositionParams &Params,
204 Callback<std::optional<Hover>> Reply) {
205 using CheckTy = std::optional<Hover>;
206 auto Action = [Reply = std::move(Reply),
207 File = std::string(Params.textDocument.uri.file()),
208 RawPos = Params.position, this]() mutable {
209 return Reply([&]() -> llvm::Expected<CheckTy> {
210 const auto TU = CheckDefault(getTU(File));
211 const auto AST = CheckDefault(getAST(*TU));
212 const auto Pos = nixf::Position{RawPos.line, RawPos.character};
213 const auto &N = *CheckDefault(AST->descend({Pos, Pos}));
214
215 const auto Name = std::string(N.name());
216 const auto &VLA = *TU->variableLookup();
217 const auto &PM = *TU->parentMap();
218
219 const auto &UpExpr = *CheckDefault(PM.upExpr(N));
220
221 // Try to get hover info from nixpkgs.
222 if (auto *Client = nixpkgsClient(); Client) {
223 switch (UpExpr.kind()) {
224 case Node::NK_ExprVar: {
225 const auto &Var = static_cast<const ExprVar &>(UpExpr);
226 if (auto H = hoverVar(Var, VLA, PM, *Client, TU->src()))
227 return *H;
228 break;
229 }
230 case Node::NK_ExprSelect: {
231 const auto &Sel = static_cast<const ExprSelect &>(UpExpr);
232 if (auto H = hoverSelect(Sel, VLA, PM, *Client, TU->src()))
233 return *H;
234 break;
235 }
236 case Node::NK_ExprAttrs: {
237 // Try to get hover info from options.
238 auto Scope = std::vector<std::string>();
239 const auto R = findAttrPathForOptions(N, PM, Scope);
240 if (R == FindAttrPathResult::OK) {
241 std::lock_guard _(OptionsLock);
242 for (const auto &[_, Client] : Options) {
243 if (AttrSetClient *C = Client->client()) {
244 OptionsHoverProvider OHP(*C);
245 std::optional<OptionDescription> Desc = OHP.resolveHover(Scope);
246 std::string Docs;
247 if (Desc) {
248 if (Desc->Type) {
249 std::string TypeName = Desc->Type->Name.value_or("");
250 std::string TypeDesc = Desc->Type->Description.value_or("");
251 Docs += llvm::formatv("{0} ({1})", TypeName, TypeDesc);
252 } else {
253 Docs += "? (missing type)";
254 }
255 if (Desc->Description) {
256 Docs += "\n\n" + Desc->Description.value_or("");
257 }
258 return Hover{
259 .contents =
260 MarkupContent{
261 .kind = MarkupKind::Markdown,
262 .value = std::move(Docs),
263 },
264 .range = toLSPRange(TU->src(), N.range()),
265 };
266 }
267 }
268 }
269 }
270 break;
271 }
272 default:
273 break;
274 }
275 }
276
277 return std::nullopt;
278 }());
279 };
280 boost::asio::post(Pool, std::move(Action));
281}
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:320
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.