nixd
Loading...
Searching...
No Matches
InlayHints.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of [Inlay Hints].
3/// [Inlay Hints]:
4/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint
5///
6/// In nixd, "Inlay Hints" are placed after each "package" node, showing it's
7/// version.
8///
9/// For example
10///
11/// nixd[: 1.2.3]
12/// nix[: 2.19.3]
13///
14///
15
16#include "AST.h"
17#include "CheckReturn.h"
18#include "Convert.h"
19
22
23#include <boost/asio/post.hpp>
24
25#include <llvm/ADT/StringRef.h>
26#include <llvm/Support/CommandLine.h>
27
28#include <semaphore>
29
30using namespace nixd;
31using namespace nixf;
32using namespace lspserver;
33using namespace llvm::cl;
34
35namespace {
36
37opt<bool> EnableInlayHints{"inlay-hints", desc("Enable/Disable inlay-hints"),
38 init(true), cat(NixdCategory)};
39
40/// Ask nixpkgs provider to compute package information, to get inlay-hints.
41class NixpkgsInlayHintsProvider {
42 AttrSetClient &NixpkgsProvider;
43 const VariableLookupAnalysis &VLA;
44 const ParentMapAnalysis &PMA;
45
46 /// Only positions contained in this range should be computed && added;
47 std::optional<nixf::PositionRange> Range;
48
49 std::vector<InlayHint> &Hints;
50
51 bool rangeOK(const nixf::PositionRange &R) {
52 if (!Range)
53 return true; // Always OK if there is no limitation.
54 return Range->contains(R);
55 }
56
57 llvm::StringRef Src;
58
59public:
60 NixpkgsInlayHintsProvider(AttrSetClient &NixpkgsProvider,
61 const VariableLookupAnalysis &VLA,
62 const ParentMapAnalysis &PMA,
63 std::optional<lspserver::Range> Range,
64 std::vector<InlayHint> &Hints, llvm::StringRef Src)
65 : NixpkgsProvider(NixpkgsProvider), VLA(VLA), PMA(PMA), Hints(Hints),
66 Src(Src) {
67 if (Range)
68 this->Range = toNixfRange(*Range);
69 }
70
71 void dfs(const Node *N) {
72 if (!N)
73 return;
74 if (N->kind() == Node::NK_ExprVar) {
75 if (havePackageScope(*N, VLA, PMA)) {
76 if (!rangeOK(N->positionRange()))
77 return;
78 // Ask nixpkgs eval to provide it's information.
79 // This is relatively slow. Maybe better query a set of packages in the
80 // future?
81 std::binary_semaphore Ready(0);
82 const std::string &Name = static_cast<const ExprVar &>(*N).id().name();
83 AttrPathInfoResponse R;
84 auto OnReply = [&Ready, &R](llvm::Expected<AttrPathInfoResponse> Resp) {
85 if (!Resp) {
86 Ready.release();
87 return;
88 }
89 R = *Resp;
90 Ready.release();
91 };
92 NixpkgsProvider.attrpathInfo({Name}, std::move(OnReply));
93 Ready.acquire();
94
95 if (const std::optional<std::string> &Version = R.PackageDesc.Version) {
96 // Construct inlay hints.
97 InlayHint H{
98 .position = toLSPPosition(Src, N->rCur()),
99 .label = ": " + *Version,
100 .kind = InlayHintKind::Designator,
101 .range = toLSPRange(Src, N->range()),
102 };
103 Hints.emplace_back(std::move(H));
104 }
105 }
106 }
107 // FIXME: process other node kinds. e.g. ExprSelect.
108 for (const Node *Ch : N->children())
109 dfs(Ch);
110 }
111};
112
113} // namespace
114
115void Controller::onInlayHint(const InlayHintsParams &Params,
116 Callback<std::vector<InlayHint>> Reply) {
117
118 using CheckTy = std::vector<InlayHint>;
119
120 // If not enabled, exit early.
121 if (!EnableInlayHints)
122 return Reply(std::vector<InlayHint>{});
123
124 auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
125 Range = Params.range, this]() mutable {
126 const auto File = URI.file();
127 return Reply([&]() -> llvm::Expected<CheckTy> {
128 const auto TU = CheckDefault(getTU(File));
129 const auto AST = CheckDefault(getAST(*TU));
130 // Perform inlay hints computation on the range.
131 std::vector<InlayHint> Response;
132 NixpkgsInlayHintsProvider NP(*nixpkgsClient(), *TU->variableLookup(),
133 *TU->parentMap(), Range, Response,
134 TU->src());
135 NP.dfs(AST.get());
136 return Response;
137 }());
138 };
139 boost::asio::post(Pool, std::move(Action));
140}
This file declares some common analysis (tree walk) on the AST.
#define CheckDefault(x)
Variant of CheckReturn, but returns default constructed CheckTy
Definition CheckReturn.h:16
Convert between LSP and nixf types.
NodeKind kind() const
Definition Basic.h:34
LexerCursor rCur() const
Definition Basic.h:38
PositionRange positionRange() const
Definition Basic.h:36
LexerCursorRange range() const
Definition Basic.h:35
virtual ChildVector children() const =0
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::unique_function< void(llvm::Expected< T >)> Callback
Definition Function.h:14
nixf::PositionRange toNixfRange(const lspserver::Range &P)
Definition Convert.cpp:36
llvm::cl::OptionCategory NixdCategory
lspserver::Position toLSPPosition(llvm::StringRef Code, const nixf::LexerCursor &P)
Definition Convert.cpp:27
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
A parameter literal used in inlay hint requests.
TextDocumentIdentifier textDocument
The text document.
PackageDescription PackageDesc
Package description of the attribute path, if available.
Definition AttrSet.h:86
std::optional< std::string > Version
Definition AttrSet.h:52