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-open.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.listView()) {
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()->get(State.symbols.create("type"))) [[likely]] {
142 fillOptionType(State, *It->value, Type);
143 R.Type = std::move(Type);
144 }
145
146 if (auto *It = V.attrs()->get(State.symbols.create("example"))) {
147 State.forceValue(*It->value, It->pos);
148
149 // In nixpkgs some examples are nested in "literalExpression"
150 if (nixt::checkField(State, *It->value, "_type", "literalExpression")) {
151 R.Example = nixt::getFieldString(State, *It->value, "text");
152 } else {
153 std::ostringstream OS;
154 It->value->print(State, OS);
155 R.Example = OS.str();
156 }
157 }
158 }
159}
160
161std::vector<std::string> completeNames(nix::Value &Scope,
162 const nix::EvalState &State,
163 std::string_view Prefix) {
164 int Num = 0;
165 std::vector<std::string> Names;
166
167 // FIXME: we may want to use "Trie" to speedup the string searching.
168 // However as my (roughtly) profiling the critical in this loop is
169 // evaluating package details.
170 // "Trie"s may not beneficial because it cannot speedup eval.
171 for (const auto *AttrPtr : Scope.attrs()->lexicographicOrder(State.symbols)) {
172 const nix::Attr &Attr = *AttrPtr;
173 const std::string_view Name = State.symbols[Attr.name];
174 if (Name.starts_with(Prefix)) {
175 ++Num;
176 Names.emplace_back(Name);
177 // We set this a very limited number as to speedup
178 if (Num > MaxItems)
179 break;
180 }
181 }
182 return Names;
183}
184
185std::optional<ValueDescription> describeValue(nix::EvalState &State,
186 nix::Value &V) {
187 if (V.isPrimOp()) {
188 const auto *PrimOp = V.primOp();
189 assert(PrimOp);
190 return ValueDescription{
191 .Doc = PrimOp->doc.value_or(""),
192 .Arity = static_cast<int>(PrimOp->arity),
193 .Args = PrimOp->args,
194 };
195 } else if (V.isLambda()) {
196 auto *Lambda = V.lambda().fun;
197 assert(Lambda);
198 const auto DocComment = Lambda->docComment;
199
200 // We can only get the comment in the function, not the Arity and Args
201 // information. Therefore, if the comment doesn't exist, return
202 // `std::nullopt`, indicating that we didn't get any valuable information.
203 // https://github.com/NixOS/nix/blob/ee59af99f8619e17db4289843da62a24302d20b7/src/libexpr/eval.cc#L638
204 if (!DocComment)
205 return std::nullopt;
206
207 return ValueDescription{
208 .Doc = DocComment.getInnerText(State.positions),
209 .Arity = 0,
210 .Args = {},
211 };
212 }
213
214 return std::nullopt;
215}
216
217} // namespace
218
219AttrSetProvider::AttrSetProvider(std::unique_ptr<InboundPort> In,
220 std::unique_ptr<OutboundPort> Out)
221 : LSPServer(std::move(In), std::move(Out)),
222 State(new nix::EvalState({}, nix::openStore(), nix::fetchSettings,
223 nix::evalSettings)) {
224 Registry.addMethod(rpcMethod::EvalExpr, this, &AttrSetProvider::onEvalExpr);
225 Registry.addMethod(rpcMethod::AttrPathInfo, this,
227 Registry.addMethod(rpcMethod::AttrPathComplete, this,
229 Registry.addMethod(rpcMethod::OptionInfo, this,
231 Registry.addMethod(rpcMethod::OptionComplete, this,
233}
234
236 const std::string &Name,
237 lspserver::Callback<std::optional<std::string>> Reply) {
238 try {
239 nix::Expr *AST = state().parseExprFromString(Name, state().rootPath("."));
240 state().eval(AST, Nixpkgs);
241 Reply(std::nullopt);
242 return;
243 } catch (const nix::BaseError &Err) {
244 Reply(error(Err.info().msg.str()));
245 return;
246 } catch (const std::exception &Err) {
247 Reply(error(Err.what()));
248 return;
249 }
250}
251
253 const AttrPathInfoParams &AttrPath,
255 using RespT = AttrPathInfoResponse;
256 Reply([&]() -> llvm::Expected<RespT> {
257 try {
258 if (AttrPath.empty())
259 return error("attrpath is empty!");
260
261 nix::Value &V = nixt::selectStrings(state(), Nixpkgs, AttrPath);
262 state().forceValue(V, nix::noPos);
263 return RespT{
264 .Meta = metadataOf(state(), V),
265 .PackageDesc = describePackage(state(), V),
266 .ValueDesc = describeValue(state(), V),
267 };
268 } catch (const nix::BaseError &Err) {
269 return error(Err.info().msg.str());
270 } catch (const std::exception &Err) {
271 return error(Err.what());
272 }
273 }());
274}
275
277 const AttrPathCompleteParams &Params,
279 try {
280 nix::Value &Scope = nixt::selectStrings(state(), Nixpkgs, Params.Scope);
281
282 state().forceValue(Scope, nix::noPos);
283
284 if (Scope.type() != nix::ValueType::nAttrs) {
285 Reply(error("scope is not an attrset"));
286 return;
287 }
288
289 return Reply(completeNames(Scope, state(), Params.Prefix));
290 } catch (const nix::BaseError &Err) {
291 return Reply(error(Err.info().msg.str()));
292 } catch (const std::exception &Err) {
293 return Reply(error(Err.what()));
294 }
295}
296
298 const AttrPathInfoParams &AttrPath,
300 try {
301 if (AttrPath.empty()) {
302 Reply(error("attrpath is empty!"));
303 return;
304 }
305
306 nix::Value Option = nixt::selectOptions(
307 state(), Nixpkgs, nixt::toSymbols(state().symbols, AttrPath));
308
310
311 fillOptionDescription(state(), Option, R);
312
313 Reply(std::move(R));
314 return;
315 } catch (const nix::BaseError &Err) {
316 Reply(error(Err.info().msg.str()));
317 return;
318 } catch (const std::exception &Err) {
319 Reply(error(Err.what()));
320 return;
321 }
322}
323
325 const AttrPathCompleteParams &Params,
327 try {
328 nix::Value Scope = nixt::selectOptions(
329 state(), Nixpkgs, nixt::toSymbols(state().symbols, Params.Scope));
330
331 state().forceValue(Scope, nix::noPos);
332
333 if (Scope.type() != nix::ValueType::nAttrs) {
334 Reply(error("scope is not an attrset"));
335 return;
336 }
337
338 if (nixt::isOption(state(), Scope)) {
339 Reply(error("scope is already an option"));
340 return;
341 }
342
343 std::vector<OptionField> Response;
344
345 // FIXME: we may want to use "Trie" to speedup the string searching.
346 // However as my (roughtly) profiling the critical in this loop is
347 // evaluating package details.
348 // "Trie"s may not beneficial becausae it cannot speedup eval.
349 for (const auto *AttrPtr :
350 Scope.attrs()->lexicographicOrder(state().symbols)) {
351 const nix::Attr &Attr = *AttrPtr;
352 std::string_view Name = state().symbols[Attr.name];
353 if (Name.starts_with(Params.Prefix)) {
354 // Add a new "OptionField", see it's type.
355 assert(Attr.value);
356 OptionField NewField;
357 NewField.Name = Name;
358 if (nixt::isOption(state(), *Attr.value)) {
360 fillOptionDescription(state(), *Attr.value, Desc);
361 NewField.Description = std::move(Desc);
362 }
363 Response.emplace_back(std::move(NewField));
364 // We set this a very limited number as to speedup
365 if (Response.size() >= MaxItems)
366 break;
367 }
368 }
369 Reply(std::move(Response));
370 return;
371 } catch (const nix::BaseError &Err) {
372 Reply(error(Err.info().msg.str()));
373 return;
374 } catch (const std::exception &Err) {
375 Reply(error(Err.what()));
376 return;
377 }
378}
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