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 "Convert.h"
8
11
12#include <boost/asio/post.hpp>
13
14#include <llvm/Support/Error.h>
15
16#include <semaphore>
17#include <sstream>
18
19using namespace nixd;
20using namespace llvm::json;
21using namespace nixf;
22using namespace lspserver;
23
24namespace {
25
26class OptionsHoverProvider {
27 AttrSetClient &Client;
28
29public:
30 OptionsHoverProvider(AttrSetClient &Client) : Client(Client) {}
31 std::optional<OptionDescription>
32 resolveHover(const std::vector<std::string> &Scope) {
33 std::binary_semaphore Ready(0);
34 std::optional<OptionDescription> Desc;
35 auto OnReply = [&Ready, &Desc](llvm::Expected<OptionInfoResponse> Resp) {
36 if (Resp)
37 Desc = *Resp;
38 else
39 elog("options hover: {0}", Resp.takeError());
40 Ready.release();
41 };
42
43 Client.optionInfo(Scope, std::move(OnReply));
44 Ready.acquire();
45
46 return Desc;
47 }
48};
49
50/// \brief Provide package information, library information ... , from nixpkgs.
51class NixpkgsHoverProvider {
52 AttrSetClient &NixpkgsClient;
53
54 /// \brief Make markdown documentation by package description
55 ///
56 /// FIXME: there are many markdown generation in language server.
57 /// Maybe we can add structured generating first?
58 static std::string mkMarkdown(const PackageDescription &Package) {
59 std::ostringstream OS;
60 // Make each field a new section
61
62 if (Package.Name) {
63 OS << "`" << *Package.Name << "`";
64 OS << "\n";
65 }
66
67 // Make links to homepage.
68 if (Package.Homepage) {
69 OS << "[homepage](" << *Package.Homepage << ")";
70 OS << "\n";
71 }
72
73 if (Package.Description) {
74 OS << "## Description"
75 << "\n\n";
76 OS << *Package.Description;
77 OS << "\n\n";
78
79 if (Package.LongDescription) {
80 OS << "\n\n";
81 OS << *Package.LongDescription;
82 OS << "\n\n";
83 }
84 }
85
86 return OS.str();
87 }
88
89public:
90 NixpkgsHoverProvider(AttrSetClient &NixpkgsClient)
91 : NixpkgsClient(NixpkgsClient) {}
92
93 std::optional<std::string> resolvePackage(std::vector<std::string> Scope,
94 std::string Name) {
95 std::binary_semaphore Ready(0);
96 std::optional<AttrPathInfoResponse> Desc;
97 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
98 if (Resp)
99 Desc = *Resp;
100 else
101 elog("nixpkgs provider: {0}", Resp.takeError());
102 Ready.release();
103 };
104 Scope.emplace_back(std::move(Name));
105 NixpkgsClient.attrpathInfo(Scope, std::move(OnReply));
106 Ready.acquire();
107
108 if (!Desc)
109 return std::nullopt;
110
111 return mkMarkdown(Desc->PackageDesc);
112 }
113};
114
115} // namespace
116
117void Controller::onHover(const TextDocumentPositionParams &Params,
118 Callback<std::optional<Hover>> Reply) {
119 auto Action = [Reply = std::move(Reply),
120 File = std::string(Params.textDocument.uri.file()),
121 RawPos = Params.position, this]() mutable {
122 if (std::shared_ptr<NixTU> TU = getTU(File, Reply)) [[likely]] {
123 if (std::shared_ptr<nixf::Node> AST = getAST(*TU, Reply)) [[likely]] {
124 nixf::Position Pos{RawPos.line, RawPos.character};
125 const nixf::Node *N = AST->descend({Pos, Pos});
126 if (!N) {
127 Reply(std::nullopt);
128 return;
129 }
130 std::string Name = N->name();
131 const VariableLookupAnalysis &VLA = *TU->variableLookup();
132 const ParentMapAnalysis &PM = *TU->parentMap();
133 if (havePackageScope(*N, VLA, PM) && nixpkgsClient()) {
134 // Ask nixpkgs client what's current package documentation.
135 NixpkgsHoverProvider NHP(*nixpkgsClient());
136 auto [Scope, Name] = getScopeAndPrefix(*N, PM);
137 if (std::optional<std::string> Doc =
138 NHP.resolvePackage(Scope, Name)) {
139 Reply(Hover{
140 .contents =
142 .kind = MarkupKind::Markdown,
143 .value = std::move(*Doc),
144 },
145 .range = toLSPRange(TU->src(), N->range()),
146 });
147 return;
148 }
149 }
150
151 std::vector<std::string> Scope;
152 auto R = findAttrPath(*N, PM, Scope);
153 if (R == FindAttrPathResult::OK) {
154 std::lock_guard _(OptionsLock);
155 for (const auto &[_, Client] : Options) {
156 if (AttrSetClient *C = Client->client()) {
157 OptionsHoverProvider OHP(*C);
158 std::optional<OptionDescription> Desc = OHP.resolveHover(Scope);
159 std::string Docs;
160 if (Desc) {
161 if (Desc->Type) {
162 std::string TypeName = Desc->Type->Name.value_or("");
163 std::string TypeDesc = Desc->Type->Description.value_or("");
164 Docs += llvm::formatv("{0} ({1})", TypeName, TypeDesc);
165 } else {
166 Docs += "? (missing type)";
167 }
168 if (Desc->Description) {
169 Docs += "\n\n" + Desc->Description.value_or("");
170 }
171 Reply(Hover{
172 .contents =
174 .kind = MarkupKind::Markdown,
175 .value = std::move(Docs),
176 },
177 .range = toLSPRange(TU->src(), N->range()),
178 });
179 return;
180 }
181 }
182 }
183 }
184
185 // Reply it's kind by static analysis
186 // FIXME: support more.
187 Reply(Hover{
188 .contents =
190 .kind = MarkupKind::Markdown,
191 .value = "`" + Name + "`",
192 },
193 .range = toLSPRange(TU->src(), N->range()),
194 });
195 }
196 }
197 };
198 boost::asio::post(Pool, std::move(Action));
199}
This file declares some common analysis (tree walk) on the AST.
Types used in nixpkgs provider.
Convert between LSP and nixf types.
void optionInfo(const AttrPathInfoParams &Params, lspserver::Callback< OptionInfoResponse > Reply)
void attrpathInfo(const AttrPathInfoParams &Params, lspserver::Callback< AttrPathInfoResponse > Reply)
const Node * descend(PositionRange Range) const
Descendant node that contains the given range.
Definition Basic.h:49
int64_t line() const
Definition Range.h:16
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
bool fromJSON(const llvm::json::Value &Params, Configuration::Diagnostic &R, llvm::json::Path P)
FindAttrPathResult findAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, std::vector< std::string > &Path)
Heuristically find attrpath suitable for "attrpath" completion.
Definition AST.cpp:283
bool havePackageScope(const nixf::Node &N, const nixf::VariableLookupAnalysis &VLA, const nixf::ParentMapAnalysis &PM)
Determine whether or not some node has enclosed "with pkgs; [ ]".
Definition AST.cpp:108
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
Definition Convert.cpp:40
std::pair< std::vector< std::string >, std::string > getScopeAndPrefix(const nixf::Node &N, const nixf::ParentMapAnalysis &PM)
get variable scope, and it's prefix name.
Definition AST.cpp:274
MarkupContent contents
The hover's content.