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

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