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
53namespace clang {
54namespace clangd {
55
56namespace {
57// Tracks end-to-end latency of high level lsp calls. Measurements are in
58// seconds.
59constexpr 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.
64std::string encodeVersion(std::optional<int64_t> LSPVersion) {
65 return LSPVersion ? llvm::to_string(Value: *LSPVersion) : "";
66}
67std::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
76const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix";
77const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak";
78const llvm::StringLiteral ApplyRenameCommand = "clangd.applyRename";
79
80CodeAction 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.
98CodeAction 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.
120CodeAction 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
149void 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
157SymbolKindBitset 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
165CompletionItemKindBitset 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.
176llvm::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.
206class ClangdLSPServer::MessageHandler : public Transport::MessageHandler {
207public:
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
322private:
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};
459constexpr int ClangdLSPServer::MessageHandler::MaxReplayCallbacks;
460
461// call(), notify(), and reply() wrap the Transport, adding logging and locking.
462void 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
470void 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
477static 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
485static 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
494void 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
705void ClangdLSPServer::onInitialized(const InitializedParams &Params) {}
706
707void 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!
716void 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
723void 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
734void 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
762void ClangdLSPServer::onDocumentDidSave(
763 const DidSaveTextDocumentParams &Params) {
764 Server->reparseOpenFilesIfNeeded(Filter: [](llvm::StringRef) { return true; });
765}
766
767void 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
779void 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
790void 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
803void 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
839void 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
849void 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
868void 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
884void 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
899void 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
925void 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
948void 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
955void 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
963void 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
978void 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.
996static std::vector<SymbolInformation>
997flattenSymbolHierarchy(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
1020void 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
1036void 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
1042static 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
1060void 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
1133void 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
1163void 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.
1189static 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
1203void 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
1221void 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
1240void 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
1255void 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
1262void 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
1289llvm::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
1319void 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
1338void 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
1358void 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
1365void 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
1371void ClangdLSPServer::onSubTypes(
1372 const ResolveTypeHierarchyItemParams &Params,
1373 Callback<std::vector<TypeHierarchyItem>> Reply) {
1374 Server->subTypes(Item: Params.item, CB: std::move(Reply));
1375}
1376
1377void 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
1384void ClangdLSPServer::onCallHierarchyIncomingCalls(
1385 const CallHierarchyIncomingCallsParams &Params,
1386 Callback<std::vector<CallHierarchyIncomingCall>> Reply) {
1387 Server->incomingCalls(Item: Params.item, std::move(Reply));
1388}
1389
1390void 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
1423void 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
1429void ClangdLSPServer::onCallHierarchyOutgoingCalls(
1430 const CallHierarchyOutgoingCallsParams &Params,
1431 Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
1432 Server->outgoingCalls(Item: Params.item, std::move(Reply));
1433}
1434
1435void 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
1453void 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
1465void ClangdLSPServer::maybeCleanupMemory() {
1466 if (!Opts.MemoryCleanup || !ShouldCleanupMemory())
1467 return;
1468 Opts.MemoryCleanup();
1469}
1470
1471// FIXME: This function needs to be properly tested.
1472void ClangdLSPServer::onChangeConfiguration(
1473 const DidChangeConfigurationParams &Params) {
1474 applyConfiguration(Settings: Params.settings);
1475}
1476
1477void 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
1500void 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
1515void 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
1531void 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
1537void 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
1550void 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 ...
1570static 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
1581void 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
1604void 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
1640void 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
1648void 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
1653ClangdLSPServer::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
1673void 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
1743ClangdLSPServer::~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
1750bool 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
1761void ClangdLSPServer::profile(MemoryTree &MT) const {
1762 if (Server)
1763 Server->profile(MT&: MT.child(Name: "clangd_server"));
1764}
1765
1766std::optional<ClangdServer::DiagRef>
1767ClangdLSPServer::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.
1787bool 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
1804void 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
1838void 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
1847void 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
1919void 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
1933void 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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clangd/ClangdLSPServer.cpp