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