1 | //===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "ClangdLSPServer.h" |
10 | #include "ClangdServer.h" |
11 | #include "CodeComplete.h" |
12 | #include "CompileCommands.h" |
13 | #include "Diagnostics.h" |
14 | #include "Feature.h" |
15 | #include "GlobalCompilationDatabase.h" |
16 | #include "LSPBinder.h" |
17 | #include "Protocol.h" |
18 | #include "SemanticHighlighting.h" |
19 | #include "SourceCode.h" |
20 | #include "TUScheduler.h" |
21 | #include "URI.h" |
22 | #include "refactor/Tweak.h" |
23 | #include "support/Cancellation.h" |
24 | #include "support/Context.h" |
25 | #include "support/MemoryTree.h" |
26 | #include "support/Trace.h" |
27 | #include "clang/Tooling/Core/Replacement.h" |
28 | #include "llvm/ADT/ArrayRef.h" |
29 | #include "llvm/ADT/FunctionExtras.h" |
30 | #include "llvm/ADT/ScopeExit.h" |
31 | #include "llvm/ADT/StringRef.h" |
32 | #include "llvm/ADT/Twine.h" |
33 | #include "llvm/Support/Allocator.h" |
34 | #include "llvm/Support/Error.h" |
35 | #include "llvm/Support/FormatVariadic.h" |
36 | #include "llvm/Support/JSON.h" |
37 | #include "llvm/Support/SHA1.h" |
38 | #include "llvm/Support/ScopedPrinter.h" |
39 | #include "llvm/Support/raw_ostream.h" |
40 | #include <chrono> |
41 | #include <cstddef> |
42 | #include <cstdint> |
43 | #include <functional> |
44 | #include <map> |
45 | #include <memory> |
46 | #include <mutex> |
47 | #include <optional> |
48 | #include <string> |
49 | #include <utility> |
50 | #include <vector> |
51 | |
52 | namespace clang { |
53 | namespace clangd { |
54 | namespace { |
55 | // Tracks end-to-end latency of high level lsp calls. Measurements are in |
56 | // seconds. |
57 | constexpr trace::Metric LSPLatency("lsp_latency" , trace::Metric::Distribution, |
58 | "method_name" ); |
59 | |
60 | // LSP defines file versions as numbers that increase. |
61 | // ClangdServer treats them as opaque and therefore uses strings instead. |
62 | std::string encodeVersion(std::optional<int64_t> LSPVersion) { |
63 | return LSPVersion ? llvm::to_string(Value: *LSPVersion) : "" ; |
64 | } |
65 | std::optional<int64_t> decodeVersion(llvm::StringRef Encoded) { |
66 | int64_t Result; |
67 | if (llvm::to_integer(S: Encoded, Num&: Result, Base: 10)) |
68 | return Result; |
69 | if (!Encoded.empty()) // Empty can be e.g. diagnostics on close. |
70 | elog(Fmt: "unexpected non-numeric version {0}" , Vals&: Encoded); |
71 | return std::nullopt; |
72 | } |
73 | |
74 | const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix" ; |
75 | const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak" ; |
76 | |
77 | /// Transforms a tweak into a code action that would apply it if executed. |
78 | /// EXPECTS: T.prepare() was called and returned true. |
79 | CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File, |
80 | Range Selection) { |
81 | CodeAction CA; |
82 | CA.title = T.Title; |
83 | CA.kind = T.Kind.str(); |
84 | // This tweak may have an expensive second stage, we only run it if the user |
85 | // actually chooses it in the UI. We reply with a command that would run the |
86 | // corresponding tweak. |
87 | // FIXME: for some tweaks, computing the edits is cheap and we could send them |
88 | // directly. |
89 | CA.command.emplace(); |
90 | CA.command->title = T.Title; |
91 | CA.command->command = std::string(ApplyTweakCommand); |
92 | TweakArgs Args; |
93 | Args.file = File; |
94 | Args.tweakID = T.ID; |
95 | Args.selection = Selection; |
96 | CA.command->argument = std::move(Args); |
97 | return CA; |
98 | } |
99 | |
100 | /// Convert from Fix to LSP CodeAction. |
101 | CodeAction toCodeAction(const Fix &F, const URIForFile &File, |
102 | const std::optional<int64_t> &Version, |
103 | bool SupportsDocumentChanges, |
104 | bool SupportChangeAnnotation) { |
105 | CodeAction Action; |
106 | Action.title = F.Message; |
107 | Action.kind = std::string(CodeAction::QUICKFIX_KIND); |
108 | Action.edit.emplace(); |
109 | if (!SupportsDocumentChanges) { |
110 | Action.edit->changes.emplace(); |
111 | auto &Changes = (*Action.edit->changes)[File.uri()]; |
112 | for (const auto &E : F.Edits) |
113 | Changes.push_back(x: {.range: E.range, .newText: E.newText, /*annotationId=*/"" }); |
114 | } else { |
115 | Action.edit->documentChanges.emplace(); |
116 | TextDocumentEdit &Edit = Action.edit->documentChanges->emplace_back(); |
117 | Edit.textDocument = VersionedTextDocumentIdentifier{{.uri: File}, .version: Version}; |
118 | for (const auto &E : F.Edits) |
119 | Edit.edits.push_back( |
120 | x: {.range: E.range, .newText: E.newText, |
121 | .annotationId: SupportChangeAnnotation ? E.annotationId : "" }); |
122 | if (SupportChangeAnnotation) { |
123 | for (const auto &[AID, Annotation]: F.Annotations) |
124 | Action.edit->changeAnnotations[AID] = Annotation; |
125 | } |
126 | } |
127 | return Action; |
128 | } |
129 | |
130 | void adjustSymbolKinds(llvm::MutableArrayRef<DocumentSymbol> Syms, |
131 | SymbolKindBitset Kinds) { |
132 | for (auto &S : Syms) { |
133 | S.kind = adjustKindToCapability(Kind: S.kind, supportedSymbolKinds&: Kinds); |
134 | adjustSymbolKinds(Syms: S.children, Kinds); |
135 | } |
136 | } |
137 | |
138 | SymbolKindBitset defaultSymbolKinds() { |
139 | SymbolKindBitset Defaults; |
140 | for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array); |
141 | ++I) |
142 | Defaults.set(position: I); |
143 | return Defaults; |
144 | } |
145 | |
146 | CompletionItemKindBitset defaultCompletionItemKinds() { |
147 | CompletionItemKindBitset Defaults; |
148 | for (size_t I = CompletionItemKindMin; |
149 | I <= static_cast<size_t>(CompletionItemKind::Reference); ++I) |
150 | Defaults.set(position: I); |
151 | return Defaults; |
152 | } |
153 | |
154 | // Makes sure edits in \p FE are applicable to latest file contents reported by |
155 | // editor. If not generates an error message containing information about files |
156 | // that needs to be saved. |
157 | llvm::Error validateEdits(const ClangdServer &Server, const FileEdits &FE) { |
158 | size_t InvalidFileCount = 0; |
159 | llvm::StringRef LastInvalidFile; |
160 | for (const auto &It : FE) { |
161 | if (auto Draft = Server.getDraft(File: It.first())) { |
162 | // If the file is open in user's editor, make sure the version we |
163 | // saw and current version are compatible as this is the text that |
164 | // will be replaced by editors. |
165 | if (!It.second.canApplyTo(Code: *Draft)) { |
166 | ++InvalidFileCount; |
167 | LastInvalidFile = It.first(); |
168 | } |
169 | } |
170 | } |
171 | if (!InvalidFileCount) |
172 | return llvm::Error::success(); |
173 | if (InvalidFileCount == 1) |
174 | return error(Fmt: "File must be saved first: {0}" , Vals&: LastInvalidFile); |
175 | return error(Fmt: "Files must be saved first: {0} (and {1} others)" , |
176 | Vals&: LastInvalidFile, Vals: InvalidFileCount - 1); |
177 | } |
178 | } // namespace |
179 | |
180 | // MessageHandler dispatches incoming LSP messages. |
181 | // It handles cross-cutting concerns: |
182 | // - serializes/deserializes protocol objects to JSON |
183 | // - logging of inbound messages |
184 | // - cancellation handling |
185 | // - basic call tracing |
186 | // MessageHandler ensures that initialize() is called before any other handler. |
187 | class ClangdLSPServer::MessageHandler : public Transport::MessageHandler { |
188 | public: |
189 | MessageHandler(ClangdLSPServer &Server) : Server(Server) {} |
190 | |
191 | bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override { |
192 | trace::Span Tracer(Method, LSPLatency); |
193 | SPAN_ATTACH(Tracer, "Params" , Params); |
194 | WithContext HandlerContext(handlerContext()); |
195 | log(Fmt: "<-- {0}" , Vals&: Method); |
196 | if (Method == "exit" ) |
197 | return false; |
198 | auto Handler = Server.Handlers.NotificationHandlers.find(Key: Method); |
199 | if (Handler != Server.Handlers.NotificationHandlers.end()) { |
200 | Handler->second(std::move(Params)); |
201 | Server.maybeExportMemoryProfile(); |
202 | Server.maybeCleanupMemory(); |
203 | } else if (!Server.Server) { |
204 | elog(Fmt: "Notification {0} before initialization" , Vals&: Method); |
205 | } else if (Method == "$/cancelRequest" ) { |
206 | onCancel(Params: std::move(Params)); |
207 | } else { |
208 | log(Fmt: "unhandled notification {0}" , Vals&: Method); |
209 | } |
210 | return true; |
211 | } |
212 | |
213 | bool onCall(llvm::StringRef Method, llvm::json::Value Params, |
214 | llvm::json::Value ID) override { |
215 | WithContext HandlerContext(handlerContext()); |
216 | // Calls can be canceled by the client. Add cancellation context. |
217 | WithContext WithCancel(cancelableRequestContext(ID)); |
218 | trace::Span Tracer(Method, LSPLatency); |
219 | SPAN_ATTACH(Tracer, "Params" , Params); |
220 | ReplyOnce Reply(ID, Method, &Server, Tracer.Args); |
221 | log(Fmt: "<-- {0}({1})" , Vals&: Method, Vals&: ID); |
222 | auto Handler = Server.Handlers.MethodHandlers.find(Key: Method); |
223 | if (Handler != Server.Handlers.MethodHandlers.end()) { |
224 | Handler->second(std::move(Params), std::move(Reply)); |
225 | } else if (!Server.Server) { |
226 | elog(Fmt: "Call {0} before initialization." , Vals&: Method); |
227 | Reply(llvm::make_error<LSPError>(Args: "server not initialized" , |
228 | Args: ErrorCode::ServerNotInitialized)); |
229 | } else { |
230 | Reply(llvm::make_error<LSPError>(Args: "method not found" , |
231 | Args: ErrorCode::MethodNotFound)); |
232 | } |
233 | return true; |
234 | } |
235 | |
236 | bool onReply(llvm::json::Value ID, |
237 | llvm::Expected<llvm::json::Value> Result) override { |
238 | WithContext HandlerContext(handlerContext()); |
239 | |
240 | Callback<llvm::json::Value> ReplyHandler = nullptr; |
241 | if (auto IntID = ID.getAsInteger()) { |
242 | std::lock_guard<std::mutex> Mutex(CallMutex); |
243 | // Find a corresponding callback for the request ID; |
244 | for (size_t Index = 0; Index < ReplyCallbacks.size(); ++Index) { |
245 | if (ReplyCallbacks[Index].first == *IntID) { |
246 | ReplyHandler = std::move(ReplyCallbacks[Index].second); |
247 | ReplyCallbacks.erase(position: ReplyCallbacks.begin() + |
248 | Index); // remove the entry |
249 | break; |
250 | } |
251 | } |
252 | } |
253 | |
254 | if (!ReplyHandler) { |
255 | // No callback being found, use a default log callback. |
256 | ReplyHandler = [&ID](llvm::Expected<llvm::json::Value> Result) { |
257 | elog(Fmt: "received a reply with ID {0}, but there was no such call" , Vals&: ID); |
258 | if (!Result) |
259 | llvm::consumeError(Err: Result.takeError()); |
260 | }; |
261 | } |
262 | |
263 | // Log and run the reply handler. |
264 | if (Result) { |
265 | log(Fmt: "<-- reply({0})" , Vals&: ID); |
266 | ReplyHandler(std::move(Result)); |
267 | } else { |
268 | auto Err = Result.takeError(); |
269 | log(Fmt: "<-- reply({0}) error: {1}" , Vals&: ID, Vals&: Err); |
270 | ReplyHandler(std::move(Err)); |
271 | } |
272 | return true; |
273 | } |
274 | |
275 | // Bind a reply callback to a request. The callback will be invoked when |
276 | // clangd receives the reply from the LSP client. |
277 | // Return a call id of the request. |
278 | llvm::json::Value bindReply(Callback<llvm::json::Value> Reply) { |
279 | std::optional<std::pair<int, Callback<llvm::json::Value>>> OldestCB; |
280 | int ID; |
281 | { |
282 | std::lock_guard<std::mutex> Mutex(CallMutex); |
283 | ID = NextCallID++; |
284 | ReplyCallbacks.emplace_back(args&: ID, args: std::move(Reply)); |
285 | |
286 | // If the queue overflows, we assume that the client didn't reply the |
287 | // oldest request, and run the corresponding callback which replies an |
288 | // error to the client. |
289 | if (ReplyCallbacks.size() > MaxReplayCallbacks) { |
290 | elog(Fmt: "more than {0} outstanding LSP calls, forgetting about {1}" , |
291 | Vals: MaxReplayCallbacks, Vals&: ReplyCallbacks.front().first); |
292 | OldestCB = std::move(ReplyCallbacks.front()); |
293 | ReplyCallbacks.pop_front(); |
294 | } |
295 | } |
296 | if (OldestCB) |
297 | OldestCB->second( |
298 | error(Fmt: "failed to receive a client reply for request ({0})" , |
299 | Vals&: OldestCB->first)); |
300 | return ID; |
301 | } |
302 | |
303 | private: |
304 | // Function object to reply to an LSP call. |
305 | // Each instance must be called exactly once, otherwise: |
306 | // - the bug is logged, and (in debug mode) an assert will fire |
307 | // - if there was no reply, an error reply is sent |
308 | // - if there were multiple replies, only the first is sent |
309 | class ReplyOnce { |
310 | std::atomic<bool> Replied = {false}; |
311 | std::chrono::steady_clock::time_point Start; |
312 | llvm::json::Value ID; |
313 | std::string Method; |
314 | ClangdLSPServer *Server; // Null when moved-from. |
315 | llvm::json::Object *TraceArgs; |
316 | |
317 | public: |
318 | ReplyOnce(const llvm::json::Value &ID, llvm::StringRef Method, |
319 | ClangdLSPServer *Server, llvm::json::Object *TraceArgs) |
320 | : Start(std::chrono::steady_clock::now()), ID(ID), Method(Method), |
321 | Server(Server), TraceArgs(TraceArgs) { |
322 | assert(Server); |
323 | } |
324 | ReplyOnce(ReplyOnce &&Other) |
325 | : Replied(Other.Replied.load()), Start(Other.Start), |
326 | ID(std::move(Other.ID)), Method(std::move(Other.Method)), |
327 | Server(Other.Server), TraceArgs(Other.TraceArgs) { |
328 | Other.Server = nullptr; |
329 | } |
330 | ReplyOnce &operator=(ReplyOnce &&) = delete; |
331 | ReplyOnce(const ReplyOnce &) = delete; |
332 | ReplyOnce &operator=(const ReplyOnce &) = delete; |
333 | |
334 | ~ReplyOnce() { |
335 | // There's one legitimate reason to never reply to a request: clangd's |
336 | // request handler send a call to the client (e.g. applyEdit) and the |
337 | // client never replied. In this case, the ReplyOnce is owned by |
338 | // ClangdLSPServer's reply callback table and is destroyed along with the |
339 | // server. We don't attempt to send a reply in this case, there's little |
340 | // to be gained from doing so. |
341 | if (Server && !Server->IsBeingDestroyed && !Replied) { |
342 | elog(Fmt: "No reply to message {0}({1})" , Vals&: Method, Vals&: ID); |
343 | assert(false && "must reply to all calls!" ); |
344 | (*this)(llvm::make_error<LSPError>(Args: "server failed to reply" , |
345 | Args: ErrorCode::InternalError)); |
346 | } |
347 | } |
348 | |
349 | void operator()(llvm::Expected<llvm::json::Value> Reply) { |
350 | assert(Server && "moved-from!" ); |
351 | if (Replied.exchange(i: true)) { |
352 | elog(Fmt: "Replied twice to message {0}({1})" , Vals&: Method, Vals&: ID); |
353 | assert(false && "must reply to each call only once!" ); |
354 | return; |
355 | } |
356 | auto Duration = std::chrono::steady_clock::now() - Start; |
357 | if (Reply) { |
358 | log(Fmt: "--> reply:{0}({1}) {2:ms}" , Vals&: Method, Vals&: ID, Vals&: Duration); |
359 | if (TraceArgs) |
360 | (*TraceArgs)["Reply" ] = *Reply; |
361 | std::lock_guard<std::mutex> Lock(Server->TranspWriter); |
362 | Server->Transp.reply(ID: std::move(ID), Result: std::move(Reply)); |
363 | } else { |
364 | llvm::Error Err = Reply.takeError(); |
365 | log(Fmt: "--> reply:{0}({1}) {2:ms}, error: {3}" , Vals&: Method, Vals&: ID, Vals&: Duration, Vals&: Err); |
366 | if (TraceArgs) |
367 | (*TraceArgs)["Error" ] = llvm::to_string(Value: Err); |
368 | std::lock_guard<std::mutex> Lock(Server->TranspWriter); |
369 | Server->Transp.reply(ID: std::move(ID), Result: std::move(Err)); |
370 | } |
371 | } |
372 | }; |
373 | |
374 | // Method calls may be cancelled by ID, so keep track of their state. |
375 | // This needs a mutex: handlers may finish on a different thread, and that's |
376 | // when we clean up entries in the map. |
377 | mutable std::mutex RequestCancelersMutex; |
378 | llvm::StringMap<std::pair<Canceler, /*Cookie*/ unsigned>> RequestCancelers; |
379 | unsigned NextRequestCookie = 0; // To disambiguate reused IDs, see below. |
380 | void onCancel(const llvm::json::Value &Params) { |
381 | const llvm::json::Value *ID = nullptr; |
382 | if (auto *O = Params.getAsObject()) |
383 | ID = O->get(K: "id" ); |
384 | if (!ID) { |
385 | elog(Fmt: "Bad cancellation request: {0}" , Vals: Params); |
386 | return; |
387 | } |
388 | auto StrID = llvm::to_string(Value: *ID); |
389 | std::lock_guard<std::mutex> Lock(RequestCancelersMutex); |
390 | auto It = RequestCancelers.find(Key: StrID); |
391 | if (It != RequestCancelers.end()) |
392 | It->second.first(); // Invoke the canceler. |
393 | } |
394 | |
395 | Context handlerContext() const { |
396 | return Context::current().derive( |
397 | Key: kCurrentOffsetEncoding, |
398 | Value: Server.Opts.Encoding.value_or(u: OffsetEncoding::UTF16)); |
399 | } |
400 | |
401 | // We run cancelable requests in a context that does two things: |
402 | // - allows cancellation using RequestCancelers[ID] |
403 | // - cleans up the entry in RequestCancelers when it's no longer needed |
404 | // If a client reuses an ID, the last wins and the first cannot be canceled. |
405 | Context cancelableRequestContext(const llvm::json::Value &ID) { |
406 | auto Task = cancelableTask( |
407 | /*Reason=*/static_cast<int>(ErrorCode::RequestCancelled)); |
408 | auto StrID = llvm::to_string(Value: ID); // JSON-serialize ID for map key. |
409 | auto Cookie = NextRequestCookie++; // No lock, only called on main thread. |
410 | { |
411 | std::lock_guard<std::mutex> Lock(RequestCancelersMutex); |
412 | RequestCancelers[StrID] = {std::move(Task.second), Cookie}; |
413 | } |
414 | // When the request ends, we can clean up the entry we just added. |
415 | // The cookie lets us check that it hasn't been overwritten due to ID |
416 | // reuse. |
417 | return Task.first.derive(Value: llvm::make_scope_exit(F: [this, StrID, Cookie] { |
418 | std::lock_guard<std::mutex> Lock(RequestCancelersMutex); |
419 | auto It = RequestCancelers.find(Key: StrID); |
420 | if (It != RequestCancelers.end() && It->second.second == Cookie) |
421 | RequestCancelers.erase(I: It); |
422 | })); |
423 | } |
424 | |
425 | // The maximum number of callbacks held in clangd. |
426 | // |
427 | // We bound the maximum size to the pending map to prevent memory leakage |
428 | // for cases where LSP clients don't reply for the request. |
429 | // This has to go after RequestCancellers and RequestCancellersMutex since it |
430 | // can contain a callback that has a cancelable context. |
431 | static constexpr int MaxReplayCallbacks = 100; |
432 | mutable std::mutex CallMutex; |
433 | int NextCallID = 0; /* GUARDED_BY(CallMutex) */ |
434 | std::deque<std::pair</*RequestID*/ int, |
435 | /*ReplyHandler*/ Callback<llvm::json::Value>>> |
436 | ReplyCallbacks; /* GUARDED_BY(CallMutex) */ |
437 | |
438 | ClangdLSPServer &Server; |
439 | }; |
440 | constexpr int ClangdLSPServer::MessageHandler::MaxReplayCallbacks; |
441 | |
442 | // call(), notify(), and reply() wrap the Transport, adding logging and locking. |
443 | void ClangdLSPServer::callMethod(StringRef Method, llvm::json::Value Params, |
444 | Callback<llvm::json::Value> CB) { |
445 | auto ID = MsgHandler->bindReply(Reply: std::move(CB)); |
446 | log(Fmt: "--> {0}({1})" , Vals&: Method, Vals&: ID); |
447 | std::lock_guard<std::mutex> Lock(TranspWriter); |
448 | Transp.call(Method, Params: std::move(Params), ID); |
449 | } |
450 | |
451 | void ClangdLSPServer::notify(llvm::StringRef Method, llvm::json::Value Params) { |
452 | log(Fmt: "--> {0}" , Vals&: Method); |
453 | maybeCleanupMemory(); |
454 | std::lock_guard<std::mutex> Lock(TranspWriter); |
455 | Transp.notify(Method, Params: std::move(Params)); |
456 | } |
457 | |
458 | static std::vector<llvm::StringRef> semanticTokenTypes() { |
459 | std::vector<llvm::StringRef> Types; |
460 | for (unsigned I = 0; I <= static_cast<unsigned>(HighlightingKind::LastKind); |
461 | ++I) |
462 | Types.push_back(x: toSemanticTokenType(Kind: static_cast<HighlightingKind>(I))); |
463 | return Types; |
464 | } |
465 | |
466 | static std::vector<llvm::StringRef> semanticTokenModifiers() { |
467 | std::vector<llvm::StringRef> Modifiers; |
468 | for (unsigned I = 0; |
469 | I <= static_cast<unsigned>(HighlightingModifier::LastModifier); ++I) |
470 | Modifiers.push_back( |
471 | x: toSemanticTokenModifier(Modifier: static_cast<HighlightingModifier>(I))); |
472 | return Modifiers; |
473 | } |
474 | |
475 | void ClangdLSPServer::onInitialize(const InitializeParams &Params, |
476 | Callback<llvm::json::Value> Reply) { |
477 | // Determine character encoding first as it affects constructed ClangdServer. |
478 | if (Params.capabilities.offsetEncoding && !Opts.Encoding) { |
479 | Opts.Encoding = OffsetEncoding::UTF16; // fallback |
480 | for (OffsetEncoding Supported : *Params.capabilities.offsetEncoding) |
481 | if (Supported != OffsetEncoding::UnsupportedEncoding) { |
482 | Opts.Encoding = Supported; |
483 | break; |
484 | } |
485 | } |
486 | |
487 | if (Params.capabilities.TheiaSemanticHighlighting && |
488 | !Params.capabilities.SemanticTokens) { |
489 | elog(Fmt: "Client requested legacy semanticHighlights notification, which is " |
490 | "no longer supported. Migrate to standard semanticTokens request" ); |
491 | } |
492 | |
493 | if (Params.rootUri && *Params.rootUri) |
494 | Opts.WorkspaceRoot = std::string(Params.rootUri->file()); |
495 | else if (Params.rootPath && !Params.rootPath->empty()) |
496 | Opts.WorkspaceRoot = *Params.rootPath; |
497 | if (Server) |
498 | return Reply(llvm::make_error<LSPError>(Args: "server already initialized" , |
499 | Args: ErrorCode::InvalidRequest)); |
500 | |
501 | Opts.CodeComplete.EnableSnippets = Params.capabilities.CompletionSnippets; |
502 | Opts.CodeComplete.IncludeFixIts = Params.capabilities.CompletionFixes; |
503 | if (!Opts.CodeComplete.BundleOverloads) |
504 | Opts.CodeComplete.BundleOverloads = Params.capabilities.HasSignatureHelp; |
505 | Opts.CodeComplete.DocumentationFormat = |
506 | Params.capabilities.CompletionDocumentationFormat; |
507 | Opts.SignatureHelpDocumentationFormat = |
508 | Params.capabilities.SignatureHelpDocumentationFormat; |
509 | DiagOpts.EmbedFixesInDiagnostics = Params.capabilities.DiagnosticFixes; |
510 | DiagOpts.SendDiagnosticCategory = Params.capabilities.DiagnosticCategory; |
511 | DiagOpts.EmitRelatedLocations = |
512 | Params.capabilities.DiagnosticRelatedInformation; |
513 | if (Params.capabilities.WorkspaceSymbolKinds) |
514 | SupportedSymbolKinds |= *Params.capabilities.WorkspaceSymbolKinds; |
515 | if (Params.capabilities.CompletionItemKinds) |
516 | SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds; |
517 | SupportsCompletionLabelDetails = Params.capabilities.CompletionLabelDetail; |
518 | SupportsCodeAction = Params.capabilities.CodeActionStructure; |
519 | SupportsHierarchicalDocumentSymbol = |
520 | Params.capabilities.HierarchicalDocumentSymbol; |
521 | SupportsReferenceContainer = Params.capabilities.ReferenceContainer; |
522 | SupportFileStatus = Params.initializationOptions.FileStatus; |
523 | SupportsDocumentChanges = Params.capabilities.DocumentChanges; |
524 | SupportsChangeAnnotation = Params.capabilities.ChangeAnnotation; |
525 | HoverContentFormat = Params.capabilities.HoverContentFormat; |
526 | Opts.LineFoldingOnly = Params.capabilities.LineFoldingOnly; |
527 | SupportsOffsetsInSignatureHelp = Params.capabilities.OffsetsInSignatureHelp; |
528 | if (Params.capabilities.WorkDoneProgress) |
529 | BackgroundIndexProgressState = BackgroundIndexProgress::Empty; |
530 | BackgroundIndexSkipCreate = Params.capabilities.ImplicitProgressCreation; |
531 | Opts.ImplicitCancellation = !Params.capabilities.CancelsStaleRequests; |
532 | Opts.PublishInactiveRegions = Params.capabilities.InactiveRegions; |
533 | |
534 | if (Opts.UseDirBasedCDB) { |
535 | DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS); |
536 | if (const auto &Dir = Params.initializationOptions.compilationDatabasePath) |
537 | CDBOpts.CompileCommandsDir = Dir; |
538 | CDBOpts.ContextProvider = Opts.ContextProvider; |
539 | BaseCDB = |
540 | std::make_unique<DirectoryBasedGlobalCompilationDatabase>(args&: CDBOpts); |
541 | } |
542 | auto Mangler = CommandMangler::detect(); |
543 | Mangler.SystemIncludeExtractor = |
544 | getSystemIncludeExtractor(QueryDriverGlobs: llvm::ArrayRef(Opts.QueryDriverGlobs)); |
545 | if (Opts.ResourceDir) |
546 | Mangler.ResourceDir = *Opts.ResourceDir; |
547 | CDB.emplace(args: BaseCDB.get(), args: Params.initializationOptions.fallbackFlags, |
548 | args: std::move(Mangler)); |
549 | { |
550 | // Switch caller's context with LSPServer's background context. Since we |
551 | // rather want to propagate information from LSPServer's context into the |
552 | // Server, CDB, etc. |
553 | WithContext MainContext(BackgroundContext.clone()); |
554 | std::optional<WithContextValue> WithOffsetEncoding; |
555 | if (Opts.Encoding) |
556 | WithOffsetEncoding.emplace(args&: kCurrentOffsetEncoding, args&: *Opts.Encoding); |
557 | Server.emplace(args&: *CDB, args: TFS, args&: Opts, |
558 | args: static_cast<ClangdServer::Callbacks *>(this)); |
559 | } |
560 | |
561 | llvm::json::Object ServerCaps{ |
562 | {.K: "textDocumentSync" , |
563 | .V: llvm::json::Object{ |
564 | {.K: "openClose" , .V: true}, |
565 | {.K: "change" , .V: (int)TextDocumentSyncKind::Incremental}, |
566 | {.K: "save" , .V: true}, |
567 | }}, |
568 | {.K: "documentFormattingProvider" , .V: true}, |
569 | {.K: "documentRangeFormattingProvider" , .V: true}, |
570 | {.K: "documentOnTypeFormattingProvider" , |
571 | .V: llvm::json::Object{ |
572 | {.K: "firstTriggerCharacter" , .V: "\n" }, |
573 | {.K: "moreTriggerCharacter" , .V: {}}, |
574 | }}, |
575 | {.K: "completionProvider" , |
576 | .V: llvm::json::Object{ |
577 | // We don't set `(` etc as allCommitCharacters as they interact |
578 | // poorly with snippet results. |
579 | // See https://github.com/clangd/vscode-clangd/issues/357 |
580 | // Hopefully we can use them one day without this side-effect: |
581 | // https://github.com/microsoft/vscode/issues/42544 |
582 | {.K: "resolveProvider" , .V: false}, |
583 | // We do extra checks, e.g. that > is part of ->. |
584 | {.K: "triggerCharacters" , .V: {"." , "<" , ">" , ":" , "\"" , "/" , "*" }}, |
585 | }}, |
586 | {.K: "semanticTokensProvider" , |
587 | .V: llvm::json::Object{ |
588 | {.K: "full" , .V: llvm::json::Object{{.K: "delta" , .V: true}}}, |
589 | {.K: "range" , .V: false}, |
590 | {.K: "legend" , |
591 | .V: llvm::json::Object{{.K: "tokenTypes" , .V: semanticTokenTypes()}, |
592 | {.K: "tokenModifiers" , .V: semanticTokenModifiers()}}}, |
593 | }}, |
594 | {.K: "signatureHelpProvider" , |
595 | .V: llvm::json::Object{ |
596 | {.K: "triggerCharacters" , .V: {"(" , ")" , "{" , "}" , "<" , ">" , "," }}, |
597 | }}, |
598 | {.K: "declarationProvider" , .V: true}, |
599 | {.K: "definitionProvider" , .V: true}, |
600 | {.K: "implementationProvider" , .V: true}, |
601 | {.K: "typeDefinitionProvider" , .V: true}, |
602 | {.K: "documentHighlightProvider" , .V: true}, |
603 | {.K: "documentLinkProvider" , |
604 | .V: llvm::json::Object{ |
605 | {.K: "resolveProvider" , .V: false}, |
606 | }}, |
607 | {.K: "hoverProvider" , .V: true}, |
608 | {.K: "selectionRangeProvider" , .V: true}, |
609 | {.K: "documentSymbolProvider" , .V: true}, |
610 | {.K: "workspaceSymbolProvider" , .V: true}, |
611 | {.K: "referencesProvider" , .V: true}, |
612 | {.K: "astProvider" , .V: true}, // clangd extension |
613 | {.K: "typeHierarchyProvider" , .V: true}, |
614 | // Unfortunately our extension made use of the same capability name as the |
615 | // standard. Advertise this capability to tell clients that implement our |
616 | // extension we really have support for the standardized one as well. |
617 | {.K: "standardTypeHierarchyProvider" , .V: true}, // clangd extension |
618 | {.K: "memoryUsageProvider" , .V: true}, // clangd extension |
619 | {.K: "compilationDatabase" , // clangd extension |
620 | .V: llvm::json::Object{{.K: "automaticReload" , .V: true}}}, |
621 | {.K: "inactiveRegionsProvider" , .V: true}, // clangd extension |
622 | {.K: "callHierarchyProvider" , .V: true}, |
623 | {.K: "clangdInlayHintsProvider" , .V: true}, |
624 | {.K: "inlayHintProvider" , .V: true}, |
625 | {.K: "foldingRangeProvider" , .V: true}, |
626 | }; |
627 | |
628 | { |
629 | LSPBinder Binder(Handlers, *this); |
630 | bindMethods(Binder, Caps: Params.capabilities); |
631 | if (Opts.FeatureModules) |
632 | for (auto &Mod : *Opts.FeatureModules) |
633 | Mod.initializeLSP(Bind&: Binder, ClientCaps: Params.rawCapabilities, ServerCaps); |
634 | } |
635 | |
636 | // Per LSP, renameProvider can be either boolean or RenameOptions. |
637 | // RenameOptions will be specified if the client states it supports prepare. |
638 | ServerCaps["renameProvider" ] = |
639 | Params.capabilities.RenamePrepareSupport |
640 | ? llvm::json::Object{{.K: "prepareProvider" , .V: true}} |
641 | : llvm::json::Value(true); |
642 | |
643 | // Per LSP, codeActionProvider can be either boolean or CodeActionOptions. |
644 | // CodeActionOptions is only valid if the client supports action literal |
645 | // via textDocument.codeAction.codeActionLiteralSupport. |
646 | ServerCaps["codeActionProvider" ] = |
647 | Params.capabilities.CodeActionStructure |
648 | ? llvm::json::Object{{.K: "codeActionKinds" , |
649 | .V: {CodeAction::QUICKFIX_KIND, |
650 | CodeAction::REFACTOR_KIND, |
651 | CodeAction::INFO_KIND}}} |
652 | : llvm::json::Value(true); |
653 | |
654 | std::vector<llvm::StringRef> Commands; |
655 | for (llvm::StringRef Command : Handlers.CommandHandlers.keys()) |
656 | Commands.push_back(x: Command); |
657 | llvm::sort(C&: Commands); |
658 | ServerCaps["executeCommandProvider" ] = |
659 | llvm::json::Object{{.K: "commands" , .V: Commands}}; |
660 | |
661 | llvm::json::Object Result{ |
662 | {{.K: "serverInfo" , |
663 | .V: llvm::json::Object{ |
664 | {.K: "name" , .V: "clangd" }, |
665 | {.K: "version" , .V: llvm::formatv(Fmt: "{0} {1} {2}" , Vals: versionString(), |
666 | Vals: featureString(), Vals: platformString())}}}, |
667 | {.K: "capabilities" , .V: std::move(ServerCaps)}}}; |
668 | if (Opts.Encoding) |
669 | Result["offsetEncoding" ] = *Opts.Encoding; |
670 | Reply(std::move(Result)); |
671 | |
672 | // Apply settings after we're fully initialized. |
673 | // This can start background indexing and in turn trigger LSP notifications. |
674 | applyConfiguration(Settings: Params.initializationOptions.ConfigSettings); |
675 | } |
676 | |
677 | void ClangdLSPServer::onInitialized(const InitializedParams &Params) {} |
678 | |
679 | void ClangdLSPServer::onShutdown(const NoParams &, |
680 | Callback<std::nullptr_t> Reply) { |
681 | // Do essentially nothing, just say we're ready to exit. |
682 | ShutdownRequestReceived = true; |
683 | Reply(nullptr); |
684 | } |
685 | |
686 | // sync is a clangd extension: it blocks until all background work completes. |
687 | // It blocks the calling thread, so no messages are processed until it returns! |
688 | void ClangdLSPServer::onSync(const NoParams &, Callback<std::nullptr_t> Reply) { |
689 | if (Server->blockUntilIdleForTest(/*TimeoutSeconds=*/60)) |
690 | Reply(nullptr); |
691 | else |
692 | Reply(error(Fmt: "Not idle after a minute" )); |
693 | } |
694 | |
695 | void ClangdLSPServer::onDocumentDidOpen( |
696 | const DidOpenTextDocumentParams &Params) { |
697 | PathRef File = Params.textDocument.uri.file(); |
698 | |
699 | const std::string &Contents = Params.textDocument.text; |
700 | |
701 | Server->addDocument(File, Contents, |
702 | Version: encodeVersion(LSPVersion: Params.textDocument.version), |
703 | WD: WantDiagnostics::Yes); |
704 | } |
705 | |
706 | void ClangdLSPServer::onDocumentDidChange( |
707 | const DidChangeTextDocumentParams &Params) { |
708 | auto WantDiags = WantDiagnostics::Auto; |
709 | if (Params.wantDiagnostics) |
710 | WantDiags = |
711 | *Params.wantDiagnostics ? WantDiagnostics::Yes : WantDiagnostics::No; |
712 | |
713 | PathRef File = Params.textDocument.uri.file(); |
714 | auto Code = Server->getDraft(File); |
715 | if (!Code) { |
716 | log(Fmt: "Trying to incrementally change non-added document: {0}" , Vals&: File); |
717 | return; |
718 | } |
719 | std::string NewCode(*Code); |
720 | for (const auto &Change : Params.contentChanges) { |
721 | if (auto Err = applyChange(Contents&: NewCode, Change)) { |
722 | // If this fails, we are most likely going to be not in sync anymore with |
723 | // the client. It is better to remove the draft and let further |
724 | // operations fail rather than giving wrong results. |
725 | Server->removeDocument(File); |
726 | elog(Fmt: "Failed to update {0}: {1}" , Vals&: File, Vals: std::move(Err)); |
727 | return; |
728 | } |
729 | } |
730 | Server->addDocument(File, Contents: NewCode, Version: encodeVersion(LSPVersion: Params.textDocument.version), |
731 | WD: WantDiags, ForceRebuild: Params.forceRebuild); |
732 | } |
733 | |
734 | void ClangdLSPServer::onDocumentDidSave( |
735 | const DidSaveTextDocumentParams &Params) { |
736 | Server->reparseOpenFilesIfNeeded(Filter: [](llvm::StringRef) { return true; }); |
737 | } |
738 | |
739 | void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { |
740 | // We could also reparse all open files here. However: |
741 | // - this could be frequent, and revalidating all the preambles isn't free |
742 | // - this is useful e.g. when switching git branches, but we're likely to see |
743 | // fresh headers but still have the old-branch main-file content |
744 | Server->onFileEvent(Params); |
745 | // FIXME: observe config files, immediately expire time-based caches, reparse: |
746 | // - compile_commands.json and compile_flags.txt |
747 | // - .clang_format and .clang-tidy |
748 | // - .clangd and clangd/config.yaml |
749 | } |
750 | |
751 | void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params, |
752 | Callback<llvm::json::Value> Reply) { |
753 | auto It = Handlers.CommandHandlers.find(Key: Params.command); |
754 | if (It == Handlers.CommandHandlers.end()) { |
755 | return Reply(llvm::make_error<LSPError>( |
756 | Args: llvm::formatv(Fmt: "Unsupported command \"{0}\"." , Vals: Params.command).str(), |
757 | Args: ErrorCode::InvalidParams)); |
758 | } |
759 | It->second(Params.argument, std::move(Reply)); |
760 | } |
761 | |
762 | void ClangdLSPServer::onCommandApplyEdit(const WorkspaceEdit &WE, |
763 | Callback<llvm::json::Value> Reply) { |
764 | // The flow for "apply-fix" : |
765 | // 1. We publish a diagnostic, including fixits |
766 | // 2. The user clicks on the diagnostic, the editor asks us for code actions |
767 | // 3. We send code actions, with the fixit embedded as context |
768 | // 4. The user selects the fixit, the editor asks us to apply it |
769 | // 5. We unwrap the changes and send them back to the editor |
770 | // 6. The editor applies the changes (applyEdit), and sends us a reply |
771 | // 7. We unwrap the reply and send a reply to the editor. |
772 | applyEdit(WE, Success: "Fix applied." , Reply: std::move(Reply)); |
773 | } |
774 | |
775 | void ClangdLSPServer::onCommandApplyTweak(const TweakArgs &Args, |
776 | Callback<llvm::json::Value> Reply) { |
777 | auto Action = [this, Reply = std::move(Reply)]( |
778 | llvm::Expected<Tweak::Effect> R) mutable { |
779 | if (!R) |
780 | return Reply(R.takeError()); |
781 | |
782 | assert(R->ShowMessage || (!R->ApplyEdits.empty() && "tweak has no effect" )); |
783 | |
784 | if (R->ShowMessage) { |
785 | ShowMessageParams Msg; |
786 | Msg.message = *R->ShowMessage; |
787 | Msg.type = MessageType::Info; |
788 | ShowMessage(Msg); |
789 | } |
790 | // When no edit is specified, make sure we Reply(). |
791 | if (R->ApplyEdits.empty()) |
792 | return Reply("Tweak applied." ); |
793 | |
794 | if (auto Err = validateEdits(Server: *Server, FE: R->ApplyEdits)) |
795 | return Reply(std::move(Err)); |
796 | |
797 | WorkspaceEdit WE; |
798 | // FIXME: use documentChanges when SupportDocumentChanges is true. |
799 | WE.changes.emplace(); |
800 | for (const auto &It : R->ApplyEdits) { |
801 | (*WE.changes)[URI::createFile(AbsolutePath: It.first()).toString()] = |
802 | It.second.asTextEdits(); |
803 | } |
804 | // ApplyEdit will take care of calling Reply(). |
805 | return applyEdit(WE: std::move(WE), Success: "Tweak applied." , Reply: std::move(Reply)); |
806 | }; |
807 | Server->applyTweak(File: Args.file.file(), Sel: Args.selection, ID: Args.tweakID, |
808 | CB: std::move(Action)); |
809 | } |
810 | |
811 | void ClangdLSPServer::applyEdit(WorkspaceEdit WE, llvm::json::Value Success, |
812 | Callback<llvm::json::Value> Reply) { |
813 | ApplyWorkspaceEditParams Edit; |
814 | Edit.edit = std::move(WE); |
815 | ApplyWorkspaceEdit( |
816 | Edit, [Reply = std::move(Reply), SuccessMessage = std::move(Success)]( |
817 | llvm::Expected<ApplyWorkspaceEditResponse> Response) mutable { |
818 | if (!Response) |
819 | return Reply(Response.takeError()); |
820 | if (!Response->applied) { |
821 | std::string Reason = Response->failureReason |
822 | ? *Response->failureReason |
823 | : "unknown reason" ; |
824 | return Reply(error(Fmt: "edits were not applied: {0}" , Vals&: Reason)); |
825 | } |
826 | return Reply(SuccessMessage); |
827 | }); |
828 | } |
829 | |
830 | void ClangdLSPServer::onWorkspaceSymbol( |
831 | const WorkspaceSymbolParams &Params, |
832 | Callback<std::vector<SymbolInformation>> Reply) { |
833 | Server->workspaceSymbols( |
834 | Query: Params.query, Limit: Params.limit.value_or(u&: Opts.CodeComplete.Limit), |
835 | CB: [Reply = std::move(Reply), |
836 | this](llvm::Expected<std::vector<SymbolInformation>> Items) mutable { |
837 | if (!Items) |
838 | return Reply(Items.takeError()); |
839 | for (auto &Sym : *Items) |
840 | Sym.kind = adjustKindToCapability(Kind: Sym.kind, supportedSymbolKinds&: SupportedSymbolKinds); |
841 | |
842 | Reply(std::move(*Items)); |
843 | }); |
844 | } |
845 | |
846 | void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params, |
847 | Callback<std::optional<Range>> Reply) { |
848 | Server->prepareRename( |
849 | File: Params.textDocument.uri.file(), Pos: Params.position, /*NewName*/ std::nullopt, |
850 | RenameOpts: Opts.Rename, |
851 | CB: [Reply = std::move(Reply)](llvm::Expected<RenameResult> Result) mutable { |
852 | if (!Result) |
853 | return Reply(Result.takeError()); |
854 | return Reply(std::move(Result->Target)); |
855 | }); |
856 | } |
857 | |
858 | void ClangdLSPServer::onRename(const RenameParams &Params, |
859 | Callback<WorkspaceEdit> Reply) { |
860 | Path File = std::string(Params.textDocument.uri.file()); |
861 | if (!Server->getDraft(File)) |
862 | return Reply(llvm::make_error<LSPError>( |
863 | Args: "onRename called for non-added file" , Args: ErrorCode::InvalidParams)); |
864 | Server->rename(File, Pos: Params.position, NewName: Params.newName, Opts: Opts.Rename, |
865 | CB: [File, Params, Reply = std::move(Reply), |
866 | this](llvm::Expected<RenameResult> R) mutable { |
867 | if (!R) |
868 | return Reply(R.takeError()); |
869 | if (auto Err = validateEdits(Server: *Server, FE: R->GlobalChanges)) |
870 | return Reply(std::move(Err)); |
871 | WorkspaceEdit Result; |
872 | // FIXME: use documentChanges if SupportDocumentChanges is |
873 | // true. |
874 | Result.changes.emplace(); |
875 | for (const auto &Rep : R->GlobalChanges) { |
876 | (*Result |
877 | .changes)[URI::createFile(AbsolutePath: Rep.first()).toString()] = |
878 | Rep.second.asTextEdits(); |
879 | } |
880 | Reply(Result); |
881 | }); |
882 | } |
883 | |
884 | void ClangdLSPServer::onDocumentDidClose( |
885 | const DidCloseTextDocumentParams &Params) { |
886 | PathRef File = Params.textDocument.uri.file(); |
887 | Server->removeDocument(File); |
888 | |
889 | { |
890 | std::lock_guard<std::mutex> Lock(DiagRefMutex); |
891 | DiagRefMap.erase(Key: File); |
892 | } |
893 | { |
894 | std::lock_guard<std::mutex> HLock(SemanticTokensMutex); |
895 | LastSemanticTokens.erase(Key: File); |
896 | } |
897 | // clangd will not send updates for this file anymore, so we empty out the |
898 | // list of diagnostics shown on the client (e.g. in the "Problems" pane of |
899 | // VSCode). Note that this cannot race with actual diagnostics responses |
900 | // because removeDocument() guarantees no diagnostic callbacks will be |
901 | // executed after it returns. |
902 | PublishDiagnosticsParams Notification; |
903 | Notification.uri = URIForFile::canonicalize(AbsPath: File, /*TUPath=*/File); |
904 | PublishDiagnostics(Notification); |
905 | } |
906 | |
907 | void ClangdLSPServer::onDocumentOnTypeFormatting( |
908 | const DocumentOnTypeFormattingParams &Params, |
909 | Callback<std::vector<TextEdit>> Reply) { |
910 | auto File = Params.textDocument.uri.file(); |
911 | Server->formatOnType(File, Pos: Params.position, TriggerText: Params.ch, CB: std::move(Reply)); |
912 | } |
913 | |
914 | void ClangdLSPServer::onDocumentRangeFormatting( |
915 | const DocumentRangeFormattingParams &Params, |
916 | Callback<std::vector<TextEdit>> Reply) { |
917 | auto File = Params.textDocument.uri.file(); |
918 | auto Code = Server->getDraft(File); |
919 | Server->formatFile(File, Rng: Params.range, |
920 | CB: [Code = std::move(Code), Reply = std::move(Reply)]( |
921 | llvm::Expected<tooling::Replacements> Result) mutable { |
922 | if (Result) |
923 | Reply(replacementsToEdits(Code: *Code, Repls: Result.get())); |
924 | else |
925 | Reply(Result.takeError()); |
926 | }); |
927 | } |
928 | |
929 | void ClangdLSPServer::onDocumentFormatting( |
930 | const DocumentFormattingParams &Params, |
931 | Callback<std::vector<TextEdit>> Reply) { |
932 | auto File = Params.textDocument.uri.file(); |
933 | auto Code = Server->getDraft(File); |
934 | Server->formatFile(File, |
935 | /*Rng=*/std::nullopt, |
936 | CB: [Code = std::move(Code), Reply = std::move(Reply)]( |
937 | llvm::Expected<tooling::Replacements> Result) mutable { |
938 | if (Result) |
939 | Reply(replacementsToEdits(Code: *Code, Repls: Result.get())); |
940 | else |
941 | Reply(Result.takeError()); |
942 | }); |
943 | } |
944 | |
945 | /// The functions constructs a flattened view of the DocumentSymbol hierarchy. |
946 | /// Used by the clients that do not support the hierarchical view. |
947 | static std::vector<SymbolInformation> |
948 | flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols, |
949 | const URIForFile &FileURI) { |
950 | std::vector<SymbolInformation> Results; |
951 | std::function<void(const DocumentSymbol &, llvm::StringRef)> Process = |
952 | [&](const DocumentSymbol &S, std::optional<llvm::StringRef> ParentName) { |
953 | SymbolInformation SI; |
954 | SI.containerName = std::string(ParentName ? "" : *ParentName); |
955 | SI.name = S.name; |
956 | SI.kind = S.kind; |
957 | SI.location.range = S.range; |
958 | SI.location.uri = FileURI; |
959 | |
960 | Results.push_back(x: std::move(SI)); |
961 | std::string FullName = |
962 | !ParentName ? S.name : (ParentName->str() + "::" + S.name); |
963 | for (auto &C : S.children) |
964 | Process(C, /*ParentName=*/FullName); |
965 | }; |
966 | for (auto &S : Symbols) |
967 | Process(S, /*ParentName=*/"" ); |
968 | return Results; |
969 | } |
970 | |
971 | void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params, |
972 | Callback<llvm::json::Value> Reply) { |
973 | URIForFile FileURI = Params.textDocument.uri; |
974 | Server->documentSymbols( |
975 | File: Params.textDocument.uri.file(), |
976 | CB: [this, FileURI, Reply = std::move(Reply)]( |
977 | llvm::Expected<std::vector<DocumentSymbol>> Items) mutable { |
978 | if (!Items) |
979 | return Reply(Items.takeError()); |
980 | adjustSymbolKinds(Syms: *Items, Kinds: SupportedSymbolKinds); |
981 | if (SupportsHierarchicalDocumentSymbol) |
982 | return Reply(std::move(*Items)); |
983 | return Reply(flattenSymbolHierarchy(Symbols: *Items, FileURI)); |
984 | }); |
985 | } |
986 | |
987 | void ClangdLSPServer::onFoldingRange( |
988 | const FoldingRangeParams &Params, |
989 | Callback<std::vector<FoldingRange>> Reply) { |
990 | Server->foldingRanges(File: Params.textDocument.uri.file(), CB: std::move(Reply)); |
991 | } |
992 | |
993 | static std::optional<Command> asCommand(const CodeAction &Action) { |
994 | Command Cmd; |
995 | if (Action.command && Action.edit) |
996 | return std::nullopt; // Not representable. (We never emit these anyway). |
997 | if (Action.command) { |
998 | Cmd = *Action.command; |
999 | } else if (Action.edit) { |
1000 | Cmd.command = std::string(ApplyFixCommand); |
1001 | Cmd.argument = *Action.edit; |
1002 | } else { |
1003 | return std::nullopt; |
1004 | } |
1005 | Cmd.title = Action.title; |
1006 | if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND) |
1007 | Cmd.title = "Apply fix: " + Cmd.title; |
1008 | return Cmd; |
1009 | } |
1010 | |
1011 | void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, |
1012 | Callback<llvm::json::Value> Reply) { |
1013 | URIForFile File = Params.textDocument.uri; |
1014 | std::map<ClangdServer::DiagRef, clangd::Diagnostic> ToLSPDiags; |
1015 | ClangdServer::CodeActionInputs Inputs; |
1016 | |
1017 | for (const auto& LSPDiag : Params.context.diagnostics) { |
1018 | if (auto DiagRef = getDiagRef(File: File.file(), D: LSPDiag)) { |
1019 | ToLSPDiags[*DiagRef] = LSPDiag; |
1020 | Inputs.Diagnostics.push_back(x: *DiagRef); |
1021 | } |
1022 | } |
1023 | Inputs.File = File.file(); |
1024 | Inputs.Selection = Params.range; |
1025 | Inputs.RequestedActionKinds = Params.context.only; |
1026 | Inputs.TweakFilter = [this](const Tweak &T) { |
1027 | return Opts.TweakFilter(T); |
1028 | }; |
1029 | auto CB = [this, |
1030 | Reply = std::move(Reply), |
1031 | ToLSPDiags = std::move(ToLSPDiags), File, |
1032 | Selection = Params.range]( |
1033 | llvm::Expected<ClangdServer::CodeActionResult> Fixits) mutable { |
1034 | if (!Fixits) |
1035 | return Reply(Fixits.takeError()); |
1036 | std::vector<CodeAction> CAs; |
1037 | auto Version = decodeVersion(Encoded: Fixits->Version); |
1038 | for (const auto &QF : Fixits->QuickFixes) { |
1039 | CAs.push_back(x: toCodeAction(F: QF.F, File, Version, SupportsDocumentChanges, |
1040 | SupportChangeAnnotation: SupportsChangeAnnotation)); |
1041 | if (auto It = ToLSPDiags.find(x: QF.Diag); |
1042 | It != ToLSPDiags.end()) { |
1043 | CAs.back().diagnostics = {It->second}; |
1044 | } |
1045 | } |
1046 | for (const auto &TR : Fixits->TweakRefs) |
1047 | CAs.push_back(x: toCodeAction(T: TR, File, Selection)); |
1048 | |
1049 | // If there's exactly one quick-fix, call it "preferred". |
1050 | // We never consider refactorings etc as preferred. |
1051 | CodeAction *OnlyFix = nullptr; |
1052 | for (auto &Action : CAs) { |
1053 | if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND) { |
1054 | if (OnlyFix) { |
1055 | OnlyFix = nullptr; |
1056 | break; |
1057 | } |
1058 | OnlyFix = &Action; |
1059 | } |
1060 | } |
1061 | if (OnlyFix) { |
1062 | OnlyFix->isPreferred = true; |
1063 | if (ToLSPDiags.size() == 1 && |
1064 | ToLSPDiags.begin()->second.range == Selection) |
1065 | OnlyFix->diagnostics = {ToLSPDiags.begin()->second}; |
1066 | } |
1067 | |
1068 | if (SupportsCodeAction) |
1069 | return Reply(llvm::json::Array(CAs)); |
1070 | std::vector<Command> Commands; |
1071 | for (const auto &Action : CAs) { |
1072 | if (auto Command = asCommand(Action)) |
1073 | Commands.push_back(x: std::move(*Command)); |
1074 | } |
1075 | return Reply(llvm::json::Array(Commands)); |
1076 | }; |
1077 | Server->codeAction(Inputs, CB: std::move(CB)); |
1078 | } |
1079 | |
1080 | void ClangdLSPServer::onCompletion(const CompletionParams &Params, |
1081 | Callback<CompletionList> Reply) { |
1082 | if (!shouldRunCompletion(Params)) { |
1083 | // Clients sometimes auto-trigger completions in undesired places (e.g. |
1084 | // 'a >^ '), we return empty results in those cases. |
1085 | vlog(Fmt: "ignored auto-triggered completion, preceding char did not match" ); |
1086 | return Reply(CompletionList()); |
1087 | } |
1088 | auto Opts = this->Opts.CodeComplete; |
1089 | if (Params.limit && *Params.limit >= 0) |
1090 | Opts.Limit = *Params.limit; |
1091 | Server->codeComplete(File: Params.textDocument.uri.file(), Pos: Params.position, Opts, |
1092 | CB: [Reply = std::move(Reply), Opts, |
1093 | this](llvm::Expected<CodeCompleteResult> List) mutable { |
1094 | if (!List) |
1095 | return Reply(List.takeError()); |
1096 | CompletionList LSPList; |
1097 | LSPList.isIncomplete = List->HasMore; |
1098 | for (const auto &R : List->Completions) { |
1099 | CompletionItem C = R.render(Opts); |
1100 | C.kind = adjustKindToCapability( |
1101 | Kind: C.kind, SupportedCompletionItemKinds); |
1102 | if (!SupportsCompletionLabelDetails) |
1103 | removeCompletionLabelDetails(C); |
1104 | LSPList.items.push_back(x: std::move(C)); |
1105 | } |
1106 | return Reply(std::move(LSPList)); |
1107 | }); |
1108 | } |
1109 | |
1110 | void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams &Params, |
1111 | Callback<SignatureHelp> Reply) { |
1112 | Server->signatureHelp(File: Params.textDocument.uri.file(), Pos: Params.position, |
1113 | DocumentationFormat: Opts.SignatureHelpDocumentationFormat, |
1114 | CB: [Reply = std::move(Reply), this]( |
1115 | llvm::Expected<SignatureHelp> Signature) mutable { |
1116 | if (!Signature) |
1117 | return Reply(Signature.takeError()); |
1118 | if (SupportsOffsetsInSignatureHelp) |
1119 | return Reply(std::move(*Signature)); |
1120 | // Strip out the offsets from signature help for |
1121 | // clients that only support string labels. |
1122 | for (auto &SigInfo : Signature->signatures) { |
1123 | for (auto &Param : SigInfo.parameters) |
1124 | Param.labelOffsets.reset(); |
1125 | } |
1126 | return Reply(std::move(*Signature)); |
1127 | }); |
1128 | } |
1129 | |
1130 | // Go to definition has a toggle function: if def and decl are distinct, then |
1131 | // the first press gives you the def, the second gives you the matching def. |
1132 | // getToggle() returns the counterpart location that under the cursor. |
1133 | // |
1134 | // We return the toggled location alone (ignoring other symbols) to encourage |
1135 | // editors to "bounce" quickly between locations, without showing a menu. |
1136 | static Location *getToggle(const TextDocumentPositionParams &Point, |
1137 | LocatedSymbol &Sym) { |
1138 | // Toggle only makes sense with two distinct locations. |
1139 | if (!Sym.Definition || *Sym.Definition == Sym.PreferredDeclaration) |
1140 | return nullptr; |
1141 | if (Sym.Definition->uri.file() == Point.textDocument.uri.file() && |
1142 | Sym.Definition->range.contains(Pos: Point.position)) |
1143 | return &Sym.PreferredDeclaration; |
1144 | if (Sym.PreferredDeclaration.uri.file() == Point.textDocument.uri.file() && |
1145 | Sym.PreferredDeclaration.range.contains(Pos: Point.position)) |
1146 | return &*Sym.Definition; |
1147 | return nullptr; |
1148 | } |
1149 | |
1150 | void ClangdLSPServer::onGoToDefinition(const TextDocumentPositionParams &Params, |
1151 | Callback<std::vector<Location>> Reply) { |
1152 | Server->locateSymbolAt( |
1153 | File: Params.textDocument.uri.file(), Pos: Params.position, |
1154 | CB: [Params, Reply = std::move(Reply)]( |
1155 | llvm::Expected<std::vector<LocatedSymbol>> Symbols) mutable { |
1156 | if (!Symbols) |
1157 | return Reply(Symbols.takeError()); |
1158 | std::vector<Location> Defs; |
1159 | for (auto &S : *Symbols) { |
1160 | if (Location *Toggle = getToggle(Point: Params, Sym&: S)) |
1161 | return Reply(std::vector<Location>{std::move(*Toggle)}); |
1162 | Defs.push_back(x: S.Definition.value_or(u&: S.PreferredDeclaration)); |
1163 | } |
1164 | Reply(std::move(Defs)); |
1165 | }); |
1166 | } |
1167 | |
1168 | void ClangdLSPServer::onGoToDeclaration( |
1169 | const TextDocumentPositionParams &Params, |
1170 | Callback<std::vector<Location>> Reply) { |
1171 | Server->locateSymbolAt( |
1172 | File: Params.textDocument.uri.file(), Pos: Params.position, |
1173 | CB: [Params, Reply = std::move(Reply)]( |
1174 | llvm::Expected<std::vector<LocatedSymbol>> Symbols) mutable { |
1175 | if (!Symbols) |
1176 | return Reply(Symbols.takeError()); |
1177 | std::vector<Location> Decls; |
1178 | for (auto &S : *Symbols) { |
1179 | if (Location *Toggle = getToggle(Point: Params, Sym&: S)) |
1180 | return Reply(std::vector<Location>{std::move(*Toggle)}); |
1181 | Decls.push_back(x: std::move(S.PreferredDeclaration)); |
1182 | } |
1183 | Reply(std::move(Decls)); |
1184 | }); |
1185 | } |
1186 | |
1187 | void ClangdLSPServer::( |
1188 | const TextDocumentIdentifier &Params, |
1189 | Callback<std::optional<URIForFile>> Reply) { |
1190 | Server->switchSourceHeader( |
1191 | Path: Params.uri.file(), |
1192 | CB: [Reply = std::move(Reply), |
1193 | Params](llvm::Expected<std::optional<clangd::Path>> Path) mutable { |
1194 | if (!Path) |
1195 | return Reply(Path.takeError()); |
1196 | if (*Path) |
1197 | return Reply(URIForFile::canonicalize(AbsPath: **Path, TUPath: Params.uri.file())); |
1198 | return Reply(std::nullopt); |
1199 | }); |
1200 | } |
1201 | |
1202 | void ClangdLSPServer::onDocumentHighlight( |
1203 | const TextDocumentPositionParams &Params, |
1204 | Callback<std::vector<DocumentHighlight>> Reply) { |
1205 | Server->findDocumentHighlights(File: Params.textDocument.uri.file(), |
1206 | Pos: Params.position, CB: std::move(Reply)); |
1207 | } |
1208 | |
1209 | void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params, |
1210 | Callback<std::optional<Hover>> Reply) { |
1211 | Server->findHover(File: Params.textDocument.uri.file(), Pos: Params.position, |
1212 | CB: [Reply = std::move(Reply), |
1213 | this](llvm::Expected<std::optional<HoverInfo>> H) mutable { |
1214 | if (!H) |
1215 | return Reply(H.takeError()); |
1216 | if (!*H) |
1217 | return Reply(std::nullopt); |
1218 | |
1219 | Hover R; |
1220 | R.contents.kind = HoverContentFormat; |
1221 | R.range = (*H)->SymRange; |
1222 | switch (HoverContentFormat) { |
1223 | case MarkupKind::PlainText: |
1224 | R.contents.value = (*H)->present().asPlainText(); |
1225 | return Reply(std::move(R)); |
1226 | case MarkupKind::Markdown: |
1227 | R.contents.value = (*H)->present().asMarkdown(); |
1228 | return Reply(std::move(R)); |
1229 | }; |
1230 | llvm_unreachable("unhandled MarkupKind" ); |
1231 | }); |
1232 | } |
1233 | |
1234 | // Our extension has a different representation on the wire than the standard. |
1235 | // https://clangd.llvm.org/extensions#type-hierarchy |
1236 | llvm::json::Value serializeTHIForExtension(TypeHierarchyItem THI) { |
1237 | llvm::json::Object Result{{ |
1238 | {.K: "name" , .V: std::move(THI.name)}, |
1239 | {.K: "kind" , .V: static_cast<int>(THI.kind)}, |
1240 | {.K: "uri" , .V: std::move(THI.uri)}, |
1241 | {.K: "range" , .V: THI.range}, |
1242 | {.K: "selectionRange" , .V: THI.selectionRange}, |
1243 | {.K: "data" , .V: std::move(THI.data)}, |
1244 | }}; |
1245 | if (THI.deprecated) |
1246 | Result["deprecated" ] = THI.deprecated; |
1247 | if (THI.detail) |
1248 | Result["detail" ] = std::move(*THI.detail); |
1249 | |
1250 | if (THI.parents) { |
1251 | llvm::json::Array Parents; |
1252 | for (auto &Parent : *THI.parents) |
1253 | Parents.emplace_back(A: serializeTHIForExtension(THI: std::move(Parent))); |
1254 | Result["parents" ] = std::move(Parents); |
1255 | } |
1256 | |
1257 | if (THI.children) { |
1258 | llvm::json::Array Children; |
1259 | for (auto &child : *THI.children) |
1260 | Children.emplace_back(A: serializeTHIForExtension(THI: std::move(child))); |
1261 | Result["children" ] = std::move(Children); |
1262 | } |
1263 | return Result; |
1264 | } |
1265 | |
1266 | void ClangdLSPServer::onTypeHierarchy(const TypeHierarchyPrepareParams &Params, |
1267 | Callback<llvm::json::Value> Reply) { |
1268 | auto Serialize = |
1269 | [Reply = std::move(Reply)]( |
1270 | llvm::Expected<std::vector<TypeHierarchyItem>> Resp) mutable { |
1271 | if (!Resp) { |
1272 | Reply(Resp.takeError()); |
1273 | return; |
1274 | } |
1275 | if (Resp->empty()) { |
1276 | Reply(nullptr); |
1277 | return; |
1278 | } |
1279 | Reply(serializeTHIForExtension(THI: std::move(Resp->front()))); |
1280 | }; |
1281 | Server->typeHierarchy(File: Params.textDocument.uri.file(), Pos: Params.position, |
1282 | Resolve: Params.resolve, Direction: Params.direction, CB: std::move(Serialize)); |
1283 | } |
1284 | |
1285 | void ClangdLSPServer::onResolveTypeHierarchy( |
1286 | const ResolveTypeHierarchyItemParams &Params, |
1287 | Callback<llvm::json::Value> Reply) { |
1288 | auto Serialize = |
1289 | [Reply = std::move(Reply)]( |
1290 | llvm::Expected<std::optional<TypeHierarchyItem>> Resp) mutable { |
1291 | if (!Resp) { |
1292 | Reply(Resp.takeError()); |
1293 | return; |
1294 | } |
1295 | if (!*Resp) { |
1296 | Reply(std::move(*Resp)); |
1297 | return; |
1298 | } |
1299 | Reply(serializeTHIForExtension(THI: std::move(**Resp))); |
1300 | }; |
1301 | Server->resolveTypeHierarchy(Item: Params.item, Resolve: Params.resolve, Direction: Params.direction, |
1302 | CB: std::move(Serialize)); |
1303 | } |
1304 | |
1305 | void ClangdLSPServer::onPrepareTypeHierarchy( |
1306 | const TypeHierarchyPrepareParams &Params, |
1307 | Callback<std::vector<TypeHierarchyItem>> Reply) { |
1308 | Server->typeHierarchy(File: Params.textDocument.uri.file(), Pos: Params.position, |
1309 | Resolve: Params.resolve, Direction: Params.direction, CB: std::move(Reply)); |
1310 | } |
1311 | |
1312 | void ClangdLSPServer::onSuperTypes( |
1313 | const ResolveTypeHierarchyItemParams &Params, |
1314 | Callback<std::optional<std::vector<TypeHierarchyItem>>> Reply) { |
1315 | Server->superTypes(Item: Params.item, CB: std::move(Reply)); |
1316 | } |
1317 | |
1318 | void ClangdLSPServer::onSubTypes( |
1319 | const ResolveTypeHierarchyItemParams &Params, |
1320 | Callback<std::vector<TypeHierarchyItem>> Reply) { |
1321 | Server->subTypes(Item: Params.item, CB: std::move(Reply)); |
1322 | } |
1323 | |
1324 | void ClangdLSPServer::onPrepareCallHierarchy( |
1325 | const CallHierarchyPrepareParams &Params, |
1326 | Callback<std::vector<CallHierarchyItem>> Reply) { |
1327 | Server->prepareCallHierarchy(File: Params.textDocument.uri.file(), Pos: Params.position, |
1328 | CB: std::move(Reply)); |
1329 | } |
1330 | |
1331 | void ClangdLSPServer::onCallHierarchyIncomingCalls( |
1332 | const CallHierarchyIncomingCallsParams &Params, |
1333 | Callback<std::vector<CallHierarchyIncomingCall>> Reply) { |
1334 | Server->incomingCalls(Item: Params.item, std::move(Reply)); |
1335 | } |
1336 | |
1337 | void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params, |
1338 | Callback<llvm::json::Value> Reply) { |
1339 | // Our extension has a different representation on the wire than the standard. |
1340 | // We have a "range" property and "kind" is represented as a string, not as an |
1341 | // enum value. |
1342 | // https://clangd.llvm.org/extensions#inlay-hints |
1343 | auto Serialize = [Reply = std::move(Reply)]( |
1344 | llvm::Expected<std::vector<InlayHint>> Hints) mutable { |
1345 | if (!Hints) { |
1346 | Reply(Hints.takeError()); |
1347 | return; |
1348 | } |
1349 | llvm::json::Array Result; |
1350 | Result.reserve(S: Hints->size()); |
1351 | for (auto &Hint : *Hints) { |
1352 | Result.emplace_back(A: llvm::json::Object{ |
1353 | {.K: "kind" , .V: llvm::to_string(Value: Hint.kind)}, |
1354 | {.K: "range" , .V: Hint.range}, |
1355 | {.K: "position" , .V: Hint.position}, |
1356 | // Extension doesn't have paddingLeft/Right so adjust the label |
1357 | // accordingly. |
1358 | {.K: "label" , |
1359 | .V: ((Hint.paddingLeft ? " " : "" ) + llvm::StringRef(Hint.label) + |
1360 | (Hint.paddingRight ? " " : "" )) |
1361 | .str()}, |
1362 | }); |
1363 | } |
1364 | Reply(std::move(Result)); |
1365 | }; |
1366 | Server->inlayHints(File: Params.textDocument.uri.file(), RestrictRange: Params.range, |
1367 | std::move(Serialize)); |
1368 | } |
1369 | |
1370 | void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params, |
1371 | Callback<std::vector<InlayHint>> Reply) { |
1372 | Server->inlayHints(File: Params.textDocument.uri.file(), RestrictRange: Params.range, |
1373 | std::move(Reply)); |
1374 | } |
1375 | |
1376 | void ClangdLSPServer::applyConfiguration( |
1377 | const ConfigurationSettings &Settings) { |
1378 | // Per-file update to the compilation database. |
1379 | llvm::StringSet<> ModifiedFiles; |
1380 | for (auto &Entry : Settings.compilationDatabaseChanges) { |
1381 | PathRef File = Entry.first; |
1382 | auto Old = CDB->getCompileCommand(File); |
1383 | auto New = |
1384 | tooling::CompileCommand(std::move(Entry.second.workingDirectory), File, |
1385 | std::move(Entry.second.compilationCommand), |
1386 | /*Output=*/"" ); |
1387 | if (Old != New) { |
1388 | CDB->setCompileCommand(File, CompilationCommand: std::move(New)); |
1389 | ModifiedFiles.insert(key: File); |
1390 | } |
1391 | } |
1392 | |
1393 | Server->reparseOpenFilesIfNeeded( |
1394 | Filter: [&](llvm::StringRef File) { return ModifiedFiles.count(Key: File) != 0; }); |
1395 | } |
1396 | |
1397 | void ClangdLSPServer::maybeExportMemoryProfile() { |
1398 | if (!trace::enabled() || !ShouldProfile()) |
1399 | return; |
1400 | |
1401 | static constexpr trace::Metric MemoryUsage( |
1402 | "memory_usage" , trace::Metric::Value, "component_name" ); |
1403 | trace::Span Tracer("ProfileBrief" ); |
1404 | MemoryTree MT; |
1405 | profile(MT); |
1406 | record(MT, RootName: "clangd_lsp_server" , Out: MemoryUsage); |
1407 | } |
1408 | |
1409 | void ClangdLSPServer::maybeCleanupMemory() { |
1410 | if (!Opts.MemoryCleanup || !ShouldCleanupMemory()) |
1411 | return; |
1412 | Opts.MemoryCleanup(); |
1413 | } |
1414 | |
1415 | // FIXME: This function needs to be properly tested. |
1416 | void ClangdLSPServer::onChangeConfiguration( |
1417 | const DidChangeConfigurationParams &Params) { |
1418 | applyConfiguration(Settings: Params.settings); |
1419 | } |
1420 | |
1421 | void ClangdLSPServer::onReference( |
1422 | const ReferenceParams &Params, |
1423 | Callback<std::vector<ReferenceLocation>> Reply) { |
1424 | Server->findReferences(File: Params.textDocument.uri.file(), Pos: Params.position, |
1425 | Limit: Opts.ReferencesLimit, AddContainer: SupportsReferenceContainer, |
1426 | CB: [Reply = std::move(Reply), |
1427 | IncludeDecl(Params.context.includeDeclaration)]( |
1428 | llvm::Expected<ReferencesResult> Refs) mutable { |
1429 | if (!Refs) |
1430 | return Reply(Refs.takeError()); |
1431 | // Filter out declarations if the client asked. |
1432 | std::vector<ReferenceLocation> Result; |
1433 | Result.reserve(n: Refs->References.size()); |
1434 | for (auto &Ref : Refs->References) { |
1435 | bool IsDecl = |
1436 | Ref.Attributes & ReferencesResult::Declaration; |
1437 | if (IncludeDecl || !IsDecl) |
1438 | Result.push_back(x: std::move(Ref.Loc)); |
1439 | } |
1440 | return Reply(std::move(Result)); |
1441 | }); |
1442 | } |
1443 | |
1444 | void ClangdLSPServer::onGoToType(const TextDocumentPositionParams &Params, |
1445 | Callback<std::vector<Location>> Reply) { |
1446 | Server->findType( |
1447 | File: Params.textDocument.uri.file(), Pos: Params.position, |
1448 | CB: [Reply = std::move(Reply)]( |
1449 | llvm::Expected<std::vector<LocatedSymbol>> Types) mutable { |
1450 | if (!Types) |
1451 | return Reply(Types.takeError()); |
1452 | std::vector<Location> Response; |
1453 | for (const LocatedSymbol &Sym : *Types) |
1454 | Response.push_back(x: Sym.Definition.value_or(u: Sym.PreferredDeclaration)); |
1455 | return Reply(std::move(Response)); |
1456 | }); |
1457 | } |
1458 | |
1459 | void ClangdLSPServer::onGoToImplementation( |
1460 | const TextDocumentPositionParams &Params, |
1461 | Callback<std::vector<Location>> Reply) { |
1462 | Server->findImplementations( |
1463 | File: Params.textDocument.uri.file(), Pos: Params.position, |
1464 | CB: [Reply = std::move(Reply)]( |
1465 | llvm::Expected<std::vector<LocatedSymbol>> Overrides) mutable { |
1466 | if (!Overrides) |
1467 | return Reply(Overrides.takeError()); |
1468 | std::vector<Location> Impls; |
1469 | for (const LocatedSymbol &Sym : *Overrides) |
1470 | Impls.push_back(x: Sym.Definition.value_or(u: Sym.PreferredDeclaration)); |
1471 | return Reply(std::move(Impls)); |
1472 | }); |
1473 | } |
1474 | |
1475 | void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params, |
1476 | Callback<std::vector<SymbolDetails>> Reply) { |
1477 | Server->symbolInfo(File: Params.textDocument.uri.file(), Pos: Params.position, |
1478 | CB: std::move(Reply)); |
1479 | } |
1480 | |
1481 | void ClangdLSPServer::onSelectionRange( |
1482 | const SelectionRangeParams &Params, |
1483 | Callback<std::vector<SelectionRange>> Reply) { |
1484 | Server->semanticRanges( |
1485 | File: Params.textDocument.uri.file(), Pos: Params.positions, |
1486 | CB: [Reply = std::move(Reply)]( |
1487 | llvm::Expected<std::vector<SelectionRange>> Ranges) mutable { |
1488 | if (!Ranges) |
1489 | return Reply(Ranges.takeError()); |
1490 | return Reply(std::move(*Ranges)); |
1491 | }); |
1492 | } |
1493 | |
1494 | void ClangdLSPServer::onDocumentLink( |
1495 | const DocumentLinkParams &Params, |
1496 | Callback<std::vector<DocumentLink>> Reply) { |
1497 | |
1498 | // TODO(forster): This currently resolves all targets eagerly. This is slow, |
1499 | // because it blocks on the preamble/AST being built. We could respond to the |
1500 | // request faster by using string matching or the lexer to find the includes |
1501 | // and resolving the targets lazily. |
1502 | Server->documentLinks( |
1503 | File: Params.textDocument.uri.file(), |
1504 | CB: [Reply = std::move(Reply)]( |
1505 | llvm::Expected<std::vector<DocumentLink>> Links) mutable { |
1506 | if (!Links) { |
1507 | return Reply(Links.takeError()); |
1508 | } |
1509 | return Reply(std::move(Links)); |
1510 | }); |
1511 | } |
1512 | |
1513 | // Increment a numeric string: "" -> 1 -> 2 -> ... -> 9 -> 10 -> 11 ... |
1514 | static void increment(std::string &S) { |
1515 | for (char &C : llvm::reverse(C&: S)) { |
1516 | if (C != '9') { |
1517 | ++C; |
1518 | return; |
1519 | } |
1520 | C = '0'; |
1521 | } |
1522 | S.insert(p: S.begin(), c: '1'); |
1523 | } |
1524 | |
1525 | void ClangdLSPServer::onSemanticTokens(const SemanticTokensParams &Params, |
1526 | Callback<SemanticTokens> CB) { |
1527 | auto File = Params.textDocument.uri.file(); |
1528 | Server->semanticHighlights( |
1529 | File: Params.textDocument.uri.file(), |
1530 | [this, File(File.str()), CB(std::move(CB)), Code(Server->getDraft(File))]( |
1531 | llvm::Expected<std::vector<HighlightingToken>> HT) mutable { |
1532 | if (!HT) |
1533 | return CB(HT.takeError()); |
1534 | SemanticTokens Result; |
1535 | Result.tokens = toSemanticTokens(*HT, Code: *Code); |
1536 | { |
1537 | std::lock_guard<std::mutex> Lock(SemanticTokensMutex); |
1538 | auto &Last = LastSemanticTokens[File]; |
1539 | |
1540 | Last.tokens = Result.tokens; |
1541 | increment(S&: Last.resultId); |
1542 | Result.resultId = Last.resultId; |
1543 | } |
1544 | CB(std::move(Result)); |
1545 | }); |
1546 | } |
1547 | |
1548 | void ClangdLSPServer::onSemanticTokensDelta( |
1549 | const SemanticTokensDeltaParams &Params, |
1550 | Callback<SemanticTokensOrDelta> CB) { |
1551 | auto File = Params.textDocument.uri.file(); |
1552 | Server->semanticHighlights( |
1553 | File: Params.textDocument.uri.file(), |
1554 | [this, PrevResultID(Params.previousResultId), File(File.str()), |
1555 | CB(std::move(CB)), Code(Server->getDraft(File))]( |
1556 | llvm::Expected<std::vector<HighlightingToken>> HT) mutable { |
1557 | if (!HT) |
1558 | return CB(HT.takeError()); |
1559 | std::vector<SemanticToken> Toks = toSemanticTokens(*HT, Code: *Code); |
1560 | |
1561 | SemanticTokensOrDelta Result; |
1562 | { |
1563 | std::lock_guard<std::mutex> Lock(SemanticTokensMutex); |
1564 | auto &Last = LastSemanticTokens[File]; |
1565 | |
1566 | if (PrevResultID == Last.resultId) { |
1567 | Result.edits = diffTokens(Before: Last.tokens, After: Toks); |
1568 | } else { |
1569 | vlog(Fmt: "semanticTokens/full/delta: wanted edits vs {0} but last " |
1570 | "result had ID {1}. Returning full token list." , |
1571 | Vals&: PrevResultID, Vals&: Last.resultId); |
1572 | Result.tokens = Toks; |
1573 | } |
1574 | |
1575 | Last.tokens = std::move(Toks); |
1576 | increment(S&: Last.resultId); |
1577 | Result.resultId = Last.resultId; |
1578 | } |
1579 | |
1580 | CB(std::move(Result)); |
1581 | }); |
1582 | } |
1583 | |
1584 | void ClangdLSPServer::onMemoryUsage(const NoParams &, |
1585 | Callback<MemoryTree> Reply) { |
1586 | llvm::BumpPtrAllocator DetailAlloc; |
1587 | MemoryTree MT(&DetailAlloc); |
1588 | profile(MT); |
1589 | Reply(std::move(MT)); |
1590 | } |
1591 | |
1592 | void ClangdLSPServer::onAST(const ASTParams &Params, |
1593 | Callback<std::optional<ASTNode>> CB) { |
1594 | Server->getAST(File: Params.textDocument.uri.file(), R: Params.range, CB: std::move(CB)); |
1595 | } |
1596 | |
1597 | ClangdLSPServer::ClangdLSPServer(Transport &Transp, const ThreadsafeFS &TFS, |
1598 | const ClangdLSPServer::Options &Opts) |
1599 | : ShouldProfile(/*Period=*/std::chrono::minutes(5), |
1600 | /*Delay=*/std::chrono::minutes(1)), |
1601 | ShouldCleanupMemory(/*Period=*/std::chrono::minutes(1), |
1602 | /*Delay=*/std::chrono::minutes(1)), |
1603 | BackgroundContext(Context::current().clone()), Transp(Transp), |
1604 | MsgHandler(new MessageHandler(*this)), TFS(TFS), |
1605 | SupportedSymbolKinds(defaultSymbolKinds()), |
1606 | SupportedCompletionItemKinds(defaultCompletionItemKinds()), Opts(Opts) { |
1607 | if (Opts.ConfigProvider) { |
1608 | assert(!Opts.ContextProvider && |
1609 | "Only one of ConfigProvider and ContextProvider allowed!" ); |
1610 | this->Opts.ContextProvider = ClangdServer::createConfiguredContextProvider( |
1611 | Provider: Opts.ConfigProvider, this); |
1612 | } |
1613 | LSPBinder Bind(this->Handlers, *this); |
1614 | Bind.method(Method: "initialize" , This: this, Handler: &ClangdLSPServer::onInitialize); |
1615 | } |
1616 | |
1617 | void ClangdLSPServer::bindMethods(LSPBinder &Bind, |
1618 | const ClientCapabilities &Caps) { |
1619 | // clang-format off |
1620 | Bind.notification(Method: "initialized" , This: this, Handler: &ClangdLSPServer::onInitialized); |
1621 | Bind.method(Method: "shutdown" , This: this, Handler: &ClangdLSPServer::onShutdown); |
1622 | Bind.method(Method: "sync" , This: this, Handler: &ClangdLSPServer::onSync); |
1623 | Bind.method(Method: "textDocument/rangeFormatting" , This: this, Handler: &ClangdLSPServer::onDocumentRangeFormatting); |
1624 | Bind.method(Method: "textDocument/onTypeFormatting" , This: this, Handler: &ClangdLSPServer::onDocumentOnTypeFormatting); |
1625 | Bind.method(Method: "textDocument/formatting" , This: this, Handler: &ClangdLSPServer::onDocumentFormatting); |
1626 | Bind.method(Method: "textDocument/codeAction" , This: this, Handler: &ClangdLSPServer::onCodeAction); |
1627 | Bind.method(Method: "textDocument/completion" , This: this, Handler: &ClangdLSPServer::onCompletion); |
1628 | Bind.method(Method: "textDocument/signatureHelp" , This: this, Handler: &ClangdLSPServer::onSignatureHelp); |
1629 | Bind.method(Method: "textDocument/definition" , This: this, Handler: &ClangdLSPServer::onGoToDefinition); |
1630 | Bind.method(Method: "textDocument/declaration" , This: this, Handler: &ClangdLSPServer::onGoToDeclaration); |
1631 | Bind.method(Method: "textDocument/typeDefinition" , This: this, Handler: &ClangdLSPServer::onGoToType); |
1632 | Bind.method(Method: "textDocument/implementation" , This: this, Handler: &ClangdLSPServer::onGoToImplementation); |
1633 | Bind.method(Method: "textDocument/references" , This: this, Handler: &ClangdLSPServer::onReference); |
1634 | Bind.method(Method: "textDocument/switchSourceHeader" , This: this, Handler: &ClangdLSPServer::onSwitchSourceHeader); |
1635 | Bind.method(Method: "textDocument/prepareRename" , This: this, Handler: &ClangdLSPServer::onPrepareRename); |
1636 | Bind.method(Method: "textDocument/rename" , This: this, Handler: &ClangdLSPServer::onRename); |
1637 | Bind.method(Method: "textDocument/hover" , This: this, Handler: &ClangdLSPServer::onHover); |
1638 | Bind.method(Method: "textDocument/documentSymbol" , This: this, Handler: &ClangdLSPServer::onDocumentSymbol); |
1639 | Bind.method(Method: "workspace/executeCommand" , This: this, Handler: &ClangdLSPServer::onCommand); |
1640 | Bind.method(Method: "textDocument/documentHighlight" , This: this, Handler: &ClangdLSPServer::onDocumentHighlight); |
1641 | Bind.method(Method: "workspace/symbol" , This: this, Handler: &ClangdLSPServer::onWorkspaceSymbol); |
1642 | Bind.method(Method: "textDocument/ast" , This: this, Handler: &ClangdLSPServer::onAST); |
1643 | Bind.notification(Method: "textDocument/didOpen" , This: this, Handler: &ClangdLSPServer::onDocumentDidOpen); |
1644 | Bind.notification(Method: "textDocument/didClose" , This: this, Handler: &ClangdLSPServer::onDocumentDidClose); |
1645 | Bind.notification(Method: "textDocument/didChange" , This: this, Handler: &ClangdLSPServer::onDocumentDidChange); |
1646 | Bind.notification(Method: "textDocument/didSave" , This: this, Handler: &ClangdLSPServer::onDocumentDidSave); |
1647 | Bind.notification(Method: "workspace/didChangeWatchedFiles" , This: this, Handler: &ClangdLSPServer::onFileEvent); |
1648 | Bind.notification(Method: "workspace/didChangeConfiguration" , This: this, Handler: &ClangdLSPServer::onChangeConfiguration); |
1649 | Bind.method(Method: "textDocument/symbolInfo" , This: this, Handler: &ClangdLSPServer::onSymbolInfo); |
1650 | Bind.method(Method: "textDocument/typeHierarchy" , This: this, Handler: &ClangdLSPServer::onTypeHierarchy); |
1651 | Bind.method(Method: "typeHierarchy/resolve" , This: this, Handler: &ClangdLSPServer::onResolveTypeHierarchy); |
1652 | Bind.method(Method: "textDocument/prepareTypeHierarchy" , This: this, Handler: &ClangdLSPServer::onPrepareTypeHierarchy); |
1653 | Bind.method(Method: "typeHierarchy/supertypes" , This: this, Handler: &ClangdLSPServer::onSuperTypes); |
1654 | Bind.method(Method: "typeHierarchy/subtypes" , This: this, Handler: &ClangdLSPServer::onSubTypes); |
1655 | Bind.method(Method: "textDocument/prepareCallHierarchy" , This: this, Handler: &ClangdLSPServer::onPrepareCallHierarchy); |
1656 | Bind.method(Method: "callHierarchy/incomingCalls" , This: this, Handler: &ClangdLSPServer::onCallHierarchyIncomingCalls); |
1657 | Bind.method(Method: "textDocument/selectionRange" , This: this, Handler: &ClangdLSPServer::onSelectionRange); |
1658 | Bind.method(Method: "textDocument/documentLink" , This: this, Handler: &ClangdLSPServer::onDocumentLink); |
1659 | Bind.method(Method: "textDocument/semanticTokens/full" , This: this, Handler: &ClangdLSPServer::onSemanticTokens); |
1660 | Bind.method(Method: "textDocument/semanticTokens/full/delta" , This: this, Handler: &ClangdLSPServer::onSemanticTokensDelta); |
1661 | Bind.method(Method: "clangd/inlayHints" , This: this, Handler: &ClangdLSPServer::onClangdInlayHints); |
1662 | Bind.method(Method: "textDocument/inlayHint" , This: this, Handler: &ClangdLSPServer::onInlayHint); |
1663 | Bind.method(Method: "$/memoryUsage" , This: this, Handler: &ClangdLSPServer::onMemoryUsage); |
1664 | Bind.method(Method: "textDocument/foldingRange" , This: this, Handler: &ClangdLSPServer::onFoldingRange); |
1665 | Bind.command(Method: ApplyFixCommand, This: this, Handler: &ClangdLSPServer::onCommandApplyEdit); |
1666 | Bind.command(Method: ApplyTweakCommand, This: this, Handler: &ClangdLSPServer::onCommandApplyTweak); |
1667 | |
1668 | ApplyWorkspaceEdit = Bind.outgoingMethod(Method: "workspace/applyEdit" ); |
1669 | PublishDiagnostics = Bind.outgoingNotification(Method: "textDocument/publishDiagnostics" ); |
1670 | if (Caps.InactiveRegions) |
1671 | PublishInactiveRegions = Bind.outgoingNotification(Method: "textDocument/inactiveRegions" ); |
1672 | ShowMessage = Bind.outgoingNotification(Method: "window/showMessage" ); |
1673 | NotifyFileStatus = Bind.outgoingNotification(Method: "textDocument/clangd.fileStatus" ); |
1674 | CreateWorkDoneProgress = Bind.outgoingMethod(Method: "window/workDoneProgress/create" ); |
1675 | BeginWorkDoneProgress = Bind.outgoingNotification(Method: "$/progress" ); |
1676 | ReportWorkDoneProgress = Bind.outgoingNotification(Method: "$/progress" ); |
1677 | EndWorkDoneProgress = Bind.outgoingNotification(Method: "$/progress" ); |
1678 | if(Caps.SemanticTokenRefreshSupport) |
1679 | SemanticTokensRefresh = Bind.outgoingMethod(Method: "workspace/semanticTokens/refresh" ); |
1680 | // clang-format on |
1681 | } |
1682 | |
1683 | ClangdLSPServer::~ClangdLSPServer() { |
1684 | IsBeingDestroyed = true; |
1685 | // Explicitly destroy ClangdServer first, blocking on threads it owns. |
1686 | // This ensures they don't access any other members. |
1687 | Server.reset(); |
1688 | } |
1689 | |
1690 | bool ClangdLSPServer::run() { |
1691 | // Run the Language Server loop. |
1692 | bool CleanExit = true; |
1693 | if (auto Err = Transp.loop(*MsgHandler)) { |
1694 | elog(Fmt: "Transport error: {0}" , Vals: std::move(Err)); |
1695 | CleanExit = false; |
1696 | } |
1697 | |
1698 | return CleanExit && ShutdownRequestReceived; |
1699 | } |
1700 | |
1701 | void ClangdLSPServer::profile(MemoryTree &MT) const { |
1702 | if (Server) |
1703 | Server->profile(MT&: MT.child(Name: "clangd_server" )); |
1704 | } |
1705 | |
1706 | std::optional<ClangdServer::DiagRef> |
1707 | ClangdLSPServer::getDiagRef(StringRef File, const clangd::Diagnostic &D) { |
1708 | std::lock_guard<std::mutex> Lock(DiagRefMutex); |
1709 | auto DiagToDiagRefIter = DiagRefMap.find(Key: File); |
1710 | if (DiagToDiagRefIter == DiagRefMap.end()) |
1711 | return std::nullopt; |
1712 | |
1713 | const auto &DiagToDiagRefMap = DiagToDiagRefIter->second; |
1714 | auto FixItsIter = DiagToDiagRefMap.find(x: toDiagKey(LSPDiag: D)); |
1715 | if (FixItsIter == DiagToDiagRefMap.end()) |
1716 | return std::nullopt; |
1717 | |
1718 | return FixItsIter->second; |
1719 | } |
1720 | |
1721 | // A completion request is sent when the user types '>' or ':', but we only |
1722 | // want to trigger on '->' and '::'. We check the preceding text to make |
1723 | // sure it matches what we expected. |
1724 | // Running the lexer here would be more robust (e.g. we can detect comments |
1725 | // and avoid triggering completion there), but we choose to err on the side |
1726 | // of simplicity here. |
1727 | bool ClangdLSPServer::shouldRunCompletion( |
1728 | const CompletionParams &Params) const { |
1729 | if (Params.context.triggerKind != CompletionTriggerKind::TriggerCharacter) |
1730 | return true; |
1731 | auto Code = Server->getDraft(File: Params.textDocument.uri.file()); |
1732 | if (!Code) |
1733 | return true; // completion code will log the error for untracked doc. |
1734 | auto Offset = positionToOffset(Code: *Code, P: Params.position, |
1735 | /*AllowColumnsBeyondLineLength=*/false); |
1736 | if (!Offset) { |
1737 | vlog(Fmt: "could not convert position '{0}' to offset for file '{1}'" , |
1738 | Vals: Params.position, Vals: Params.textDocument.uri.file()); |
1739 | return true; |
1740 | } |
1741 | return allowImplicitCompletion(Content: *Code, Offset: *Offset); |
1742 | } |
1743 | |
1744 | void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
1745 | llvm::ArrayRef<Diag> Diagnostics) { |
1746 | PublishDiagnosticsParams Notification; |
1747 | Notification.version = decodeVersion(Encoded: Version); |
1748 | Notification.uri = URIForFile::canonicalize(AbsPath: File, /*TUPath=*/File); |
1749 | DiagnosticToDiagRefMap LocalDiagMap; // Temporary storage |
1750 | for (auto &Diag : Diagnostics) { |
1751 | toLSPDiags(D: Diag, File: Notification.uri, Opts: DiagOpts, |
1752 | OutFn: [&](clangd::Diagnostic LSPDiag, llvm::ArrayRef<Fix> Fixes) { |
1753 | if (DiagOpts.EmbedFixesInDiagnostics) { |
1754 | std::vector<CodeAction> CodeActions; |
1755 | for (const auto &Fix : Fixes) |
1756 | CodeActions.push_back(x: toCodeAction( |
1757 | F: Fix, File: Notification.uri, Version: Notification.version, |
1758 | SupportsDocumentChanges, SupportChangeAnnotation: SupportsChangeAnnotation)); |
1759 | LSPDiag.codeActions.emplace(args: std::move(CodeActions)); |
1760 | if (LSPDiag.codeActions->size() == 1) |
1761 | LSPDiag.codeActions->front().isPreferred = true; |
1762 | } |
1763 | LocalDiagMap[toDiagKey(LSPDiag)] = {.Range: Diag.Range, .Message: Diag.Message}; |
1764 | Notification.diagnostics.push_back(x: std::move(LSPDiag)); |
1765 | }); |
1766 | } |
1767 | |
1768 | // Cache DiagRefMap |
1769 | { |
1770 | std::lock_guard<std::mutex> Lock(DiagRefMutex); |
1771 | DiagRefMap[File] = LocalDiagMap; |
1772 | } |
1773 | |
1774 | // Send a notification to the LSP client. |
1775 | PublishDiagnostics(Notification); |
1776 | } |
1777 | |
1778 | void ClangdLSPServer::onInactiveRegionsReady( |
1779 | PathRef File, std::vector<Range> InactiveRegions) { |
1780 | InactiveRegionsParams Notification; |
1781 | Notification.TextDocument = {.uri: URIForFile::canonicalize(AbsPath: File, /*TUPath=*/File)}; |
1782 | Notification.InactiveRegions = std::move(InactiveRegions); |
1783 | |
1784 | PublishInactiveRegions(Notification); |
1785 | } |
1786 | |
1787 | void ClangdLSPServer::onBackgroundIndexProgress( |
1788 | const BackgroundQueue::Stats &Stats) { |
1789 | static const char ProgressToken[] = "backgroundIndexProgress" ; |
1790 | |
1791 | // The background index did some work, maybe we need to cleanup |
1792 | maybeCleanupMemory(); |
1793 | |
1794 | std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex); |
1795 | |
1796 | auto NotifyProgress = [this](const BackgroundQueue::Stats &Stats) { |
1797 | if (BackgroundIndexProgressState != BackgroundIndexProgress::Live) { |
1798 | WorkDoneProgressBegin Begin; |
1799 | Begin.percentage = true; |
1800 | Begin.title = "indexing" ; |
1801 | BeginWorkDoneProgress({.token: ProgressToken, .value: std::move(Begin)}); |
1802 | BackgroundIndexProgressState = BackgroundIndexProgress::Live; |
1803 | } |
1804 | |
1805 | if (Stats.Completed < Stats.Enqueued) { |
1806 | assert(Stats.Enqueued > Stats.LastIdle); |
1807 | WorkDoneProgressReport Report; |
1808 | Report.percentage = 100 * (Stats.Completed - Stats.LastIdle) / |
1809 | (Stats.Enqueued - Stats.LastIdle); |
1810 | Report.message = |
1811 | llvm::formatv(Fmt: "{0}/{1}" , Vals: Stats.Completed - Stats.LastIdle, |
1812 | Vals: Stats.Enqueued - Stats.LastIdle); |
1813 | ReportWorkDoneProgress({.token: ProgressToken, .value: std::move(Report)}); |
1814 | } else { |
1815 | assert(Stats.Completed == Stats.Enqueued); |
1816 | EndWorkDoneProgress({.token: ProgressToken, .value: WorkDoneProgressEnd()}); |
1817 | BackgroundIndexProgressState = BackgroundIndexProgress::Empty; |
1818 | } |
1819 | }; |
1820 | |
1821 | switch (BackgroundIndexProgressState) { |
1822 | case BackgroundIndexProgress::Unsupported: |
1823 | return; |
1824 | case BackgroundIndexProgress::Creating: |
1825 | // Cache this update for when the progress bar is available. |
1826 | PendingBackgroundIndexProgress = Stats; |
1827 | return; |
1828 | case BackgroundIndexProgress::Empty: { |
1829 | if (BackgroundIndexSkipCreate) { |
1830 | NotifyProgress(Stats); |
1831 | break; |
1832 | } |
1833 | // Cache this update for when the progress bar is available. |
1834 | PendingBackgroundIndexProgress = Stats; |
1835 | BackgroundIndexProgressState = BackgroundIndexProgress::Creating; |
1836 | WorkDoneProgressCreateParams CreateRequest; |
1837 | CreateRequest.token = ProgressToken; |
1838 | CreateWorkDoneProgress( |
1839 | CreateRequest, |
1840 | [this, NotifyProgress](llvm::Expected<std::nullptr_t> E) { |
1841 | std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex); |
1842 | if (E) { |
1843 | NotifyProgress(this->PendingBackgroundIndexProgress); |
1844 | } else { |
1845 | elog(Fmt: "Failed to create background index progress bar: {0}" , |
1846 | Vals: E.takeError()); |
1847 | // give up forever rather than thrashing about |
1848 | BackgroundIndexProgressState = BackgroundIndexProgress::Unsupported; |
1849 | } |
1850 | }); |
1851 | break; |
1852 | } |
1853 | case BackgroundIndexProgress::Live: |
1854 | NotifyProgress(Stats); |
1855 | break; |
1856 | } |
1857 | } |
1858 | |
1859 | void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) { |
1860 | if (!SupportFileStatus) |
1861 | return; |
1862 | // FIXME: we don't emit "BuildingFile" and `RunningAction`, as these |
1863 | // two statuses are running faster in practice, which leads the UI constantly |
1864 | // changing, and doesn't provide much value. We may want to emit status at a |
1865 | // reasonable time interval (e.g. 0.5s). |
1866 | if (Status.PreambleActivity == PreambleAction::Idle && |
1867 | (Status.ASTActivity.K == ASTAction::Building || |
1868 | Status.ASTActivity.K == ASTAction::RunningAction)) |
1869 | return; |
1870 | NotifyFileStatus(Status.render(File)); |
1871 | } |
1872 | |
1873 | void ClangdLSPServer::onSemanticsMaybeChanged(PathRef File) { |
1874 | if (SemanticTokensRefresh) { |
1875 | SemanticTokensRefresh(NoParams{}, [](llvm::Expected<std::nullptr_t> E) { |
1876 | if (E) |
1877 | return; |
1878 | elog(Fmt: "Failed to refresh semantic tokens: {0}" , Vals: E.takeError()); |
1879 | }); |
1880 | } |
1881 | } |
1882 | |
1883 | } // namespace clangd |
1884 | } // namespace clang |
1885 | |