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