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

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