nixd
Loading...
Searching...
No Matches
AttrSetProvider.cpp
Go to the documentation of this file.
3
5
6#include <nix/cmd/common-eval-args.hh>
7#include <nix/expr/attr-path.hh>
8#include <nix/expr/nixexpr.hh>
9#include <nix/store/store-api.hh>
10#include <nixt/Value.h>
11
12using namespace nixd;
13using namespace lspserver;
14
15namespace {
16
17constexpr int MaxItems = 30;
18
19void fillString(nix::EvalState &State, nix::Value &V,
20 const std::vector<std::string_view> &AttrPath,
21 std::optional<std::string> &Field) {
22 try {
23 nix::Value &Select = nixt::selectStringViews(State, V, AttrPath);
24 State.forceValue(Select, nix::noPos);
25 if (Select.type() == nix::ValueType::nString)
26 Field = Select.string_view();
27 } catch (std::exception &E) {
28 Field = std::nullopt;
29 }
30}
31
32/// Describe the value as if \p Package is actually a nixpkgs package.
33PackageDescription describePackage(nix::EvalState &State, nix::Value &Package) {
35 fillString(State, Package, {"name"}, R.Name);
36 fillString(State, Package, {"pname"}, R.PName);
37 fillString(State, Package, {"version"}, R.Version);
38 fillString(State, Package, {"meta", "description"}, R.Description);
39 fillString(State, Package, {"meta", "longDescription"}, R.LongDescription);
40 fillString(State, Package, {"meta", "position"}, R.Position);
41 fillString(State, Package, {"meta", "homepage"}, R.Homepage);
42 return R;
43}
44
45std::optional<Location> locationOf(nix::PosTable &PTable, nix::Value &V) {
46 nix::PosIdx P = V.determinePos(nix::noPos);
47 if (!P)
48 return std::nullopt;
49
50 nix::Pos NixPos = PTable[P];
51 const auto *SP = std::get_if<nix::SourcePath>(&NixPos.origin);
52
53 if (!SP)
54 return std::nullopt;
55
56 Position LPos = {
57 .line = static_cast<int64_t>(NixPos.line - 1),
58 .character = static_cast<int64_t>(NixPos.column - 1),
59 };
60
61 return Location{
62 .uri = URIForFile::canonicalize(SP->path.abs(), SP->path.abs()),
63 .range = {LPos, LPos},
64 };
65}
66
67ValueMeta metadataOf(nix::EvalState &State, nix::Value &V) {
68 return {
69 .Type = V.type(true),
70 .Location = locationOf(State.positions, V),
71 };
72}
73
74void fillUnsafeGetAttrPosLocation(nix::EvalState &State, nix::Value &V,
76 State.forceValue(V, nix::noPos);
77 nix::Value &File = nixt::selectAttr(State, V, State.symbols.create("file"));
78 nix::Value &Line = nixt::selectAttr(State, V, State.symbols.create("line"));
79 nix::Value &Column =
80 nixt::selectAttr(State, V, State.symbols.create("column"));
81
82 State.forceValue(File, nix::noPos);
83 State.forceValue(Line, nix::noPos);
84 State.forceValue(Column, nix::noPos);
85
86 if (File.type() == nix::ValueType::nString)
87 Loc.uri = URIForFile::canonicalize(File.c_str(), File.c_str());
88
89 if (Line.type() == nix::ValueType::nInt &&
90 Column.type() == nix::ValueType::nInt) {
91
92 // Nix position starts from "1" however lsp starts from zero.
93 lspserver::Position Pos = {static_cast<int64_t>(Line.integer()) - 1,
94 static_cast<int64_t>(Column.integer()) - 1};
95 Loc.range = {Pos, Pos};
96 }
97}
98
99void fillOptionDeclarationPositions(nix::EvalState &State, nix::Value &V,
101 State.forceValue(V, nix::noPos);
102 if (V.type() != nix::ValueType::nList)
103 return;
104 for (nix::Value *Item : V.listItems()) {
105 // Each item should have "column", "line", "file" fields.
107 fillUnsafeGetAttrPosLocation(State, *Item, Loc);
108 R.Declarations.emplace_back(std::move(Loc));
109 }
110}
111
112void fillOptionDeclarations(nix::EvalState &State, nix::Value &V,
114 // Eval declarations
115 try {
116 nix::Value &DeclarationPositions = nixt::selectAttr(
117 State, V, State.symbols.create("declarationPositions"));
118
119 State.forceValue(DeclarationPositions, nix::noPos);
120 // A list of positions, in unsafeGetAttrPos format.
121 fillOptionDeclarationPositions(State, DeclarationPositions, R);
122 } catch (nix::AttrPathNotFound &E) {
123 // FIXME: fallback to "declarations"
124 return;
125 }
126}
127
128void fillOptionType(nix::EvalState &State, nix::Value &VType, OptionType &R) {
129 fillString(State, VType, {"description"}, R.Description);
130 fillString(State, VType, {"name"}, R.Name);
131}
132
133void fillOptionDescription(nix::EvalState &State, nix::Value &V,
135 fillString(State, V, {"description"}, R.Description);
136 fillOptionDeclarations(State, V, R);
137 // FIXME: add definitions location.
138 if (V.type() == nix::ValueType::nAttrs) [[likely]] {
139 assert(V.attrs());
140 if (auto *It = V.attrs()->find(State.symbols.create("type"));
141 It != V.attrs()->end()) [[likely]] {
143 fillOptionType(State, *It->value, Type);
144 R.Type = std::move(Type);
145 }
146
147 if (auto *It = V.attrs()->find(State.symbols.create("example"));
148 It != V.attrs()->end()) {
149 State.forceValue(*It->value, It->pos);
150
151 // In nixpkgs some examples are nested in "literalExpression"
152 if (nixt::checkField(State, *It->value, "_type", "literalExpression")) {
153 R.Example = nixt::getFieldString(State, *It->value, "text");
154 } else {
155 std::ostringstream OS;
156 It->value->print(State, OS);
157 R.Example = OS.str();
158 }
159 }
160 }
161}
162
163std::vector<std::string> completeNames(nix::Value &Scope,
164 const nix::EvalState &State,
165 std::string_view Prefix) {
166 int Num = 0;
167 std::vector<std::string> Names;
168
169 // FIXME: we may want to use "Trie" to speedup the string searching.
170 // However as my (roughtly) profiling the critical in this loop is
171 // evaluating package details.
172 // "Trie"s may not beneficial because it cannot speedup eval.
173 for (const auto *AttrPtr : Scope.attrs()->lexicographicOrder(State.symbols)) {
174 const nix::Attr &Attr = *AttrPtr;
175 const std::string_view Name = State.symbols[Attr.name];
176 if (Name.starts_with(Prefix)) {
177 ++Num;
178 Names.emplace_back(Name);
179 // We set this a very limited number as to speedup
180 if (Num > MaxItems)
181 break;
182 }
183 }
184 return Names;
185}
186
187std::optional<ValueDescription> describeValue(nix::EvalState &State,
188 nix::Value &V) {
189 if (V.isPrimOp()) {
190 const auto *PrimOp = V.primOp();
191 assert(PrimOp);
192 return ValueDescription{
193 .Doc = PrimOp->doc ? std::string(PrimOp->doc) : "",
194 .Arity = static_cast<int>(PrimOp->arity),
195 .Args = PrimOp->args,
196 };
197 } else if (V.isLambda()) {
198 auto *Lambda = V.payload.lambda.fun;
199 assert(Lambda);
200 const auto DocComment = Lambda->docComment;
201
202 // We can only get the comment in the function, not the Arity and Args
203 // information. Therefore, if the comment doesn't exist, return
204 // `std::nullopt`, indicating that we didn't get any valuable information.
205 // https://github.com/NixOS/nix/blob/ee59af99f8619e17db4289843da62a24302d20b7/src/libexpr/eval.cc#L638
206 if (!DocComment)
207 return std::nullopt;
208
209 return ValueDescription{
210 .Doc = DocComment.getInnerText(State.positions),
211 .Arity = 0,
212 .Args = {},
213 };
214 }
215
216 return std::nullopt;
217}
218
219} // namespace
220
221AttrSetProvider::AttrSetProvider(std::unique_ptr<InboundPort> In,
222 std::unique_ptr<OutboundPort> Out)
223 : LSPServer(std::move(In), std::move(Out)),
224 State(new nix::EvalState({}, nix::openStore(), nix::fetchSettings,
225 nix::evalSettings)) {
226 Registry.addMethod(rpcMethod::EvalExpr, this, &AttrSetProvider::onEvalExpr);
227 Registry.addMethod(rpcMethod::AttrPathInfo, this,
229 Registry.addMethod(rpcMethod::AttrPathComplete, this,
231 Registry.addMethod(rpcMethod::OptionInfo, this,
233 Registry.addMethod(rpcMethod::OptionComplete, this,
235}
236
238 const std::string &Name,
239 lspserver::Callback<std::optional<std::string>> Reply) {
240 try {
241 nix::Expr *AST = state().parseExprFromString(Name, state().rootPath("."));
242 state().eval(AST, Nixpkgs);
243 Reply(std::nullopt);
244 return;
245 } catch (const nix::BaseError &Err) {
246 Reply(error(Err.info().msg.str()));
247 return;
248 } catch (const std::exception &Err) {
249 Reply(error(Err.what()));
250 return;
251 }
252}
253
255 const AttrPathInfoParams &AttrPath,
257 using RespT = AttrPathInfoResponse;
258 Reply([&]() -> llvm::Expected<RespT> {
259 try {
260 if (AttrPath.empty())
261 return error("attrpath is empty!");
262
263 nix::Value &V = nixt::selectStrings(state(), Nixpkgs, AttrPath);
264 state().forceValue(V, nix::noPos);
265 return RespT{
266 .Meta = metadataOf(state(), V),
267 .PackageDesc = describePackage(state(), V),
268 .ValueDesc = describeValue(state(), V),
269 };
270 } catch (const nix::BaseError &Err) {
271 return error(Err.info().msg.str());
272 } catch (const std::exception &Err) {
273 return error(Err.what());
274 }
275 }());
276}
277
279 const AttrPathCompleteParams &Params,
281 try {
282 nix::Value &Scope = nixt::selectStrings(state(), Nixpkgs, Params.Scope);
283
284 state().forceValue(Scope, nix::noPos);
285
286 if (Scope.type() != nix::ValueType::nAttrs) {
287 Reply(error("scope is not an attrset"));
288 return;
289 }
290
291 return Reply(completeNames(Scope, state(), Params.Prefix));
292 } catch (const nix::BaseError &Err) {
293 return Reply(error(Err.info().msg.str()));
294 } catch (const std::exception &Err) {
295 return Reply(error(Err.what()));
296 }
297}
298
300 const AttrPathInfoParams &AttrPath,
302 try {
303 if (AttrPath.empty()) {
304 Reply(error("attrpath is empty!"));
305 return;
306 }
307
308 nix::Value Option = nixt::selectOptions(
309 state(), Nixpkgs, nixt::toSymbols(state().symbols, AttrPath));
310
312
313 fillOptionDescription(state(), Option, R);
314
315 Reply(std::move(R));
316 return;
317 } catch (const nix::BaseError &Err) {
318 Reply(error(Err.info().msg.str()));
319 return;
320 } catch (const std::exception &Err) {
321 Reply(error(Err.what()));
322 return;
323 }
324}
325
327 const AttrPathCompleteParams &Params,
329 try {
330 nix::Value Scope = nixt::selectOptions(
331 state(), Nixpkgs, nixt::toSymbols(state().symbols, Params.Scope));
332
333 state().forceValue(Scope, nix::noPos);
334
335 if (Scope.type() != nix::ValueType::nAttrs) {
336 Reply(error("scope is not an attrset"));
337 return;
338 }
339
340 if (nixt::isOption(state(), Scope)) {
341 Reply(error("scope is already an option"));
342 return;
343 }
344
345 std::vector<OptionField> Response;
346
347 // FIXME: we may want to use "Trie" to speedup the string searching.
348 // However as my (roughtly) profiling the critical in this loop is
349 // evaluating package details.
350 // "Trie"s may not beneficial becausae it cannot speedup eval.
351 for (const auto *AttrPtr :
352 Scope.attrs()->lexicographicOrder(state().symbols)) {
353 const nix::Attr &Attr = *AttrPtr;
354 std::string_view Name = state().symbols[Attr.name];
355 if (Name.starts_with(Params.Prefix)) {
356 // Add a new "OptionField", see it's type.
357 assert(Attr.value);
358 OptionField NewField;
359 NewField.Name = Name;
360 if (nixt::isOption(state(), *Attr.value)) {
362 fillOptionDescription(state(), *Attr.value, Desc);
363 NewField.Description = std::move(Desc);
364 }
365 Response.emplace_back(std::move(NewField));
366 // We set this a very limited number as to speedup
367 if (Response.size() >= MaxItems)
368 break;
369 }
370 }
371 Reply(std::move(Response));
372 return;
373 } catch (const nix::BaseError &Err) {
374 Reply(error(Err.info().msg.str()));
375 return;
376 } catch (const std::exception &Err) {
377 Reply(error(Err.what()));
378 return;
379 }
380}
Dedicated worker for evaluating attrset.
Types used in nixpkgs provider.
LSPServer(std::unique_ptr< InboundPort > In, std::unique_ptr< OutboundPort > Out)
Definition LSPServer.h:87
void onOptionInfo(const AttrPathInfoParams &AttrPath, lspserver::Callback< OptionInfoResponse > Reply)
Provide option information on given attrpath.
void onAttrPathInfo(const AttrPathInfoParams &AttrPath, lspserver::Callback< AttrPathInfoResponse > Reply)
Query attrpath information.
void onEvalExpr(const EvalExprParams &Name, lspserver::Callback< EvalExprResponse > Reply)
Eval an expression, use it for furthur requests.
AttrSetProvider(std::unique_ptr< lspserver::InboundPort > In, std::unique_ptr< lspserver::OutboundPort > Out)
void onAttrPathComplete(const AttrPathCompleteParams &Params, lspserver::Callback< AttrPathCompleteResponse > Reply)
Complete attrpath entries.
void onOptionComplete(const AttrPathCompleteParams &Params, lspserver::Callback< OptionCompleteResponse > Reply)
Complete attrpath entries. However dive into submodules while selecting.
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::unique_function< void(llvm::Expected< T >)> Callback
Definition Function.h:14
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&...Vals)
Definition Logger.h:70
constexpr std::string_view EvalExpr
Definition AttrSet.h:30
constexpr std::string_view OptionInfo
Definition AttrSet.h:33
constexpr std::string_view AttrPathInfo
Definition AttrSet.h:31
constexpr std::string_view OptionComplete
Definition AttrSet.h:34
constexpr std::string_view AttrPathComplete
Definition AttrSet.h:32
OptionDescription OptionInfoResponse
Definition AttrSet.h:149
Selector AttrPathInfoParams
Definition AttrSet.h:48
std::optional< std::string_view > getFieldString(nix::EvalState &State, nix::Value &V, std::string_view Field)
Definition Value.cpp:23
bool isOption(nix::EvalState &State, nix::Value &V)
Definition Value.cpp:46
bool checkField(nix::EvalState &State, nix::Value &V, std::string_view Field, std::string_view Pred)
Check if value V is an attrset, has the field, and equals to Pred.
Definition Value.cpp:36
nix::Value & selectStrings(nix::EvalState &State, nix::Value &V, const std::vector< std::string > &AttrPath)
Given an attrpath in nix::Value V, select it.
Definition Value.h:61
nix::Value selectOptions(nix::EvalState &State, nix::Value &V, std::vector< nix::Symbol >::const_iterator Begin, std::vector< nix::Symbol >::const_iterator End)
Select the option declaration list, V, dive into "submodules".
Definition Value.cpp:159
std::vector< nix::Symbol > toSymbols(nix::SymbolTable &STable, const std::vector< std::string > &Names)
Transform a vector of string into a vector of nix symbols.
Definition Value.cpp:63
nix::Value & selectAttr(nix::EvalState &State, nix::Value &V, nix::Symbol Attr)
Select attribute Attr.
Definition Value.cpp:84
nix::Value & selectStringViews(nix::EvalState &State, nix::Value &V, const std::vector< std::string_view > &AttrPath)
Given an attrpath in nix::Value V, select it.
Definition Value.h:68
URIForFile uri
The text document's URI.
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
std::string Prefix
Search for packages prefixed with this "prefix".
Definition AttrSet.h:110
std::optional< std::string > Description
Definition AttrSet.h:129
std::optional< std::string > Example
Definition AttrSet.h:132
std::optional< OptionType > Type
Definition AttrSet.h:133
std::vector< lspserver::Location > Declarations
Definition AttrSet.h:130
std::optional< OptionDescription > Description
Definition AttrSet.h:142
std::string Name
Definition AttrSet.h:141
std::optional< std::string > Description
Definition AttrSet.h:120
std::optional< std::string > Name
Definition AttrSet.h:121
std::optional< std::string > Name
Definition AttrSet.h:51
std::optional< std::string > Version
Definition AttrSet.h:53
std::optional< std::string > PName
Definition AttrSet.h:52
std::optional< std::string > Description
Definition AttrSet.h:54
std::optional< std::string > LongDescription
Definition AttrSet.h:55
std::optional< std::string > Position
Definition AttrSet.h:56
std::optional< std::string > Homepage
Definition AttrSet.h:57
Using nix's ":doc" method to retrieve value's additional information.
Definition AttrSet.h:83
General metadata of all nix::Values.
Definition AttrSet.h:65