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