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