nixd
Loading...
Searching...
No Matches
SemanticTokens.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of [Semantic Tokens].
3/// [Semantic Tokens]:
4/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens
5
6#include "Convert.h"
7
9
13#include <nixf/Basic/Range.h>
15
16#include <lspserver/Protocol.h>
17
18#include <boost/asio/post.hpp>
19
20using namespace nixd;
21using namespace lspserver;
22using namespace nixf;
23
24namespace {
25
26enum SemaType {
35 ST_Null,
36 ST_Bool,
40};
41
42enum SemaModifiers {
43 SM_Builtin = 1 << 0,
44 SM_Deprecated = 1 << 1,
45 SM_Dynamic = 1 << 2,
46};
47
48struct RawSemanticToken {
50 bool operator<(const RawSemanticToken &Other) const {
51 return Pos < Other.Pos;
52 }
53 unsigned Length;
54 unsigned TokenType;
55 unsigned TokenModifiers;
56};
57
58class SemanticTokenBuilder {
59
60 const VariableLookupAnalysis &VLA;
61
62 nixf::Position Previous = {0, 0};
63
64 std::vector<RawSemanticToken> Raw;
65
66 llvm::StringRef Src;
67
68public:
69 SemanticTokenBuilder(const VariableLookupAnalysis &VLA, llvm::StringRef Src)
70 : VLA(VLA), Src(Src) {}
71 void addImpl(nixf::LexerCursor Pos, unsigned Length, unsigned TokenType,
72 unsigned TokenModifiers) {
73 auto P = toLSPPosition(Src, Pos);
74 Raw.emplace_back(RawSemanticToken{P, Length, TokenType, TokenModifiers});
75 }
76
77 void add(const Node &N, unsigned TokenType, unsigned TokenModifiers) {
78 if (skip(N))
79 return;
80 addImpl(N.lCur(), len(N), TokenType, TokenModifiers);
81 }
82
83 static bool skip(const Node &N) {
84 // Skip cross-line strings.
85 return N.range().lCur().line() != N.range().rCur().line();
86 }
87
88 static unsigned len(const Node &N) {
89 return N.range().rCur().offset() - N.range().lCur().offset();
90 }
91
92 void dfs(const ExprString &Str) {
93 unsigned Modifers = 0;
94 if (!Str.isLiteral())
95 return;
96 add(Str, ST_String, Modifers);
97 }
98
99 void dfs(const ExprVar &Var) {
100 if (Var.id().name() == "true" || Var.id().name() == "false") {
101 add(Var, ST_Bool, SM_Builtin);
102 return;
103 }
104
105 if (Var.id().name() == "null") {
106 add(Var, ST_Null, 0);
107 return;
108 }
109
110 auto Result = VLA.query(Var);
112 if (Result.Def && Result.Def->isBuiltin()) {
114 return;
115 }
116 if (Result.Kind == ResultKind::Defined) {
117 add(Var, ST_Defined, 0);
118 return;
119 }
120 if (Result.Kind == ResultKind::FromWith) {
122 return;
123 }
124
126 }
127
128 void dfs(const ExprSelect &Select) {
129 dfs(&Select.expr());
130 dfs(Select.defaultExpr());
131 if (!Select.path())
132 return;
133 for (const std::shared_ptr<nixf::AttrName> &Name : Select.path()->names()) {
134 if (!Name)
135 continue;
136 const AttrName &AN = *Name;
137 if (AN.isStatic()) {
138 if (AN.kind() == AttrName::ANK_ID) {
139 add(AN, ST_Select, 0);
140 }
141 }
142 }
143 }
144
145 void dfs(const SemaAttrs &SA) {
146 for (const auto &[Name, Attr] : SA.staticAttrs()) {
147 if (!Attr.value())
148 continue;
149 // If this attribute comes from "inherit", don't mark it as "AttrName"
150 // The rationale behind this is that we don't want to mark same token
151 // twice because "inherit"ed names also create variables.
152 if (Attr.fromInherit())
153 continue;
154 add(Attr.key(), ST_AttrName, 0);
155 dfs(Attr.value());
156 }
157 for (const auto &Attr : SA.dynamicAttrs()) {
158 dfs(Attr.value());
159 }
160 }
161
162 void dfs(const LambdaArg &Arg) {
163 if (Arg.id())
164 add(*Arg.id(), ST_LambdaArg, 0);
165 // Color deduplicated formals.
166 if (Arg.formals())
167 for (const auto &[_, Formal] : Arg.formals()->dedup()) {
168 if (Formal->id()) {
169 add(*Formal->id(), ST_LambdaFormal, 0);
170 }
171 }
172 }
173
174 void dfs(const ExprLambda &Lambda) {
175 if (Lambda.arg()) {
176 dfs(*Lambda.arg());
177 }
178 dfs(Lambda.body());
179 }
180
181 void dfs(const Node *AST) {
182 if (!AST)
183 return;
184 switch (AST->kind()) {
185 case Node::NK_ExprLambda: {
186 const auto &Lambda = static_cast<const ExprLambda &>(*AST);
187 dfs(Lambda);
188 break;
189 }
190 case Node::NK_ExprString: {
191 const auto &Str = static_cast<const ExprString &>(*AST);
192 dfs(Str);
193 break;
194 }
195 case Node::NK_ExprVar: {
196 const auto &Var = static_cast<const ExprVar &>(*AST);
197 dfs(Var);
198 break;
199 }
200 case Node::NK_ExprSelect: {
201 const auto &Select = static_cast<const ExprSelect &>(*AST);
202 dfs(Select);
203 break;
204 }
205 case Node::NK_ExprAttrs: {
206 const SemaAttrs &SA = static_cast<const ExprAttrs &>(*AST).sema();
207 dfs(SA);
208 break;
209 }
210 default:
211 for (const Node *Ch : AST->children()) {
212 dfs(Ch);
213 }
214 }
215 };
216
217 std::vector<SemanticToken> finish() {
218 std::vector<SemanticToken> Tokens;
219 std::sort(Raw.begin(), Raw.end());
220 lspserver::Position Prev{0, 0};
221 for (auto Elm : Raw) {
222 assert(Elm.Pos.line - Prev.line >= 0);
223 unsigned DeltaLine = Elm.Pos.line - Prev.line;
224 unsigned DeltaCol =
225 DeltaLine ? Elm.Pos.character : Elm.Pos.character - Prev.character;
226 Prev = Elm.Pos;
227 Tokens.emplace_back(DeltaLine, DeltaCol, Elm.Length, Elm.TokenType,
228 Elm.TokenModifiers);
229 }
230 return Tokens;
231 }
232};
233
234} // namespace
235
236void Controller::onSemanticTokens(const SemanticTokensParams &Params,
238 auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
239 this]() mutable {
240 if (std::shared_ptr<NixTU> TU = getTU(URI.file().str(), Reply)) {
241 if (std::shared_ptr<Node> AST = getAST(*TU, Reply)) {
242 SemanticTokenBuilder Builder(*TU->variableLookup(), TU->src());
243 Builder.dfs(AST.get());
244 Reply(SemanticTokens{.tokens = Builder.finish()});
245 }
246 }
247 };
248 boost::asio::post(Pool, std::move(Action));
249}
Convert between LSP and nixf types.
Lookup variable names, from it's parent scope.
const SemaAttrs & sema() const
Definition Attrs.h:284
Identifier * id() const
Definition Lambda.h:37
Formals * formals() const
Definition Lambda.h:101
Identifier * id() const
Definition Lambda.h:99
A point in the source file.
Definition Range.h:57
NodeKind kind() const
Definition Basic.h:34
Attribute set after deduplication.
Definition Attrs.h:231
LookupResult query(const ExprVar &Var) const
Query the which name/with binds to specific varaible.
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::unique_function< void(llvm::Expected< T >)> Callback
Definition Function.h:14
bool operator<(const CompletionItem &, const CompletionItem &)
bool fromJSON(const llvm::json::Value &Params, Configuration::Diagnostic &R, llvm::json::Path P)
lspserver::Position toLSPPosition(llvm::StringRef Code, const nixf::LexerCursor &P)
Definition Convert.cpp:27
Body of textDocument/semanticTokens/full request.
std::vector< SemanticToken > tokens
The actual tokens.