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
90public:
91 NixpkgsHoverProvider(AttrSetClient &NixpkgsClient)
92 : NixpkgsClient(NixpkgsClient) {}
93
94 std::optional<std::string> resolvePackage(std::vector<std::string> Scope,
95 std::string Name) {
96 std::binary_semaphore Ready(0);
97 std::optional<AttrPathInfoResponse> Desc;
98 auto OnReply = [&Ready, &Desc](llvm::Expected<AttrPathInfoResponse> Resp) {
99 if (Resp)
100 Desc = *Resp;
101 else
102 elog("nixpkgs provider: {0}", Resp.takeError());
103 Ready.release();
104 };
105 Scope.emplace_back(std::move(Name));
106 NixpkgsClient.attrpathInfo(Scope, std::move(OnReply));
107 Ready.acquire();
108
109 if (!Desc)
110 return std::nullopt;
111
112 return mkMarkdown(Desc->PackageDesc);
113 }
114};
115
116} // namespace
117
118void Controller::onHover(const TextDocumentPositionParams &Params,
119 Callback<std::optional<Hover>> Reply) {
120 using CheckTy = std::optional<Hover>;
121 auto Action = [Reply = std::move(Reply),
122 File = std::string(Params.textDocument.uri.file()),
123 RawPos = Params.position, this]() mutable {
124 return Reply([&]() -> llvm::Expected<CheckTy> {
125 const auto TU = CheckDefault(getTU(File));
126 const auto AST = CheckDefault(getAST(*TU));
127 const auto Pos = nixf::Position{RawPos.line, RawPos.character};
128 const auto &N = *CheckDefault(AST->descend({Pos, Pos}));
129
130 const auto Name = std::string(N.name());
131 const auto &VLA = *TU->variableLookup();
132 const auto &PM = *TU->parentMap();
133 if (havePackageScope(N, VLA, PM) && nixpkgsClient()) {
134 // Ask nixpkgs client what's current package documentation.
135 auto NHP = NixpkgsHoverProvider(*nixpkgsClient());
136 const auto [Scope, Name] = getScopeAndPrefix(N, PM);
137 if (std::optional<std::string> Doc = NHP.resolvePackage(Scope, Name)) {
138 return Hover{
139 .contents =
141 .kind = MarkupKind::Markdown,
142 .value = std::move(*Doc),
143 },
144 .range = toLSPRange(TU->src(), N.range()),
145 };
146 }
147 }
148
149 auto Scope = std::vector<std::string>();
150 const auto R = findAttrPath(N, PM, Scope);
151 if (R == FindAttrPathResult::OK) {
152 std::lock_guard _(OptionsLock);
153 for (const auto &[_, Client] : Options) {
154 if (AttrSetClient *C = Client->client()) {
155 OptionsHoverProvider OHP(*C);
156 std::optional<OptionDescription> Desc = OHP.resolveHover(Scope);
157 std::string Docs;
158 if (Desc) {
159 if (Desc->Type) {
160 std::string TypeName = Desc->Type->Name.value_or("");
161 std::string TypeDesc = Desc->Type->Description.value_or("");
162 Docs += llvm::formatv("{0} ({1})", TypeName, TypeDesc);
163 } else {
164 Docs += "? (missing type)";
165 }
166 if (Desc->Description) {
167 Docs += "\n\n" + Desc->Description.value_or("");
168 }
169 return Hover{
170 .contents =
172 .kind = MarkupKind::Markdown,
173 .value = std::move(Docs),
174 },
175 .range = toLSPRange(TU->src(), N.range()),
176 };
177 }
178 }
179 }
180 }
181
182 // Reply it's kind by static analysis
183 // FIXME: support more.
184 return Hover{
185 .contents =
187 .kind = MarkupKind::Markdown,
188 .value = "`" + Name + "`",
189 },
190 .range = toLSPRange(TU->src(), N.range()),
191 };
192 }());
193 };
194 boost::asio::post(Pool, std::move(Action));
195}
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.
void optionInfo(const AttrPathInfoParams &Params, lspserver::Callback< OptionInfoResponse > Reply)
void attrpathInfo(const AttrPathInfoParams &Params, lspserver::Callback< AttrPathInfoResponse > Reply)
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
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
Position position
The position inside the text document.
TextDocumentIdentifier textDocument
The text document.
llvm::StringRef file() const
Retrieves absolute path to the file.