1 //===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ClangdLSPServer.h"
10 #include "ClangdServer.h"
11 #include "CodeComplete.h"
12 #include "CompileCommands.h"
13 #include "Diagnostics.h"
15 #include "GlobalCompilationDatabase.h"
16 #include "LSPBinder.h"
18 #include "SemanticHighlighting.h"
19 #include "SourceCode.h"
20 #include "TUScheduler.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"
55 // Tracks end-to-end latency of high level lsp calls. Measurements are in
57 constexpr trace::Metric
LSPLatency("lsp_latency", trace::Metric::Distribution
,
60 // LSP defines file versions as numbers that increase.
61 // ClangdServer treats them as opaque and therefore uses strings instead.
62 std::string
encodeVersion(std::optional
<int64_t> LSPVersion
) {
63 return LSPVersion
? llvm::to_string(*LSPVersion
) : "";
65 std::optional
<int64_t> decodeVersion(llvm::StringRef Encoded
) {
67 if (llvm::to_integer(Encoded
, Result
, 10))
69 if (!Encoded
.empty()) // Empty can be e.g. diagnostics on close.
70 elog("unexpected non-numeric version {0}", Encoded
);
74 const llvm::StringLiteral ApplyFixCommand
= "clangd.applyFix";
75 const llvm::StringLiteral ApplyTweakCommand
= "clangd.applyTweak";
77 /// Transforms a tweak into a code action that would apply it if executed.
78 /// EXPECTS: T.prepare() was called and returned true.
79 CodeAction
toCodeAction(const ClangdServer::TweakRef
&T
, const URIForFile
&File
,
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
90 CA
.command
->title
= T
.Title
;
91 CA
.command
->command
= std::string(ApplyTweakCommand
);
95 Args
.selection
= Selection
;
96 CA
.command
->argument
= std::move(Args
);
100 /// Convert from Fix to LSP CodeAction.
101 CodeAction
toCodeAction(const Fix
&F
, const URIForFile
&File
,
102 const std::optional
<int64_t> &Version
,
103 bool SupportsDocumentChanges
,
104 bool SupportChangeAnnotation
) {
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({E
.range
, E
.newText
, /*annotationId=*/""});
115 Action
.edit
->documentChanges
.emplace();
116 TextDocumentEdit
&Edit
= Action
.edit
->documentChanges
->emplace_back();
117 Edit
.textDocument
= VersionedTextDocumentIdentifier
{{File
}, Version
};
118 for (const auto &E
: F
.Edits
)
119 Edit
.edits
.push_back(
121 SupportChangeAnnotation
? E
.annotationId
: ""});
122 if (SupportChangeAnnotation
) {
123 for (const auto &[AID
, Annotation
]: F
.Annotations
)
124 Action
.edit
->changeAnnotations
[AID
] = Annotation
;
130 void adjustSymbolKinds(llvm::MutableArrayRef
<DocumentSymbol
> Syms
,
131 SymbolKindBitset Kinds
) {
132 for (auto &S
: Syms
) {
133 S
.kind
= adjustKindToCapability(S
.kind
, Kinds
);
134 adjustSymbolKinds(S
.children
, Kinds
);
138 SymbolKindBitset
defaultSymbolKinds() {
139 SymbolKindBitset Defaults
;
140 for (size_t I
= SymbolKindMin
; I
<= static_cast<size_t>(SymbolKind::Array
);
146 CompletionItemKindBitset
defaultCompletionItemKinds() {
147 CompletionItemKindBitset Defaults
;
148 for (size_t I
= CompletionItemKindMin
;
149 I
<= static_cast<size_t>(CompletionItemKind::Reference
); ++I
)
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.
157 llvm::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(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(*Draft
)) {
167 LastInvalidFile
= It
.first();
171 if (!InvalidFileCount
)
172 return llvm::Error::success();
173 if (InvalidFileCount
== 1)
174 return error("File must be saved first: {0}", LastInvalidFile
);
175 return error("Files must be saved first: {0} (and {1} others)",
176 LastInvalidFile
, InvalidFileCount
- 1);
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.
187 class ClangdLSPServer::MessageHandler
: public Transport::MessageHandler
{
189 MessageHandler(ClangdLSPServer
&Server
) : Server(Server
) {}
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("<-- {0}", Method
);
196 if (Method
== "exit")
198 auto Handler
= Server
.Handlers
.NotificationHandlers
.find(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("Notification {0} before initialization", Method
);
205 } else if (Method
== "$/cancelRequest") {
206 onCancel(std::move(Params
));
208 log("unhandled notification {0}", Method
);
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("<-- {0}({1})", Method
, ID
);
222 auto Handler
= Server
.Handlers
.MethodHandlers
.find(Method
);
223 if (Handler
!= Server
.Handlers
.MethodHandlers
.end()) {
224 Handler
->second(std::move(Params
), std::move(Reply
));
225 } else if (!Server
.Server
) {
226 elog("Call {0} before initialization.", Method
);
227 Reply(llvm::make_error
<LSPError
>("server not initialized",
228 ErrorCode::ServerNotInitialized
));
230 Reply(llvm::make_error
<LSPError
>("method not found",
231 ErrorCode::MethodNotFound
));
236 bool onReply(llvm::json::Value ID
,
237 llvm::Expected
<llvm::json::Value
> Result
) override
{
238 WithContext
HandlerContext(handlerContext());
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(ReplyCallbacks
.begin() +
248 Index
); // remove the entry
255 // No callback being found, use a default log callback.
256 ReplyHandler
= [&ID
](llvm::Expected
<llvm::json::Value
> Result
) {
257 elog("received a reply with ID {0}, but there was no such call", ID
);
259 llvm::consumeError(Result
.takeError());
263 // Log and run the reply handler.
265 log("<-- reply({0})", ID
);
266 ReplyHandler(std::move(Result
));
268 auto Err
= Result
.takeError();
269 log("<-- reply({0}) error: {1}", ID
, Err
);
270 ReplyHandler(std::move(Err
));
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
;
282 std::lock_guard
<std::mutex
> Mutex(CallMutex
);
284 ReplyCallbacks
.emplace_back(ID
, std::move(Reply
));
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("more than {0} outstanding LSP calls, forgetting about {1}",
291 MaxReplayCallbacks
, ReplyCallbacks
.front().first
);
292 OldestCB
= std::move(ReplyCallbacks
.front());
293 ReplyCallbacks
.pop_front();
298 error("failed to receive a client reply for request ({0})",
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
310 std::atomic
<bool> Replied
= {false};
311 std::chrono::steady_clock::time_point Start
;
312 llvm::json::Value ID
;
314 ClangdLSPServer
*Server
; // Null when moved-from.
315 llvm::json::Object
*TraceArgs
;
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
) {
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;
330 ReplyOnce
&operator=(ReplyOnce
&&) = delete;
331 ReplyOnce(const ReplyOnce
&) = delete;
332 ReplyOnce
&operator=(const ReplyOnce
&) = delete;
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("No reply to message {0}({1})", Method
, ID
);
343 assert(false && "must reply to all calls!");
344 (*this)(llvm::make_error
<LSPError
>("server failed to reply",
345 ErrorCode::InternalError
));
349 void operator()(llvm::Expected
<llvm::json::Value
> Reply
) {
350 assert(Server
&& "moved-from!");
351 if (Replied
.exchange(true)) {
352 elog("Replied twice to message {0}({1})", Method
, ID
);
353 assert(false && "must reply to each call only once!");
356 auto Duration
= std::chrono::steady_clock::now() - Start
;
358 log("--> reply:{0}({1}) {2:ms}", Method
, ID
, Duration
);
360 (*TraceArgs
)["Reply"] = *Reply
;
361 std::lock_guard
<std::mutex
> Lock(Server
->TranspWriter
);
362 Server
->Transp
.reply(std::move(ID
), std::move(Reply
));
364 llvm::Error Err
= Reply
.takeError();
365 log("--> reply:{0}({1}) {2:ms}, error: {3}", Method
, ID
, Duration
, Err
);
367 (*TraceArgs
)["Error"] = llvm::to_string(Err
);
368 std::lock_guard
<std::mutex
> Lock(Server
->TranspWriter
);
369 Server
->Transp
.reply(std::move(ID
), std::move(Err
));
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())
385 elog("Bad cancellation request: {0}", Params
);
388 auto StrID
= llvm::to_string(*ID
);
389 std::lock_guard
<std::mutex
> Lock(RequestCancelersMutex
);
390 auto It
= RequestCancelers
.find(StrID
);
391 if (It
!= RequestCancelers
.end())
392 It
->second
.first(); // Invoke the canceler.
395 Context
handlerContext() const {
396 return Context::current().derive(
397 kCurrentOffsetEncoding
,
398 Server
.Opts
.Encoding
.value_or(OffsetEncoding::UTF16
));
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(ID
); // JSON-serialize ID for map key.
409 auto Cookie
= NextRequestCookie
++; // No lock, only called on main thread.
411 std::lock_guard
<std::mutex
> Lock(RequestCancelersMutex
);
412 RequestCancelers
[StrID
] = {std::move(Task
.second
), Cookie
};
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
417 return Task
.first
.derive(llvm::make_scope_exit([this, StrID
, Cookie
] {
418 std::lock_guard
<std::mutex
> Lock(RequestCancelersMutex
);
419 auto It
= RequestCancelers
.find(StrID
);
420 if (It
!= RequestCancelers
.end() && It
->second
.second
== Cookie
)
421 RequestCancelers
.erase(It
);
425 // The maximum number of callbacks held in clangd.
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) */
438 ClangdLSPServer
&Server
;
440 constexpr int ClangdLSPServer::MessageHandler::MaxReplayCallbacks
;
442 // call(), notify(), and reply() wrap the Transport, adding logging and locking.
443 void ClangdLSPServer::callMethod(StringRef Method
, llvm::json::Value Params
,
444 Callback
<llvm::json::Value
> CB
) {
445 auto ID
= MsgHandler
->bindReply(std::move(CB
));
446 log("--> {0}({1})", Method
, ID
);
447 std::lock_guard
<std::mutex
> Lock(TranspWriter
);
448 Transp
.call(Method
, std::move(Params
), ID
);
451 void ClangdLSPServer::notify(llvm::StringRef Method
, llvm::json::Value Params
) {
452 log("--> {0}", Method
);
453 maybeCleanupMemory();
454 std::lock_guard
<std::mutex
> Lock(TranspWriter
);
455 Transp
.notify(Method
, std::move(Params
));
458 static std::vector
<llvm::StringRef
> semanticTokenTypes() {
459 std::vector
<llvm::StringRef
> Types
;
460 for (unsigned I
= 0; I
<= static_cast<unsigned>(HighlightingKind::LastKind
);
462 Types
.push_back(toSemanticTokenType(static_cast<HighlightingKind
>(I
)));
466 static std::vector
<llvm::StringRef
> semanticTokenModifiers() {
467 std::vector
<llvm::StringRef
> Modifiers
;
469 I
<= static_cast<unsigned>(HighlightingModifier::LastModifier
); ++I
)
471 toSemanticTokenModifier(static_cast<HighlightingModifier
>(I
)));
475 void 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
;
487 if (Params
.capabilities
.TheiaSemanticHighlighting
&&
488 !Params
.capabilities
.SemanticTokens
) {
489 elog("Client requested legacy semanticHighlights notification, which is "
490 "no longer supported. Migrate to standard semanticTokens request");
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
;
498 return Reply(llvm::make_error
<LSPError
>("server already initialized",
499 ErrorCode::InvalidRequest
));
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
;
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
;
540 std::make_unique
<DirectoryBasedGlobalCompilationDatabase
>(CDBOpts
);
542 auto Mangler
= CommandMangler::detect();
543 Mangler
.SystemIncludeExtractor
=
544 getSystemIncludeExtractor(llvm::ArrayRef(Opts
.QueryDriverGlobs
));
545 if (Opts
.ResourceDir
)
546 Mangler
.ResourceDir
= *Opts
.ResourceDir
;
547 CDB
.emplace(BaseCDB
.get(), Params
.initializationOptions
.fallbackFlags
,
550 // Switch caller's context with LSPServer's background context. Since we
551 // rather want to propagate information from LSPServer's context into the
553 WithContext
MainContext(BackgroundContext
.clone());
554 std::optional
<WithContextValue
> WithOffsetEncoding
;
556 WithOffsetEncoding
.emplace(kCurrentOffsetEncoding
, *Opts
.Encoding
);
557 Server
.emplace(*CDB
, TFS
, Opts
,
558 static_cast<ClangdServer::Callbacks
*>(this));
561 llvm::json::Object ServerCaps
{
565 {"change", (int)TextDocumentSyncKind::Incremental
},
568 {"documentFormattingProvider", true},
569 {"documentRangeFormattingProvider", true},
570 {"documentOnTypeFormattingProvider",
572 {"firstTriggerCharacter", "\n"},
573 {"moreTriggerCharacter", {}},
575 {"completionProvider",
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 {"resolveProvider", false},
583 // We do extra checks, e.g. that > is part of ->.
584 {"triggerCharacters", {".", "<", ">", ":", "\"", "/", "*"}},
586 {"semanticTokensProvider",
588 {"full", llvm::json::Object
{{"delta", true}}},
591 llvm::json::Object
{{"tokenTypes", semanticTokenTypes()},
592 {"tokenModifiers", semanticTokenModifiers()}}},
594 {"signatureHelpProvider",
596 {"triggerCharacters", {"(", ")", "{", "}", "<", ">", ","}},
598 {"declarationProvider", true},
599 {"definitionProvider", true},
600 {"implementationProvider", true},
601 {"typeDefinitionProvider", true},
602 {"documentHighlightProvider", true},
603 {"documentLinkProvider",
605 {"resolveProvider", false},
607 {"hoverProvider", true},
608 {"selectionRangeProvider", true},
609 {"documentSymbolProvider", true},
610 {"workspaceSymbolProvider", true},
611 {"referencesProvider", true},
612 {"astProvider", true}, // clangd extension
613 {"typeHierarchyProvider", 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 {"standardTypeHierarchyProvider", true}, // clangd extension
618 {"memoryUsageProvider", true}, // clangd extension
619 {"compilationDatabase", // clangd extension
620 llvm::json::Object
{{"automaticReload", true}}},
621 {"inactiveRegionsProvider", true}, // clangd extension
622 {"callHierarchyProvider", true},
623 {"clangdInlayHintsProvider", true},
624 {"inlayHintProvider", true},
625 {"foldingRangeProvider", true},
629 LSPBinder
Binder(Handlers
, *this);
630 bindMethods(Binder
, Params
.capabilities
);
631 if (Opts
.FeatureModules
)
632 for (auto &Mod
: *Opts
.FeatureModules
)
633 Mod
.initializeLSP(Binder
, Params
.rawCapabilities
, ServerCaps
);
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
{{"prepareProvider", true}}
641 : llvm::json::Value(true);
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
{{"codeActionKinds",
649 {CodeAction::QUICKFIX_KIND
,
650 CodeAction::REFACTOR_KIND
,
651 CodeAction::INFO_KIND
}}}
652 : llvm::json::Value(true);
654 std::vector
<llvm::StringRef
> Commands
;
655 for (llvm::StringRef Command
: Handlers
.CommandHandlers
.keys())
656 Commands
.push_back(Command
);
657 llvm::sort(Commands
);
658 ServerCaps
["executeCommandProvider"] =
659 llvm::json::Object
{{"commands", Commands
}};
661 llvm::json::Object Result
{
665 {"version", llvm::formatv("{0} {1} {2}", versionString(),
666 featureString(), platformString())}}},
667 {"capabilities", std::move(ServerCaps
)}}};
669 Result
["offsetEncoding"] = *Opts
.Encoding
;
670 Reply(std::move(Result
));
672 // Apply settings after we're fully initialized.
673 // This can start background indexing and in turn trigger LSP notifications.
674 applyConfiguration(Params
.initializationOptions
.ConfigSettings
);
677 void ClangdLSPServer::onInitialized(const InitializedParams
&Params
) {}
679 void ClangdLSPServer::onShutdown(const NoParams
&,
680 Callback
<std::nullptr_t
> Reply
) {
681 // Do essentially nothing, just say we're ready to exit.
682 ShutdownRequestReceived
= true;
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!
688 void ClangdLSPServer::onSync(const NoParams
&, Callback
<std::nullptr_t
> Reply
) {
689 if (Server
->blockUntilIdleForTest(/*TimeoutSeconds=*/60))
692 Reply(error("Not idle after a minute"));
695 void ClangdLSPServer::onDocumentDidOpen(
696 const DidOpenTextDocumentParams
&Params
) {
697 PathRef File
= Params
.textDocument
.uri
.file();
699 const std::string
&Contents
= Params
.textDocument
.text
;
701 Server
->addDocument(File
, Contents
,
702 encodeVersion(Params
.textDocument
.version
),
703 WantDiagnostics::Yes
);
706 void ClangdLSPServer::onDocumentDidChange(
707 const DidChangeTextDocumentParams
&Params
) {
708 auto WantDiags
= WantDiagnostics::Auto
;
709 if (Params
.wantDiagnostics
)
711 *Params
.wantDiagnostics
? WantDiagnostics::Yes
: WantDiagnostics::No
;
713 PathRef File
= Params
.textDocument
.uri
.file();
714 auto Code
= Server
->getDraft(File
);
716 log("Trying to incrementally change non-added document: {0}", File
);
719 std::string
NewCode(*Code
);
720 for (const auto &Change
: Params
.contentChanges
) {
721 if (auto Err
= applyChange(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("Failed to update {0}: {1}", File
, std::move(Err
));
730 Server
->addDocument(File
, NewCode
, encodeVersion(Params
.textDocument
.version
),
731 WantDiags
, Params
.forceRebuild
);
734 void ClangdLSPServer::onDocumentDidSave(
735 const DidSaveTextDocumentParams
&Params
) {
736 Server
->reparseOpenFilesIfNeeded([](llvm::StringRef
) { return true; });
739 void 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
751 void ClangdLSPServer::onCommand(const ExecuteCommandParams
&Params
,
752 Callback
<llvm::json::Value
> Reply
) {
753 auto It
= Handlers
.CommandHandlers
.find(Params
.command
);
754 if (It
== Handlers
.CommandHandlers
.end()) {
755 return Reply(llvm::make_error
<LSPError
>(
756 llvm::formatv("Unsupported command \"{0}\".", Params
.command
).str(),
757 ErrorCode::InvalidParams
));
759 It
->second(Params
.argument
, std::move(Reply
));
762 void 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
, "Fix applied.", std::move(Reply
));
775 void 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 {
780 return Reply(R
.takeError());
782 assert(R
->ShowMessage
|| (!R
->ApplyEdits
.empty() && "tweak has no effect"));
784 if (R
->ShowMessage
) {
785 ShowMessageParams Msg
;
786 Msg
.message
= *R
->ShowMessage
;
787 Msg
.type
= MessageType::Info
;
790 // When no edit is specified, make sure we Reply().
791 if (R
->ApplyEdits
.empty())
792 return Reply("Tweak applied.");
794 if (auto Err
= validateEdits(*Server
, R
->ApplyEdits
))
795 return Reply(std::move(Err
));
798 // FIXME: use documentChanges when SupportDocumentChanges is true.
799 WE
.changes
.emplace();
800 for (const auto &It
: R
->ApplyEdits
) {
801 (*WE
.changes
)[URI::createFile(It
.first()).toString()] =
802 It
.second
.asTextEdits();
804 // ApplyEdit will take care of calling Reply().
805 return applyEdit(std::move(WE
), "Tweak applied.", std::move(Reply
));
807 Server
->applyTweak(Args
.file
.file(), Args
.selection
, Args
.tweakID
,
811 void ClangdLSPServer::applyEdit(WorkspaceEdit WE
, llvm::json::Value Success
,
812 Callback
<llvm::json::Value
> Reply
) {
813 ApplyWorkspaceEditParams Edit
;
814 Edit
.edit
= std::move(WE
);
816 Edit
, [Reply
= std::move(Reply
), SuccessMessage
= std::move(Success
)](
817 llvm::Expected
<ApplyWorkspaceEditResponse
> Response
) mutable {
819 return Reply(Response
.takeError());
820 if (!Response
->applied
) {
821 std::string Reason
= Response
->failureReason
822 ? *Response
->failureReason
824 return Reply(error("edits were not applied: {0}", Reason
));
826 return Reply(SuccessMessage
);
830 void ClangdLSPServer::onWorkspaceSymbol(
831 const WorkspaceSymbolParams
&Params
,
832 Callback
<std::vector
<SymbolInformation
>> Reply
) {
833 Server
->workspaceSymbols(
834 Params
.query
, Params
.limit
.value_or(Opts
.CodeComplete
.Limit
),
835 [Reply
= std::move(Reply
),
836 this](llvm::Expected
<std::vector
<SymbolInformation
>> Items
) mutable {
838 return Reply(Items
.takeError());
839 for (auto &Sym
: *Items
)
840 Sym
.kind
= adjustKindToCapability(Sym
.kind
, SupportedSymbolKinds
);
842 Reply(std::move(*Items
));
846 void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams
&Params
,
847 Callback
<std::optional
<Range
>> Reply
) {
848 Server
->prepareRename(
849 Params
.textDocument
.uri
.file(), Params
.position
, /*NewName*/ std::nullopt
,
851 [Reply
= std::move(Reply
)](llvm::Expected
<RenameResult
> Result
) mutable {
853 return Reply(Result
.takeError());
854 return Reply(std::move(Result
->Target
));
858 void 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 "onRename called for non-added file", ErrorCode::InvalidParams
));
864 Server
->rename(File
, Params
.position
, Params
.newName
, Opts
.Rename
,
865 [File
, Params
, Reply
= std::move(Reply
),
866 this](llvm::Expected
<RenameResult
> R
) mutable {
868 return Reply(R
.takeError());
869 if (auto Err
= validateEdits(*Server
, R
->GlobalChanges
))
870 return Reply(std::move(Err
));
871 WorkspaceEdit Result
;
872 // FIXME: use documentChanges if SupportDocumentChanges is
874 Result
.changes
.emplace();
875 for (const auto &Rep
: R
->GlobalChanges
) {
877 .changes
)[URI::createFile(Rep
.first()).toString()] =
878 Rep
.second
.asTextEdits();
884 void ClangdLSPServer::onDocumentDidClose(
885 const DidCloseTextDocumentParams
&Params
) {
886 PathRef File
= Params
.textDocument
.uri
.file();
887 Server
->removeDocument(File
);
890 std::lock_guard
<std::mutex
> Lock(DiagRefMutex
);
891 DiagRefMap
.erase(File
);
894 std::lock_guard
<std::mutex
> HLock(SemanticTokensMutex
);
895 LastSemanticTokens
.erase(File
);
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(File
, /*TUPath=*/File
);
904 PublishDiagnostics(Notification
);
907 void ClangdLSPServer::onDocumentOnTypeFormatting(
908 const DocumentOnTypeFormattingParams
&Params
,
909 Callback
<std::vector
<TextEdit
>> Reply
) {
910 auto File
= Params
.textDocument
.uri
.file();
911 Server
->formatOnType(File
, Params
.position
, Params
.ch
, std::move(Reply
));
914 void 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
, Params
.range
,
920 [Code
= std::move(Code
), Reply
= std::move(Reply
)](
921 llvm::Expected
<tooling::Replacements
> Result
) mutable {
923 Reply(replacementsToEdits(*Code
, Result
.get()));
925 Reply(Result
.takeError());
929 void 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 [Code
= std::move(Code
), Reply
= std::move(Reply
)](
937 llvm::Expected
<tooling::Replacements
> Result
) mutable {
939 Reply(replacementsToEdits(*Code
, Result
.get()));
941 Reply(Result
.takeError());
945 /// The functions constructs a flattened view of the DocumentSymbol hierarchy.
946 /// Used by the clients that do not support the hierarchical view.
947 static std::vector
<SymbolInformation
>
948 flattenSymbolHierarchy(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
);
957 SI
.location
.range
= S
.range
;
958 SI
.location
.uri
= FileURI
;
960 Results
.push_back(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
);
966 for (auto &S
: Symbols
)
967 Process(S
, /*ParentName=*/"");
971 void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams
&Params
,
972 Callback
<llvm::json::Value
> Reply
) {
973 URIForFile FileURI
= Params
.textDocument
.uri
;
974 Server
->documentSymbols(
975 Params
.textDocument
.uri
.file(),
976 [this, FileURI
, Reply
= std::move(Reply
)](
977 llvm::Expected
<std::vector
<DocumentSymbol
>> Items
) mutable {
979 return Reply(Items
.takeError());
980 adjustSymbolKinds(*Items
, SupportedSymbolKinds
);
981 if (SupportsHierarchicalDocumentSymbol
)
982 return Reply(std::move(*Items
));
983 return Reply(flattenSymbolHierarchy(*Items
, FileURI
));
987 void ClangdLSPServer::onFoldingRange(
988 const FoldingRangeParams
&Params
,
989 Callback
<std::vector
<FoldingRange
>> Reply
) {
990 Server
->foldingRanges(Params
.textDocument
.uri
.file(), std::move(Reply
));
993 static std::optional
<Command
> asCommand(const CodeAction
&Action
) {
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
;
1003 return std::nullopt
;
1005 Cmd
.title
= Action
.title
;
1006 if (Action
.kind
&& *Action
.kind
== CodeAction::QUICKFIX_KIND
)
1007 Cmd
.title
= "Apply fix: " + Cmd
.title
;
1011 void 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
;
1017 for (const auto& LSPDiag
: Params
.context
.diagnostics
) {
1018 if (auto DiagRef
= getDiagRef(File
.file(), LSPDiag
)) {
1019 ToLSPDiags
[*DiagRef
] = LSPDiag
;
1020 Inputs
.Diagnostics
.push_back(*DiagRef
);
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
);
1030 Reply
= std::move(Reply
),
1031 ToLSPDiags
= std::move(ToLSPDiags
), File
,
1032 Selection
= Params
.range
](
1033 llvm::Expected
<ClangdServer::CodeActionResult
> Fixits
) mutable {
1035 return Reply(Fixits
.takeError());
1036 std::vector
<CodeAction
> CAs
;
1037 auto Version
= decodeVersion(Fixits
->Version
);
1038 for (const auto &QF
: Fixits
->QuickFixes
) {
1039 CAs
.push_back(toCodeAction(QF
.F
, File
, Version
, SupportsDocumentChanges
,
1040 SupportsChangeAnnotation
));
1041 if (auto It
= ToLSPDiags
.find(QF
.Diag
);
1042 It
!= ToLSPDiags
.end()) {
1043 CAs
.back().diagnostics
= {It
->second
};
1046 for (const auto &TR
: Fixits
->TweakRefs
)
1047 CAs
.push_back(toCodeAction(TR
, File
, Selection
));
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
) {
1062 OnlyFix
->isPreferred
= true;
1063 if (ToLSPDiags
.size() == 1 &&
1064 ToLSPDiags
.begin()->second
.range
== Selection
)
1065 OnlyFix
->diagnostics
= {ToLSPDiags
.begin()->second
};
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(std::move(*Command
));
1075 return Reply(llvm::json::Array(Commands
));
1077 Server
->codeAction(Inputs
, std::move(CB
));
1080 void 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("ignored auto-triggered completion, preceding char did not match");
1086 return Reply(CompletionList());
1088 auto Opts
= this->Opts
.CodeComplete
;
1089 if (Params
.limit
&& *Params
.limit
>= 0)
1090 Opts
.Limit
= *Params
.limit
;
1091 Server
->codeComplete(Params
.textDocument
.uri
.file(), Params
.position
, Opts
,
1092 [Reply
= std::move(Reply
), Opts
,
1093 this](llvm::Expected
<CodeCompleteResult
> List
) mutable {
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 C
.kind
, SupportedCompletionItemKinds
);
1102 if (!SupportsCompletionLabelDetails
)
1103 removeCompletionLabelDetails(C
);
1104 LSPList
.items
.push_back(std::move(C
));
1106 return Reply(std::move(LSPList
));
1110 void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams
&Params
,
1111 Callback
<SignatureHelp
> Reply
) {
1112 Server
->signatureHelp(Params
.textDocument
.uri
.file(), Params
.position
,
1113 Opts
.SignatureHelpDocumentationFormat
,
1114 [Reply
= std::move(Reply
), this](
1115 llvm::Expected
<SignatureHelp
> Signature
) mutable {
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();
1126 return Reply(std::move(*Signature
));
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.
1134 // We return the toggled location alone (ignoring other symbols) to encourage
1135 // editors to "bounce" quickly between locations, without showing a menu.
1136 static 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
)
1141 if (Sym
.Definition
->uri
.file() == Point
.textDocument
.uri
.file() &&
1142 Sym
.Definition
->range
.contains(Point
.position
))
1143 return &Sym
.PreferredDeclaration
;
1144 if (Sym
.PreferredDeclaration
.uri
.file() == Point
.textDocument
.uri
.file() &&
1145 Sym
.PreferredDeclaration
.range
.contains(Point
.position
))
1146 return &*Sym
.Definition
;
1150 void ClangdLSPServer::onGoToDefinition(const TextDocumentPositionParams
&Params
,
1151 Callback
<std::vector
<Location
>> Reply
) {
1152 Server
->locateSymbolAt(
1153 Params
.textDocument
.uri
.file(), Params
.position
,
1154 [Params
, Reply
= std::move(Reply
)](
1155 llvm::Expected
<std::vector
<LocatedSymbol
>> Symbols
) mutable {
1157 return Reply(Symbols
.takeError());
1158 std::vector
<Location
> Defs
;
1159 for (auto &S
: *Symbols
) {
1160 if (Location
*Toggle
= getToggle(Params
, S
))
1161 return Reply(std::vector
<Location
>{std::move(*Toggle
)});
1162 Defs
.push_back(S
.Definition
.value_or(S
.PreferredDeclaration
));
1164 Reply(std::move(Defs
));
1168 void ClangdLSPServer::onGoToDeclaration(
1169 const TextDocumentPositionParams
&Params
,
1170 Callback
<std::vector
<Location
>> Reply
) {
1171 Server
->locateSymbolAt(
1172 Params
.textDocument
.uri
.file(), Params
.position
,
1173 [Params
, Reply
= std::move(Reply
)](
1174 llvm::Expected
<std::vector
<LocatedSymbol
>> Symbols
) mutable {
1176 return Reply(Symbols
.takeError());
1177 std::vector
<Location
> Decls
;
1178 for (auto &S
: *Symbols
) {
1179 if (Location
*Toggle
= getToggle(Params
, S
))
1180 return Reply(std::vector
<Location
>{std::move(*Toggle
)});
1181 Decls
.push_back(std::move(S
.PreferredDeclaration
));
1183 Reply(std::move(Decls
));
1187 void ClangdLSPServer::onSwitchSourceHeader(
1188 const TextDocumentIdentifier
&Params
,
1189 Callback
<std::optional
<URIForFile
>> Reply
) {
1190 Server
->switchSourceHeader(
1192 [Reply
= std::move(Reply
),
1193 Params
](llvm::Expected
<std::optional
<clangd::Path
>> Path
) mutable {
1195 return Reply(Path
.takeError());
1197 return Reply(URIForFile::canonicalize(**Path
, Params
.uri
.file()));
1198 return Reply(std::nullopt
);
1202 void ClangdLSPServer::onDocumentHighlight(
1203 const TextDocumentPositionParams
&Params
,
1204 Callback
<std::vector
<DocumentHighlight
>> Reply
) {
1205 Server
->findDocumentHighlights(Params
.textDocument
.uri
.file(),
1206 Params
.position
, std::move(Reply
));
1209 void ClangdLSPServer::onHover(const TextDocumentPositionParams
&Params
,
1210 Callback
<std::optional
<Hover
>> Reply
) {
1211 Server
->findHover(Params
.textDocument
.uri
.file(), Params
.position
,
1212 [Reply
= std::move(Reply
),
1213 this](llvm::Expected
<std::optional
<HoverInfo
>> H
) mutable {
1215 return Reply(H
.takeError());
1217 return Reply(std::nullopt
);
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
));
1230 llvm_unreachable("unhandled MarkupKind");
1234 // Our extension has a different representation on the wire than the standard.
1235 // https://clangd.llvm.org/extensions#type-hierarchy
1236 llvm::json::Value
serializeTHIForExtension(TypeHierarchyItem THI
) {
1237 llvm::json::Object Result
{{
1238 {"name", std::move(THI
.name
)},
1239 {"kind", static_cast<int>(THI
.kind
)},
1240 {"uri", std::move(THI
.uri
)},
1241 {"range", THI
.range
},
1242 {"selectionRange", THI
.selectionRange
},
1243 {"data", std::move(THI
.data
)},
1246 Result
["deprecated"] = THI
.deprecated
;
1248 Result
["detail"] = std::move(*THI
.detail
);
1251 llvm::json::Array Parents
;
1252 for (auto &Parent
: *THI
.parents
)
1253 Parents
.emplace_back(serializeTHIForExtension(std::move(Parent
)));
1254 Result
["parents"] = std::move(Parents
);
1258 llvm::json::Array Children
;
1259 for (auto &child
: *THI
.children
)
1260 Children
.emplace_back(serializeTHIForExtension(std::move(child
)));
1261 Result
["children"] = std::move(Children
);
1266 void ClangdLSPServer::onTypeHierarchy(const TypeHierarchyPrepareParams
&Params
,
1267 Callback
<llvm::json::Value
> Reply
) {
1269 [Reply
= std::move(Reply
)](
1270 llvm::Expected
<std::vector
<TypeHierarchyItem
>> Resp
) mutable {
1272 Reply(Resp
.takeError());
1275 if (Resp
->empty()) {
1279 Reply(serializeTHIForExtension(std::move(Resp
->front())));
1281 Server
->typeHierarchy(Params
.textDocument
.uri
.file(), Params
.position
,
1282 Params
.resolve
, Params
.direction
, std::move(Serialize
));
1285 void ClangdLSPServer::onResolveTypeHierarchy(
1286 const ResolveTypeHierarchyItemParams
&Params
,
1287 Callback
<llvm::json::Value
> Reply
) {
1289 [Reply
= std::move(Reply
)](
1290 llvm::Expected
<std::optional
<TypeHierarchyItem
>> Resp
) mutable {
1292 Reply(Resp
.takeError());
1296 Reply(std::move(*Resp
));
1299 Reply(serializeTHIForExtension(std::move(**Resp
)));
1301 Server
->resolveTypeHierarchy(Params
.item
, Params
.resolve
, Params
.direction
,
1302 std::move(Serialize
));
1305 void ClangdLSPServer::onPrepareTypeHierarchy(
1306 const TypeHierarchyPrepareParams
&Params
,
1307 Callback
<std::vector
<TypeHierarchyItem
>> Reply
) {
1308 Server
->typeHierarchy(Params
.textDocument
.uri
.file(), Params
.position
,
1309 Params
.resolve
, Params
.direction
, std::move(Reply
));
1312 void ClangdLSPServer::onSuperTypes(
1313 const ResolveTypeHierarchyItemParams
&Params
,
1314 Callback
<std::optional
<std::vector
<TypeHierarchyItem
>>> Reply
) {
1315 Server
->superTypes(Params
.item
, std::move(Reply
));
1318 void ClangdLSPServer::onSubTypes(
1319 const ResolveTypeHierarchyItemParams
&Params
,
1320 Callback
<std::vector
<TypeHierarchyItem
>> Reply
) {
1321 Server
->subTypes(Params
.item
, std::move(Reply
));
1324 void ClangdLSPServer::onPrepareCallHierarchy(
1325 const CallHierarchyPrepareParams
&Params
,
1326 Callback
<std::vector
<CallHierarchyItem
>> Reply
) {
1327 Server
->prepareCallHierarchy(Params
.textDocument
.uri
.file(), Params
.position
,
1331 void ClangdLSPServer::onCallHierarchyIncomingCalls(
1332 const CallHierarchyIncomingCallsParams
&Params
,
1333 Callback
<std::vector
<CallHierarchyIncomingCall
>> Reply
) {
1334 Server
->incomingCalls(Params
.item
, std::move(Reply
));
1337 void 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
1342 // https://clangd.llvm.org/extensions#inlay-hints
1343 auto Serialize
= [Reply
= std::move(Reply
)](
1344 llvm::Expected
<std::vector
<InlayHint
>> Hints
) mutable {
1346 Reply(Hints
.takeError());
1349 llvm::json::Array Result
;
1350 Result
.reserve(Hints
->size());
1351 for (auto &Hint
: *Hints
) {
1352 Result
.emplace_back(llvm::json::Object
{
1353 {"kind", llvm::to_string(Hint
.kind
)},
1354 {"range", Hint
.range
},
1355 {"position", Hint
.position
},
1356 // Extension doesn't have paddingLeft/Right so adjust the label
1359 ((Hint
.paddingLeft
? " " : "") + llvm::StringRef(Hint
.label
) +
1360 (Hint
.paddingRight
? " " : ""))
1364 Reply(std::move(Result
));
1366 Server
->inlayHints(Params
.textDocument
.uri
.file(), Params
.range
,
1367 std::move(Serialize
));
1370 void ClangdLSPServer::onInlayHint(const InlayHintsParams
&Params
,
1371 Callback
<std::vector
<InlayHint
>> Reply
) {
1372 Server
->inlayHints(Params
.textDocument
.uri
.file(), Params
.range
,
1376 void 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
);
1384 tooling::CompileCommand(std::move(Entry
.second
.workingDirectory
), File
,
1385 std::move(Entry
.second
.compilationCommand
),
1388 CDB
->setCompileCommand(File
, std::move(New
));
1389 ModifiedFiles
.insert(File
);
1393 Server
->reparseOpenFilesIfNeeded(
1394 [&](llvm::StringRef File
) { return ModifiedFiles
.count(File
) != 0; });
1397 void ClangdLSPServer::maybeExportMemoryProfile() {
1398 if (!trace::enabled() || !ShouldProfile())
1401 static constexpr trace::Metric
MemoryUsage(
1402 "memory_usage", trace::Metric::Value
, "component_name");
1403 trace::Span
Tracer("ProfileBrief");
1406 record(MT
, "clangd_lsp_server", MemoryUsage
);
1409 void ClangdLSPServer::maybeCleanupMemory() {
1410 if (!Opts
.MemoryCleanup
|| !ShouldCleanupMemory())
1412 Opts
.MemoryCleanup();
1415 // FIXME: This function needs to be properly tested.
1416 void ClangdLSPServer::onChangeConfiguration(
1417 const DidChangeConfigurationParams
&Params
) {
1418 applyConfiguration(Params
.settings
);
1421 void ClangdLSPServer::onReference(
1422 const ReferenceParams
&Params
,
1423 Callback
<std::vector
<ReferenceLocation
>> Reply
) {
1424 Server
->findReferences(Params
.textDocument
.uri
.file(), Params
.position
,
1425 Opts
.ReferencesLimit
, SupportsReferenceContainer
,
1426 [Reply
= std::move(Reply
),
1427 IncludeDecl(Params
.context
.includeDeclaration
)](
1428 llvm::Expected
<ReferencesResult
> Refs
) mutable {
1430 return Reply(Refs
.takeError());
1431 // Filter out declarations if the client asked.
1432 std::vector
<ReferenceLocation
> Result
;
1433 Result
.reserve(Refs
->References
.size());
1434 for (auto &Ref
: Refs
->References
) {
1436 Ref
.Attributes
& ReferencesResult::Declaration
;
1437 if (IncludeDecl
|| !IsDecl
)
1438 Result
.push_back(std::move(Ref
.Loc
));
1440 return Reply(std::move(Result
));
1444 void ClangdLSPServer::onGoToType(const TextDocumentPositionParams
&Params
,
1445 Callback
<std::vector
<Location
>> Reply
) {
1447 Params
.textDocument
.uri
.file(), Params
.position
,
1448 [Reply
= std::move(Reply
)](
1449 llvm::Expected
<std::vector
<LocatedSymbol
>> Types
) mutable {
1451 return Reply(Types
.takeError());
1452 std::vector
<Location
> Response
;
1453 for (const LocatedSymbol
&Sym
: *Types
)
1454 Response
.push_back(Sym
.Definition
.value_or(Sym
.PreferredDeclaration
));
1455 return Reply(std::move(Response
));
1459 void ClangdLSPServer::onGoToImplementation(
1460 const TextDocumentPositionParams
&Params
,
1461 Callback
<std::vector
<Location
>> Reply
) {
1462 Server
->findImplementations(
1463 Params
.textDocument
.uri
.file(), Params
.position
,
1464 [Reply
= std::move(Reply
)](
1465 llvm::Expected
<std::vector
<LocatedSymbol
>> Overrides
) mutable {
1467 return Reply(Overrides
.takeError());
1468 std::vector
<Location
> Impls
;
1469 for (const LocatedSymbol
&Sym
: *Overrides
)
1470 Impls
.push_back(Sym
.Definition
.value_or(Sym
.PreferredDeclaration
));
1471 return Reply(std::move(Impls
));
1475 void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams
&Params
,
1476 Callback
<std::vector
<SymbolDetails
>> Reply
) {
1477 Server
->symbolInfo(Params
.textDocument
.uri
.file(), Params
.position
,
1481 void ClangdLSPServer::onSelectionRange(
1482 const SelectionRangeParams
&Params
,
1483 Callback
<std::vector
<SelectionRange
>> Reply
) {
1484 Server
->semanticRanges(
1485 Params
.textDocument
.uri
.file(), Params
.positions
,
1486 [Reply
= std::move(Reply
)](
1487 llvm::Expected
<std::vector
<SelectionRange
>> Ranges
) mutable {
1489 return Reply(Ranges
.takeError());
1490 return Reply(std::move(*Ranges
));
1494 void ClangdLSPServer::onDocumentLink(
1495 const DocumentLinkParams
&Params
,
1496 Callback
<std::vector
<DocumentLink
>> Reply
) {
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 Params
.textDocument
.uri
.file(),
1504 [Reply
= std::move(Reply
)](
1505 llvm::Expected
<std::vector
<DocumentLink
>> Links
) mutable {
1507 return Reply(Links
.takeError());
1509 return Reply(std::move(Links
));
1513 // Increment a numeric string: "" -> 1 -> 2 -> ... -> 9 -> 10 -> 11 ...
1514 static void increment(std::string
&S
) {
1515 for (char &C
: llvm::reverse(S
)) {
1522 S
.insert(S
.begin(), '1');
1525 void ClangdLSPServer::onSemanticTokens(const SemanticTokensParams
&Params
,
1526 Callback
<SemanticTokens
> CB
) {
1527 auto File
= Params
.textDocument
.uri
.file();
1528 Server
->semanticHighlights(
1529 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 {
1533 return CB(HT
.takeError());
1534 SemanticTokens Result
;
1535 Result
.tokens
= toSemanticTokens(*HT
, *Code
);
1537 std::lock_guard
<std::mutex
> Lock(SemanticTokensMutex
);
1538 auto &Last
= LastSemanticTokens
[File
];
1540 Last
.tokens
= Result
.tokens
;
1541 increment(Last
.resultId
);
1542 Result
.resultId
= Last
.resultId
;
1544 CB(std::move(Result
));
1548 void ClangdLSPServer::onSemanticTokensDelta(
1549 const SemanticTokensDeltaParams
&Params
,
1550 Callback
<SemanticTokensOrDelta
> CB
) {
1551 auto File
= Params
.textDocument
.uri
.file();
1552 Server
->semanticHighlights(
1553 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 {
1558 return CB(HT
.takeError());
1559 std::vector
<SemanticToken
> Toks
= toSemanticTokens(*HT
, *Code
);
1561 SemanticTokensOrDelta Result
;
1563 std::lock_guard
<std::mutex
> Lock(SemanticTokensMutex
);
1564 auto &Last
= LastSemanticTokens
[File
];
1566 if (PrevResultID
== Last
.resultId
) {
1567 Result
.edits
= diffTokens(Last
.tokens
, Toks
);
1569 vlog("semanticTokens/full/delta: wanted edits vs {0} but last "
1570 "result had ID {1}. Returning full token list.",
1571 PrevResultID
, Last
.resultId
);
1572 Result
.tokens
= Toks
;
1575 Last
.tokens
= std::move(Toks
);
1576 increment(Last
.resultId
);
1577 Result
.resultId
= Last
.resultId
;
1580 CB(std::move(Result
));
1584 void ClangdLSPServer::onMemoryUsage(const NoParams
&,
1585 Callback
<MemoryTree
> Reply
) {
1586 llvm::BumpPtrAllocator DetailAlloc
;
1587 MemoryTree
MT(&DetailAlloc
);
1589 Reply(std::move(MT
));
1592 void ClangdLSPServer::onAST(const ASTParams
&Params
,
1593 Callback
<std::optional
<ASTNode
>> CB
) {
1594 Server
->getAST(Params
.textDocument
.uri
.file(), Params
.range
, std::move(CB
));
1597 ClangdLSPServer::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 Opts
.ConfigProvider
, this);
1613 LSPBinder
Bind(this->Handlers
, *this);
1614 Bind
.method("initialize", this, &ClangdLSPServer::onInitialize
);
1617 void ClangdLSPServer::bindMethods(LSPBinder
&Bind
,
1618 const ClientCapabilities
&Caps
) {
1620 Bind
.notification("initialized", this, &ClangdLSPServer::onInitialized
);
1621 Bind
.method("shutdown", this, &ClangdLSPServer::onShutdown
);
1622 Bind
.method("sync", this, &ClangdLSPServer::onSync
);
1623 Bind
.method("textDocument/rangeFormatting", this, &ClangdLSPServer::onDocumentRangeFormatting
);
1624 Bind
.method("textDocument/onTypeFormatting", this, &ClangdLSPServer::onDocumentOnTypeFormatting
);
1625 Bind
.method("textDocument/formatting", this, &ClangdLSPServer::onDocumentFormatting
);
1626 Bind
.method("textDocument/codeAction", this, &ClangdLSPServer::onCodeAction
);
1627 Bind
.method("textDocument/completion", this, &ClangdLSPServer::onCompletion
);
1628 Bind
.method("textDocument/signatureHelp", this, &ClangdLSPServer::onSignatureHelp
);
1629 Bind
.method("textDocument/definition", this, &ClangdLSPServer::onGoToDefinition
);
1630 Bind
.method("textDocument/declaration", this, &ClangdLSPServer::onGoToDeclaration
);
1631 Bind
.method("textDocument/typeDefinition", this, &ClangdLSPServer::onGoToType
);
1632 Bind
.method("textDocument/implementation", this, &ClangdLSPServer::onGoToImplementation
);
1633 Bind
.method("textDocument/references", this, &ClangdLSPServer::onReference
);
1634 Bind
.method("textDocument/switchSourceHeader", this, &ClangdLSPServer::onSwitchSourceHeader
);
1635 Bind
.method("textDocument/prepareRename", this, &ClangdLSPServer::onPrepareRename
);
1636 Bind
.method("textDocument/rename", this, &ClangdLSPServer::onRename
);
1637 Bind
.method("textDocument/hover", this, &ClangdLSPServer::onHover
);
1638 Bind
.method("textDocument/documentSymbol", this, &ClangdLSPServer::onDocumentSymbol
);
1639 Bind
.method("workspace/executeCommand", this, &ClangdLSPServer::onCommand
);
1640 Bind
.method("textDocument/documentHighlight", this, &ClangdLSPServer::onDocumentHighlight
);
1641 Bind
.method("workspace/symbol", this, &ClangdLSPServer::onWorkspaceSymbol
);
1642 Bind
.method("textDocument/ast", this, &ClangdLSPServer::onAST
);
1643 Bind
.notification("textDocument/didOpen", this, &ClangdLSPServer::onDocumentDidOpen
);
1644 Bind
.notification("textDocument/didClose", this, &ClangdLSPServer::onDocumentDidClose
);
1645 Bind
.notification("textDocument/didChange", this, &ClangdLSPServer::onDocumentDidChange
);
1646 Bind
.notification("textDocument/didSave", this, &ClangdLSPServer::onDocumentDidSave
);
1647 Bind
.notification("workspace/didChangeWatchedFiles", this, &ClangdLSPServer::onFileEvent
);
1648 Bind
.notification("workspace/didChangeConfiguration", this, &ClangdLSPServer::onChangeConfiguration
);
1649 Bind
.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo
);
1650 Bind
.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy
);
1651 Bind
.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy
);
1652 Bind
.method("textDocument/prepareTypeHierarchy", this, &ClangdLSPServer::onPrepareTypeHierarchy
);
1653 Bind
.method("typeHierarchy/supertypes", this, &ClangdLSPServer::onSuperTypes
);
1654 Bind
.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes
);
1655 Bind
.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy
);
1656 Bind
.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls
);
1657 Bind
.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange
);
1658 Bind
.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink
);
1659 Bind
.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens
);
1660 Bind
.method("textDocument/semanticTokens/full/delta", this, &ClangdLSPServer::onSemanticTokensDelta
);
1661 Bind
.method("clangd/inlayHints", this, &ClangdLSPServer::onClangdInlayHints
);
1662 Bind
.method("textDocument/inlayHint", this, &ClangdLSPServer::onInlayHint
);
1663 Bind
.method("$/memoryUsage", this, &ClangdLSPServer::onMemoryUsage
);
1664 Bind
.method("textDocument/foldingRange", this, &ClangdLSPServer::onFoldingRange
);
1665 Bind
.command(ApplyFixCommand
, this, &ClangdLSPServer::onCommandApplyEdit
);
1666 Bind
.command(ApplyTweakCommand
, this, &ClangdLSPServer::onCommandApplyTweak
);
1668 ApplyWorkspaceEdit
= Bind
.outgoingMethod("workspace/applyEdit");
1669 PublishDiagnostics
= Bind
.outgoingNotification("textDocument/publishDiagnostics");
1670 if (Caps
.InactiveRegions
)
1671 PublishInactiveRegions
= Bind
.outgoingNotification("textDocument/inactiveRegions");
1672 ShowMessage
= Bind
.outgoingNotification("window/showMessage");
1673 NotifyFileStatus
= Bind
.outgoingNotification("textDocument/clangd.fileStatus");
1674 CreateWorkDoneProgress
= Bind
.outgoingMethod("window/workDoneProgress/create");
1675 BeginWorkDoneProgress
= Bind
.outgoingNotification("$/progress");
1676 ReportWorkDoneProgress
= Bind
.outgoingNotification("$/progress");
1677 EndWorkDoneProgress
= Bind
.outgoingNotification("$/progress");
1678 if(Caps
.SemanticTokenRefreshSupport
)
1679 SemanticTokensRefresh
= Bind
.outgoingMethod("workspace/semanticTokens/refresh");
1683 ClangdLSPServer::~ClangdLSPServer() {
1684 IsBeingDestroyed
= true;
1685 // Explicitly destroy ClangdServer first, blocking on threads it owns.
1686 // This ensures they don't access any other members.
1690 bool ClangdLSPServer::run() {
1691 // Run the Language Server loop.
1692 bool CleanExit
= true;
1693 if (auto Err
= Transp
.loop(*MsgHandler
)) {
1694 elog("Transport error: {0}", std::move(Err
));
1698 return CleanExit
&& ShutdownRequestReceived
;
1701 void ClangdLSPServer::profile(MemoryTree
&MT
) const {
1703 Server
->profile(MT
.child("clangd_server"));
1706 std::optional
<ClangdServer::DiagRef
>
1707 ClangdLSPServer::getDiagRef(StringRef File
, const clangd::Diagnostic
&D
) {
1708 std::lock_guard
<std::mutex
> Lock(DiagRefMutex
);
1709 auto DiagToDiagRefIter
= DiagRefMap
.find(File
);
1710 if (DiagToDiagRefIter
== DiagRefMap
.end())
1711 return std::nullopt
;
1713 const auto &DiagToDiagRefMap
= DiagToDiagRefIter
->second
;
1714 auto FixItsIter
= DiagToDiagRefMap
.find(toDiagKey(D
));
1715 if (FixItsIter
== DiagToDiagRefMap
.end())
1716 return std::nullopt
;
1718 return FixItsIter
->second
;
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.
1727 bool ClangdLSPServer::shouldRunCompletion(
1728 const CompletionParams
&Params
) const {
1729 if (Params
.context
.triggerKind
!= CompletionTriggerKind::TriggerCharacter
)
1731 auto Code
= Server
->getDraft(Params
.textDocument
.uri
.file());
1733 return true; // completion code will log the error for untracked doc.
1734 auto Offset
= positionToOffset(*Code
, Params
.position
,
1735 /*AllowColumnsBeyondLineLength=*/false);
1737 vlog("could not convert position '{0}' to offset for file '{1}'",
1738 Params
.position
, Params
.textDocument
.uri
.file());
1741 return allowImplicitCompletion(*Code
, *Offset
);
1744 void ClangdLSPServer::onDiagnosticsReady(PathRef File
, llvm::StringRef Version
,
1745 llvm::ArrayRef
<Diag
> Diagnostics
) {
1746 PublishDiagnosticsParams Notification
;
1747 Notification
.version
= decodeVersion(Version
);
1748 Notification
.uri
= URIForFile::canonicalize(File
, /*TUPath=*/File
);
1749 DiagnosticToDiagRefMap LocalDiagMap
; // Temporary storage
1750 for (auto &Diag
: Diagnostics
) {
1751 toLSPDiags(Diag
, Notification
.uri
, DiagOpts
,
1752 [&](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(toCodeAction(
1757 Fix
, Notification
.uri
, Notification
.version
,
1758 SupportsDocumentChanges
, SupportsChangeAnnotation
));
1759 LSPDiag
.codeActions
.emplace(std::move(CodeActions
));
1760 if (LSPDiag
.codeActions
->size() == 1)
1761 LSPDiag
.codeActions
->front().isPreferred
= true;
1763 LocalDiagMap
[toDiagKey(LSPDiag
)] = {Diag
.Range
, Diag
.Message
};
1764 Notification
.diagnostics
.push_back(std::move(LSPDiag
));
1770 std::lock_guard
<std::mutex
> Lock(DiagRefMutex
);
1771 DiagRefMap
[File
] = LocalDiagMap
;
1774 // Send a notification to the LSP client.
1775 PublishDiagnostics(Notification
);
1778 void ClangdLSPServer::onInactiveRegionsReady(
1779 PathRef File
, std::vector
<Range
> InactiveRegions
) {
1780 InactiveRegionsParams Notification
;
1781 Notification
.TextDocument
= {URIForFile::canonicalize(File
, /*TUPath=*/File
)};
1782 Notification
.InactiveRegions
= std::move(InactiveRegions
);
1784 PublishInactiveRegions(Notification
);
1787 void ClangdLSPServer::onBackgroundIndexProgress(
1788 const BackgroundQueue::Stats
&Stats
) {
1789 static const char ProgressToken
[] = "backgroundIndexProgress";
1791 // The background index did some work, maybe we need to cleanup
1792 maybeCleanupMemory();
1794 std::lock_guard
<std::mutex
> Lock(BackgroundIndexProgressMutex
);
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({ProgressToken
, std::move(Begin
)});
1802 BackgroundIndexProgressState
= BackgroundIndexProgress::Live
;
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
);
1811 llvm::formatv("{0}/{1}", Stats
.Completed
- Stats
.LastIdle
,
1812 Stats
.Enqueued
- Stats
.LastIdle
);
1813 ReportWorkDoneProgress({ProgressToken
, std::move(Report
)});
1815 assert(Stats
.Completed
== Stats
.Enqueued
);
1816 EndWorkDoneProgress({ProgressToken
, WorkDoneProgressEnd()});
1817 BackgroundIndexProgressState
= BackgroundIndexProgress::Empty
;
1821 switch (BackgroundIndexProgressState
) {
1822 case BackgroundIndexProgress::Unsupported
:
1824 case BackgroundIndexProgress::Creating
:
1825 // Cache this update for when the progress bar is available.
1826 PendingBackgroundIndexProgress
= Stats
;
1828 case BackgroundIndexProgress::Empty
: {
1829 if (BackgroundIndexSkipCreate
) {
1830 NotifyProgress(Stats
);
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(
1840 [this, NotifyProgress
](llvm::Expected
<std::nullptr_t
> E
) {
1841 std::lock_guard
<std::mutex
> Lock(BackgroundIndexProgressMutex
);
1843 NotifyProgress(this->PendingBackgroundIndexProgress
);
1845 elog("Failed to create background index progress bar: {0}",
1847 // give up forever rather than thrashing about
1848 BackgroundIndexProgressState
= BackgroundIndexProgress::Unsupported
;
1853 case BackgroundIndexProgress::Live
:
1854 NotifyProgress(Stats
);
1859 void ClangdLSPServer::onFileUpdated(PathRef File
, const TUStatus
&Status
) {
1860 if (!SupportFileStatus
)
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
))
1870 NotifyFileStatus(Status
.render(File
));
1873 void ClangdLSPServer::onSemanticsMaybeChanged(PathRef File
) {
1874 if (SemanticTokensRefresh
) {
1875 SemanticTokensRefresh(NoParams
{}, [](llvm::Expected
<std::nullptr_t
> E
) {
1878 elog("Failed to refresh semantic tokens: {0}", E
.takeError());
1883 } // namespace clangd
1884 } // namespace clang