nixd
Loading...
Searching...
No Matches
AttrSetProvider.cpp
Go to the documentation of this file.
3
5
6#include <nix/attr-path.hh>
7#include <nix/common-eval-args.hh>
8#include <nix/nixexpr.hh>
9#include <nix/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 if (File.type() == nix::ValueType::nString)
83 Loc.uri = URIForFile::canonicalize(File.c_str(), File.c_str());
84
85 if (Line.type() == nix::ValueType::nInt &&
86 Column.type() == nix::ValueType::nInt) {
87
88 // Nix position starts from "1" however lsp starts from zero.
89 lspserver::Position Pos = {static_cast<int64_t>(Line.integer()) - 1,
90 static_cast<int64_t>(Column.integer()) - 1};
91 Loc.range = {Pos, Pos};
92 }
93}
94
95void fillOptionDeclarationPositions(nix::EvalState &State, nix::Value &V,
97 State.forceValue(V, nix::noPos);
98 if (V.type() != nix::ValueType::nList)
99 return;
100 for (nix::Value *Item : V.listItems()) {
101 // Each item should have "column", "line", "file" fields.
103 fillUnsafeGetAttrPosLocation(State, *Item, Loc);
104 R.Declarations.emplace_back(std::move(Loc));
105 }
106}
107
108void fillOptionDeclarations(nix::EvalState &State, nix::Value &V,
110 // Eval declarations
111 try {
112 nix::Value &DeclarationPositions = nixt::selectAttr(
113 State, V, State.symbols.create("declarationPositions"));
114
115 State.forceValue(DeclarationPositions, nix::noPos);
116 // A list of positions, in unsafeGetAttrPos format.
117 fillOptionDeclarationPositions(State, DeclarationPositions, R);
118 } catch (nix::AttrPathNotFound &E) {
119 // FIXME: fallback to "declarations"
120 return;
121 }
122}
123
124void fillOptionType(nix::EvalState &State, nix::Value &VType, OptionType &R) {
125 fillString(State, VType, {"description"}, R.Description);
126 fillString(State, VType, {"name"}, R.Name);
127}
128
129void fillOptionDescription(nix::EvalState &State, nix::Value &V,
131 fillString(State, V, {"description"}, R.Description);
132 fillOptionDeclarations(State, V, R);
133 // FIXME: add definitions location.
134 if (V.type() == nix::ValueType::nAttrs) [[likely]] {
135 assert(V.attrs());
136 if (auto *It = V.attrs()->find(State.symbols.create("type"));
137 It != V.attrs()->end()) [[likely]] {
139 fillOptionType(State, *It->value, Type);
140 R.Type = std::move(Type);
141 }
142
143 if (auto *It = V.attrs()->find(State.symbols.create("example"));
144 It != V.attrs()->end()) {
145 State.forceValue(*It->value, It->pos);
146
147 // In nixpkgs some examples are nested in "literalExpression"
148 if (nixt::checkField(State, *It->value, "_type", "literalExpression")) {
149 R.Example = nixt::getFieldString(State, *It->value, "text");
150 } else {
151 std::ostringstream OS;
152 It->value->print(State, OS);
153 R.Example = OS.str();
154 }
155 }
156 }
157}
158
159} // namespace
160
161AttrSetProvider::AttrSetProvider(std::unique_ptr<InboundPort> In,
162 std::unique_ptr<OutboundPort> Out)
163 : LSPServer(std::move(In), std::move(Out)),
164 State(new nix::EvalState({}, nix::openStore(), nix::fetchSettings,
165 nix::evalSettings)) {
166 Registry.addMethod(rpcMethod::EvalExpr, this, &AttrSetProvider::onEvalExpr);
167 Registry.addMethod(rpcMethod::AttrPathInfo, this,
169 Registry.addMethod(rpcMethod::AttrPathComplete, this,
171 Registry.addMethod(rpcMethod::OptionInfo, this,
173 Registry.addMethod(rpcMethod::OptionComplete, this,
175}
176
178 const std::string &Name,
179 lspserver::Callback<std::optional<std::string>> Reply) {
180 try {
181 nix::Expr *AST = state().parseExprFromString(Name, state().rootPath("."));
182 state().eval(AST, Nixpkgs);
183 Reply(std::nullopt);
184 return;
185 } catch (const nix::BaseError &Err) {
186 Reply(error(Err.info().msg.str()));
187 return;
188 } catch (const std::exception &Err) {
189 Reply(error(Err.what()));
190 return;
191 }
192}
193
195 const AttrPathInfoParams &AttrPath,
197 using RespT = AttrPathInfoResponse;
198 Reply([&]() -> llvm::Expected<RespT> {
199 try {
200 if (AttrPath.empty())
201 return error("attrpath is empty!");
202
203 nix::Value &V = nixt::selectStrings(state(), Nixpkgs, AttrPath);
204 state().forceValue(V, nix::noPos);
205 return RespT{
206 .Meta = metadataOf(state(), V),
207 .PackageDesc = describePackage(state(), V),
208 };
209 } catch (const nix::BaseError &Err) {
210 return error(Err.info().msg.str());
211 } catch (const std::exception &Err) {
212 return error(Err.what());
213 }
214 }());
215}
216
218 const AttrPathCompleteParams &Params,
220 try {
221 nix::Value &Scope = nixt::selectStrings(state(), Nixpkgs, Params.Scope);
222
223 state().forceValue(Scope, nix::noPos);
224
225 if (Scope.type() != nix::ValueType::nAttrs) {
226 Reply(error("scope is not an attrset"));
227 return;
228 }
229
230 std::vector<std::string> Names;
231 int Num = 0;
232
233 // FIXME: we may want to use "Trie" to speedup the string searching.
234 // However as my (roughtly) profiling the critical in this loop is
235 // evaluating package details.
236 // "Trie"s may not beneficial becausae it cannot speedup eval.
237 for (const auto *AttrPtr :
238 Scope.attrs()->lexicographicOrder(state().symbols)) {
239 const nix::Attr &Attr = *AttrPtr;
240 const std::string_view Name = state().symbols[Attr.name];
241 if (Name.starts_with(Params.Prefix)) {
242 ++Num;
243 Names.emplace_back(Name);
244 // We set this a very limited number as to speedup
245 if (Num > MaxItems)
246 break;
247 }
248 }
249 Reply(std::move(Names));
250 return;
251 } catch (const nix::BaseError &Err) {
252 Reply(error(Err.info().msg.str()));
253 return;
254 } catch (const std::exception &Err) {
255 Reply(error(Err.what()));
256 return;
257 }
258}
259
261 const AttrPathInfoParams &AttrPath,
263 try {
264 if (AttrPath.empty()) {
265 Reply(error("attrpath is empty!"));
266 return;
267 }
268
269 nix::Value Option = nixt::selectOptions(
270 state(), Nixpkgs, nixt::toSymbols(state().symbols, AttrPath));
271
273
274 fillOptionDescription(state(), Option, R);
275
276 Reply(std::move(R));
277 return;
278 } catch (const nix::BaseError &Err) {
279 Reply(error(Err.info().msg.str()));
280 return;
281 } catch (const std::exception &Err) {
282 Reply(error(Err.what()));
283 return;
284 }
285}
286
288 const AttrPathCompleteParams &Params,
290 try {
291 nix::Value Scope = nixt::selectOptions(
292 state(), Nixpkgs, nixt::toSymbols(state().symbols, Params.Scope));
293
294 state().forceValue(Scope, nix::noPos);
295
296 if (Scope.type() != nix::ValueType::nAttrs) {
297 Reply(error("scope is not an attrset"));
298 return;
299 }
300
301 if (nixt::isOption(state(), Scope)) {
302 Reply(error("scope is already an option"));
303 return;
304 }
305
306 std::vector<OptionField> Response;
307
308 // FIXME: we may want to use "Trie" to speedup the string searching.
309 // However as my (roughtly) profiling the critical in this loop is
310 // evaluating package details.
311 // "Trie"s may not beneficial becausae it cannot speedup eval.
312 for (const auto *AttrPtr :
313 Scope.attrs()->lexicographicOrder(state().symbols)) {
314 const nix::Attr &Attr = *AttrPtr;
315 std::string_view Name = state().symbols[Attr.name];
316 if (Name.starts_with(Params.Prefix)) {
317 // Add a new "OptionField", see it's type.
318 assert(Attr.value);
319 OptionField NewField;
320 NewField.Name = Name;
321 if (nixt::isOption(state(), *Attr.value)) {
323 fillOptionDescription(state(), *Attr.value, Desc);
324 NewField.Description = std::move(Desc);
325 }
326 Response.emplace_back(std::move(NewField));
327 // We set this a very limited number as to speedup
328 if (Response.size() >= MaxItems)
329 break;
330 }
331 }
332 Reply(std::move(Response));
333 return;
334 } catch (const nix::BaseError &Err) {
335 Reply(error(Err.info().msg.str()));
336 return;
337 } catch (const std::exception &Err) {
338 Reply(error(Err.what()));
339 return;
340 }
341}
Dedicated worker for evaluating attrset.
Types used in nixpkgs provider.
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
Definition Kinds.h:6
constexpr std::string_view EvalExpr
Definition AttrSet.h:29
constexpr std::string_view OptionInfo
Definition AttrSet.h:32
constexpr std::string_view AttrPathInfo
Definition AttrSet.h:30
constexpr std::string_view OptionComplete
Definition AttrSet.h:33
constexpr std::string_view AttrPathComplete
Definition AttrSet.h:31
Selector AttrPathInfoParams
Definition AttrSet.h:47
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:96
std::optional< std::string > Description
Definition AttrSet.h:115
std::optional< std::string > Example
Definition AttrSet.h:118
std::optional< OptionType > Type
Definition AttrSet.h:119
std::vector< lspserver::Location > Declarations
Definition AttrSet.h:116
std::optional< OptionDescription > Description
Definition AttrSet.h:128
std::string Name
Definition AttrSet.h:127
std::optional< std::string > Description
Definition AttrSet.h:106
std::optional< std::string > Name
Definition AttrSet.h:107
std::optional< std::string > Name
Definition AttrSet.h:50
std::optional< std::string > Version
Definition AttrSet.h:52
std::optional< std::string > PName
Definition AttrSet.h:51
std::optional< std::string > Description
Definition AttrSet.h:53
std::optional< std::string > LongDescription
Definition AttrSet.h:54
std::optional< std::string > Position
Definition AttrSet.h:55
std::optional< std::string > Homepage
Definition AttrSet.h:56
General metadata of all nix::Values.
Definition AttrSet.h:64