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 "Diagnostics.h" 11 #include "Protocol.h" 12 #include "SourceCode.h" 13 #include "Trace.h" 14 #include "URI.h" 15 #include "clang/Tooling/Core/Replacement.h" 16 #include "llvm/ADT/ScopeExit.h" 17 #include "llvm/Support/Errc.h" 18 #include "llvm/Support/Error.h" 19 #include "llvm/Support/FormatVariadic.h" 20 #include "llvm/Support/Path.h" 21 #include "llvm/Support/ScopedPrinter.h" 22 23 namespace clang { 24 namespace clangd { 25 namespace { 26 class IgnoreCompletionError : public llvm::ErrorInfo<CancelledError> { 27 public: 28 void log(llvm::raw_ostream &OS) const override { 29 OS << "ignored auto-triggered completion, preceding char did not match"; 30 } 31 std::error_code convertToErrorCode() const override { 32 return std::make_error_code(std::errc::operation_canceled); 33 } 34 }; 35 36 /// Transforms a tweak into a code action that would apply it if executed. 37 /// EXPECTS: T.prepare() was called and returned true. 38 CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File, 39 Range Selection) { 40 CodeAction CA; 41 CA.title = T.Title; 42 CA.kind = CodeAction::REFACTOR_KIND; 43 // This tweak may have an expensive second stage, we only run it if the user 44 // actually chooses it in the UI. We reply with a command that would run the 45 // corresponding tweak. 46 // FIXME: for some tweaks, computing the edits is cheap and we could send them 47 // directly. 48 CA.command.emplace(); 49 CA.command->title = T.Title; 50 CA.command->command = Command::CLANGD_APPLY_TWEAK; 51 CA.command->tweakArgs.emplace(); 52 CA.command->tweakArgs->file = File; 53 CA.command->tweakArgs->tweakID = T.ID; 54 CA.command->tweakArgs->selection = Selection; 55 return CA; 56 } 57 58 void adjustSymbolKinds(llvm::MutableArrayRef<DocumentSymbol> Syms, 59 SymbolKindBitset Kinds) { 60 for (auto &S : Syms) { 61 S.kind = adjustKindToCapability(S.kind, Kinds); 62 adjustSymbolKinds(S.children, Kinds); 63 } 64 } 65 66 SymbolKindBitset defaultSymbolKinds() { 67 SymbolKindBitset Defaults; 68 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array); 69 ++I) 70 Defaults.set(I); 71 return Defaults; 72 } 73 74 CompletionItemKindBitset defaultCompletionItemKinds() { 75 CompletionItemKindBitset Defaults; 76 for (size_t I = CompletionItemKindMin; 77 I <= static_cast<size_t>(CompletionItemKind::Reference); ++I) 78 Defaults.set(I); 79 return Defaults; 80 } 81 82 } // namespace 83 84 // MessageHandler dispatches incoming LSP messages. 85 // It handles cross-cutting concerns: 86 // - serializes/deserializes protocol objects to JSON 87 // - logging of inbound messages 88 // - cancellation handling 89 // - basic call tracing 90 // MessageHandler ensures that initialize() is called before any other handler. 91 class ClangdLSPServer::MessageHandler : public Transport::MessageHandler { 92 public: 93 MessageHandler(ClangdLSPServer &Server) : Server(Server) {} 94 95 bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override { 96 log("<-- {0}", Method); 97 if (Method == "exit") 98 return false; 99 if (!Server.Server) 100 elog("Notification {0} before initialization", Method); 101 else if (Method == "$/cancelRequest") 102 onCancel(std::move(Params)); 103 else if (auto Handler = Notifications.lookup(Method)) 104 Handler(std::move(Params)); 105 else 106 log("unhandled notification {0}", Method); 107 return true; 108 } 109 110 bool onCall(llvm::StringRef Method, llvm::json::Value Params, 111 llvm::json::Value ID) override { 112 // Calls can be canceled by the client. Add cancellation context. 113 WithContext WithCancel(cancelableRequestContext(ID)); 114 trace::Span Tracer(Method); 115 SPAN_ATTACH(Tracer, "Params", Params); 116 ReplyOnce Reply(ID, Method, &Server, Tracer.Args); 117 log("<-- {0}({1})", Method, ID); 118 if (!Server.Server && Method != "initialize") { 119 elog("Call {0} before initialization.", Method); 120 Reply(llvm::make_error<LSPError>("server not initialized", 121 ErrorCode::ServerNotInitialized)); 122 } else if (auto Handler = Calls.lookup(Method)) 123 Handler(std::move(Params), std::move(Reply)); 124 else 125 Reply(llvm::make_error<LSPError>("method not found", 126 ErrorCode::MethodNotFound)); 127 return true; 128 } 129 130 bool onReply(llvm::json::Value ID, 131 llvm::Expected<llvm::json::Value> Result) override { 132 // We ignore replies, just log them. 133 if (Result) 134 log("<-- reply({0})", ID); 135 else 136 log("<-- reply({0}) error: {1}", ID, llvm::toString(Result.takeError())); 137 return true; 138 } 139 140 // Bind an LSP method name to a call. 141 template <typename Param, typename Result> 142 void bind(const char *Method, 143 void (ClangdLSPServer::*Handler)(const Param &, Callback<Result>)) { 144 Calls[Method] = [Method, Handler, this](llvm::json::Value RawParams, 145 ReplyOnce Reply) { 146 Param P; 147 if (fromJSON(RawParams, P)) { 148 (Server.*Handler)(P, std::move(Reply)); 149 } else { 150 elog("Failed to decode {0} request.", Method); 151 Reply(llvm::make_error<LSPError>("failed to decode request", 152 ErrorCode::InvalidRequest)); 153 } 154 }; 155 } 156 157 // Bind an LSP method name to a notification. 158 template <typename Param> 159 void bind(const char *Method, 160 void (ClangdLSPServer::*Handler)(const Param &)) { 161 Notifications[Method] = [Method, Handler, 162 this](llvm::json::Value RawParams) { 163 Param P; 164 if (!fromJSON(RawParams, P)) { 165 elog("Failed to decode {0} request.", Method); 166 return; 167 } 168 trace::Span Tracer(Method); 169 SPAN_ATTACH(Tracer, "Params", RawParams); 170 (Server.*Handler)(P); 171 }; 172 } 173 174 private: 175 // Function object to reply to an LSP call. 176 // Each instance must be called exactly once, otherwise: 177 // - the bug is logged, and (in debug mode) an assert will fire 178 // - if there was no reply, an error reply is sent 179 // - if there were multiple replies, only the first is sent 180 class ReplyOnce { 181 std::atomic<bool> Replied = {false}; 182 std::chrono::steady_clock::time_point Start; 183 llvm::json::Value ID; 184 std::string Method; 185 ClangdLSPServer *Server; // Null when moved-from. 186 llvm::json::Object *TraceArgs; 187 188 public: 189 ReplyOnce(const llvm::json::Value &ID, llvm::StringRef Method, 190 ClangdLSPServer *Server, llvm::json::Object *TraceArgs) 191 : Start(std::chrono::steady_clock::now()), ID(ID), Method(Method), 192 Server(Server), TraceArgs(TraceArgs) { 193 assert(Server); 194 } 195 ReplyOnce(ReplyOnce &&Other) 196 : Replied(Other.Replied.load()), Start(Other.Start), 197 ID(std::move(Other.ID)), Method(std::move(Other.Method)), 198 Server(Other.Server), TraceArgs(Other.TraceArgs) { 199 Other.Server = nullptr; 200 } 201 ReplyOnce &operator=(ReplyOnce &&) = delete; 202 ReplyOnce(const ReplyOnce &) = delete; 203 ReplyOnce &operator=(const ReplyOnce &) = delete; 204 205 ~ReplyOnce() { 206 if (Server && !Replied) { 207 elog("No reply to message {0}({1})", Method, ID); 208 assert(false && "must reply to all calls!"); 209 (*this)(llvm::make_error<LSPError>("server failed to reply", 210 ErrorCode::InternalError)); 211 } 212 } 213 214 void operator()(llvm::Expected<llvm::json::Value> Reply) { 215 assert(Server && "moved-from!"); 216 if (Replied.exchange(true)) { 217 elog("Replied twice to message {0}({1})", Method, ID); 218 assert(false && "must reply to each call only once!"); 219 return; 220 } 221 auto Duration = std::chrono::steady_clock::now() - Start; 222 if (Reply) { 223 log("--> reply:{0}({1}) {2:ms}", Method, ID, Duration); 224 if (TraceArgs) 225 (*TraceArgs)["Reply"] = *Reply; 226 std::lock_guard<std::mutex> Lock(Server->TranspWriter); 227 Server->Transp.reply(std::move(ID), std::move(Reply)); 228 } else { 229 llvm::Error Err = Reply.takeError(); 230 log("--> reply:{0}({1}) {2:ms}, error: {3}", Method, ID, Duration, Err); 231 if (TraceArgs) 232 (*TraceArgs)["Error"] = llvm::to_string(Err); 233 std::lock_guard<std::mutex> Lock(Server->TranspWriter); 234 Server->Transp.reply(std::move(ID), std::move(Err)); 235 } 236 } 237 }; 238 239 llvm::StringMap<std::function<void(llvm::json::Value)>> Notifications; 240 llvm::StringMap<std::function<void(llvm::json::Value, ReplyOnce)>> Calls; 241 242 // Method calls may be cancelled by ID, so keep track of their state. 243 // This needs a mutex: handlers may finish on a different thread, and that's 244 // when we clean up entries in the map. 245 mutable std::mutex RequestCancelersMutex; 246 llvm::StringMap<std::pair<Canceler, /*Cookie*/ unsigned>> RequestCancelers; 247 unsigned NextRequestCookie = 0; // To disambiguate reused IDs, see below. 248 void onCancel(const llvm::json::Value &Params) { 249 const llvm::json::Value *ID = nullptr; 250 if (auto *O = Params.getAsObject()) 251 ID = O->get("id"); 252 if (!ID) { 253 elog("Bad cancellation request: {0}", Params); 254 return; 255 } 256 auto StrID = llvm::to_string(*ID); 257 std::lock_guard<std::mutex> Lock(RequestCancelersMutex); 258 auto It = RequestCancelers.find(StrID); 259 if (It != RequestCancelers.end()) 260 It->second.first(); // Invoke the canceler. 261 } 262 // We run cancelable requests in a context that does two things: 263 // - allows cancellation using RequestCancelers[ID] 264 // - cleans up the entry in RequestCancelers when it's no longer needed 265 // If a client reuses an ID, the last wins and the first cannot be canceled. 266 Context cancelableRequestContext(const llvm::json::Value &ID) { 267 auto Task = cancelableTask(); 268 auto StrID = llvm::to_string(ID); // JSON-serialize ID for map key. 269 auto Cookie = NextRequestCookie++; // No lock, only called on main thread. 270 { 271 std::lock_guard<std::mutex> Lock(RequestCancelersMutex); 272 RequestCancelers[StrID] = {std::move(Task.second), Cookie}; 273 } 274 // When the request ends, we can clean up the entry we just added. 275 // The cookie lets us check that it hasn't been overwritten due to ID 276 // reuse. 277 return Task.first.derive(llvm::make_scope_exit([this, StrID, Cookie] { 278 std::lock_guard<std::mutex> Lock(RequestCancelersMutex); 279 auto It = RequestCancelers.find(StrID); 280 if (It != RequestCancelers.end() && It->second.second == Cookie) 281 RequestCancelers.erase(It); 282 })); 283 } 284 285 ClangdLSPServer &Server; 286 }; 287 288 // call(), notify(), and reply() wrap the Transport, adding logging and locking. 289 void ClangdLSPServer::call(llvm::StringRef Method, llvm::json::Value Params) { 290 auto ID = NextCallID++; 291 log("--> {0}({1})", Method, ID); 292 // We currently don't handle responses, so no need to store ID anywhere. 293 std::lock_guard<std::mutex> Lock(TranspWriter); 294 Transp.call(Method, std::move(Params), ID); 295 } 296 297 void ClangdLSPServer::notify(llvm::StringRef Method, llvm::json::Value Params) { 298 log("--> {0}", Method); 299 std::lock_guard<std::mutex> Lock(TranspWriter); 300 Transp.notify(Method, std::move(Params)); 301 } 302 303 void ClangdLSPServer::onInitialize(const InitializeParams &Params, 304 Callback<llvm::json::Value> Reply) { 305 if (Params.rootUri && *Params.rootUri) 306 ClangdServerOpts.WorkspaceRoot = Params.rootUri->file(); 307 else if (Params.rootPath && !Params.rootPath->empty()) 308 ClangdServerOpts.WorkspaceRoot = *Params.rootPath; 309 if (Server) 310 return Reply(llvm::make_error<LSPError>("server already initialized", 311 ErrorCode::InvalidRequest)); 312 if (const auto &Dir = Params.initializationOptions.compilationDatabasePath) 313 CompileCommandsDir = Dir; 314 if (UseDirBasedCDB) 315 BaseCDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>( 316 CompileCommandsDir); 317 CDB.emplace(BaseCDB.get(), Params.initializationOptions.fallbackFlags, 318 ClangdServerOpts.ResourceDir); 319 Server.emplace(*CDB, FSProvider, static_cast<DiagnosticsConsumer &>(*this), 320 ClangdServerOpts); 321 applyConfiguration(Params.initializationOptions.ConfigSettings); 322 323 CCOpts.EnableSnippets = Params.capabilities.CompletionSnippets; 324 DiagOpts.EmbedFixesInDiagnostics = Params.capabilities.DiagnosticFixes; 325 DiagOpts.SendDiagnosticCategory = Params.capabilities.DiagnosticCategory; 326 if (Params.capabilities.WorkspaceSymbolKinds) 327 SupportedSymbolKinds |= *Params.capabilities.WorkspaceSymbolKinds; 328 if (Params.capabilities.CompletionItemKinds) 329 SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds; 330 SupportsCodeAction = Params.capabilities.CodeActionStructure; 331 SupportsHierarchicalDocumentSymbol = 332 Params.capabilities.HierarchicalDocumentSymbol; 333 SupportFileStatus = Params.initializationOptions.FileStatus; 334 Reply(llvm::json::Object{ 335 {{"capabilities", 336 llvm::json::Object{ 337 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental}, 338 {"documentFormattingProvider", true}, 339 {"documentRangeFormattingProvider", true}, 340 {"documentOnTypeFormattingProvider", 341 llvm::json::Object{ 342 {"firstTriggerCharacter", "}"}, 343 {"moreTriggerCharacter", {}}, 344 }}, 345 {"codeActionProvider", true}, 346 {"completionProvider", 347 llvm::json::Object{ 348 {"resolveProvider", false}, 349 // We do extra checks for '>' and ':' in completion to only 350 // trigger on '->' and '::'. 351 {"triggerCharacters", {".", ">", ":"}}, 352 }}, 353 {"signatureHelpProvider", 354 llvm::json::Object{ 355 {"triggerCharacters", {"(", ","}}, 356 }}, 357 {"declarationProvider", true}, 358 {"definitionProvider", true}, 359 {"documentHighlightProvider", true}, 360 {"hoverProvider", true}, 361 {"renameProvider", true}, 362 {"documentSymbolProvider", true}, 363 {"workspaceSymbolProvider", true}, 364 {"referencesProvider", true}, 365 {"executeCommandProvider", 366 llvm::json::Object{ 367 {"commands", 368 {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, 369 ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, 370 }}, 371 {"typeHierarchyProvider", true}, 372 }}}}); 373 } 374 375 void ClangdLSPServer::onShutdown(const ShutdownParams &Params, 376 Callback<std::nullptr_t> Reply) { 377 // Do essentially nothing, just say we're ready to exit. 378 ShutdownRequestReceived = true; 379 Reply(nullptr); 380 } 381 382 // sync is a clangd extension: it blocks until all background work completes. 383 // It blocks the calling thread, so no messages are processed until it returns! 384 void ClangdLSPServer::onSync(const NoParams &Params, 385 Callback<std::nullptr_t> Reply) { 386 if (Server->blockUntilIdleForTest(/*TimeoutSeconds=*/60)) 387 Reply(nullptr); 388 else 389 Reply(llvm::createStringError(llvm::inconvertibleErrorCode(), 390 "Not idle after a minute")); 391 } 392 393 void ClangdLSPServer::onDocumentDidOpen( 394 const DidOpenTextDocumentParams &Params) { 395 PathRef File = Params.textDocument.uri.file(); 396 397 const std::string &Contents = Params.textDocument.text; 398 399 DraftMgr.addDraft(File, Contents); 400 Server->addDocument(File, Contents, WantDiagnostics::Yes); 401 } 402 403 void ClangdLSPServer::onDocumentDidChange( 404 const DidChangeTextDocumentParams &Params) { 405 auto WantDiags = WantDiagnostics::Auto; 406 if (Params.wantDiagnostics.hasValue()) 407 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes 408 : WantDiagnostics::No; 409 410 PathRef File = Params.textDocument.uri.file(); 411 llvm::Expected<std::string> Contents = 412 DraftMgr.updateDraft(File, Params.contentChanges); 413 if (!Contents) { 414 // If this fails, we are most likely going to be not in sync anymore with 415 // the client. It is better to remove the draft and let further operations 416 // fail rather than giving wrong results. 417 DraftMgr.removeDraft(File); 418 Server->removeDocument(File); 419 elog("Failed to update {0}: {1}", File, Contents.takeError()); 420 return; 421 } 422 423 Server->addDocument(File, *Contents, WantDiags); 424 } 425 426 void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { 427 Server->onFileEvent(Params); 428 } 429 430 void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params, 431 Callback<llvm::json::Value> Reply) { 432 auto ApplyEdit = [this](WorkspaceEdit WE) { 433 ApplyWorkspaceEditParams Edit; 434 Edit.edit = std::move(WE); 435 // Ideally, we would wait for the response and if there is no error, we 436 // would reply success/failure to the original RPC. 437 call("workspace/applyEdit", Edit); 438 }; 439 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && 440 Params.workspaceEdit) { 441 // The flow for "apply-fix" : 442 // 1. We publish a diagnostic, including fixits 443 // 2. The user clicks on the diagnostic, the editor asks us for code actions 444 // 3. We send code actions, with the fixit embedded as context 445 // 4. The user selects the fixit, the editor asks us to apply it 446 // 5. We unwrap the changes and send them back to the editor 447 // 6. The editor applies the changes (applyEdit), and sends us a reply (but 448 // we ignore it) 449 450 Reply("Fix applied."); 451 ApplyEdit(*Params.workspaceEdit); 452 } else if (Params.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK && 453 Params.tweakArgs) { 454 auto Code = DraftMgr.getDraft(Params.tweakArgs->file.file()); 455 if (!Code) 456 return Reply(llvm::createStringError( 457 llvm::inconvertibleErrorCode(), 458 "trying to apply a code action for a non-added file")); 459 460 auto Action = [ApplyEdit](decltype(Reply) Reply, URIForFile File, 461 std::string Code, 462 llvm::Expected<tooling::Replacements> R) { 463 if (!R) 464 return Reply(R.takeError()); 465 466 WorkspaceEdit WE; 467 WE.changes.emplace(); 468 (*WE.changes)[File.uri()] = replacementsToEdits(Code, *R); 469 470 Reply("Fix applied."); 471 ApplyEdit(std::move(WE)); 472 }; 473 Server->applyTweak(Params.tweakArgs->file.file(), 474 Params.tweakArgs->selection, Params.tweakArgs->tweakID, 475 Bind(Action, std::move(Reply), Params.tweakArgs->file, 476 std::move(*Code))); 477 } else { 478 // We should not get here because ExecuteCommandParams would not have 479 // parsed in the first place and this handler should not be called. But if 480 // more commands are added, this will be here has a safe guard. 481 Reply(llvm::make_error<LSPError>( 482 llvm::formatv("Unsupported command \"{0}\".", Params.command).str(), 483 ErrorCode::InvalidParams)); 484 } 485 } 486 487 void ClangdLSPServer::onWorkspaceSymbol( 488 const WorkspaceSymbolParams &Params, 489 Callback<std::vector<SymbolInformation>> Reply) { 490 Server->workspaceSymbols( 491 Params.query, CCOpts.Limit, 492 Bind( 493 [this](decltype(Reply) Reply, 494 llvm::Expected<std::vector<SymbolInformation>> Items) { 495 if (!Items) 496 return Reply(Items.takeError()); 497 for (auto &Sym : *Items) 498 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); 499 500 Reply(std::move(*Items)); 501 }, 502 std::move(Reply))); 503 } 504 505 void ClangdLSPServer::onRename(const RenameParams &Params, 506 Callback<WorkspaceEdit> Reply) { 507 Path File = Params.textDocument.uri.file(); 508 llvm::Optional<std::string> Code = DraftMgr.getDraft(File); 509 if (!Code) 510 return Reply(llvm::make_error<LSPError>( 511 "onRename called for non-added file", ErrorCode::InvalidParams)); 512 513 Server->rename( 514 File, Params.position, Params.newName, 515 Bind( 516 [File, Code, Params]( 517 decltype(Reply) Reply, 518 llvm::Expected<std::vector<tooling::Replacement>> Replacements) { 519 if (!Replacements) 520 return Reply(Replacements.takeError()); 521 522 // Turn the replacements into the format specified by the Language 523 // Server Protocol. Fuse them into one big JSON array. 524 std::vector<TextEdit> Edits; 525 for (const auto &R : *Replacements) 526 Edits.push_back(replacementToEdit(*Code, R)); 527 WorkspaceEdit WE; 528 WE.changes = {{Params.textDocument.uri.uri(), Edits}}; 529 Reply(WE); 530 }, 531 std::move(Reply))); 532 } 533 534 void ClangdLSPServer::onDocumentDidClose( 535 const DidCloseTextDocumentParams &Params) { 536 PathRef File = Params.textDocument.uri.file(); 537 DraftMgr.removeDraft(File); 538 Server->removeDocument(File); 539 540 { 541 std::lock_guard<std::mutex> Lock(FixItsMutex); 542 FixItsMap.erase(File); 543 } 544 // clangd will not send updates for this file anymore, so we empty out the 545 // list of diagnostics shown on the client (e.g. in the "Problems" pane of 546 // VSCode). Note that this cannot race with actual diagnostics responses 547 // because removeDocument() guarantees no diagnostic callbacks will be 548 // executed after it returns. 549 publishDiagnostics(URIForFile::canonicalize(File, /*TUPath=*/File), {}); 550 } 551 552 void ClangdLSPServer::onDocumentOnTypeFormatting( 553 const DocumentOnTypeFormattingParams &Params, 554 Callback<std::vector<TextEdit>> Reply) { 555 auto File = Params.textDocument.uri.file(); 556 auto Code = DraftMgr.getDraft(File); 557 if (!Code) 558 return Reply(llvm::make_error<LSPError>( 559 "onDocumentOnTypeFormatting called for non-added file", 560 ErrorCode::InvalidParams)); 561 562 auto ReplacementsOrError = Server->formatOnType(*Code, File, Params.position); 563 if (ReplacementsOrError) 564 Reply(replacementsToEdits(*Code, ReplacementsOrError.get())); 565 else 566 Reply(ReplacementsOrError.takeError()); 567 } 568 569 void ClangdLSPServer::onDocumentRangeFormatting( 570 const DocumentRangeFormattingParams &Params, 571 Callback<std::vector<TextEdit>> Reply) { 572 auto File = Params.textDocument.uri.file(); 573 auto Code = DraftMgr.getDraft(File); 574 if (!Code) 575 return Reply(llvm::make_error<LSPError>( 576 "onDocumentRangeFormatting called for non-added file", 577 ErrorCode::InvalidParams)); 578 579 auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range); 580 if (ReplacementsOrError) 581 Reply(replacementsToEdits(*Code, ReplacementsOrError.get())); 582 else 583 Reply(ReplacementsOrError.takeError()); 584 } 585 586 void ClangdLSPServer::onDocumentFormatting( 587 const DocumentFormattingParams &Params, 588 Callback<std::vector<TextEdit>> Reply) { 589 auto File = Params.textDocument.uri.file(); 590 auto Code = DraftMgr.getDraft(File); 591 if (!Code) 592 return Reply(llvm::make_error<LSPError>( 593 "onDocumentFormatting called for non-added file", 594 ErrorCode::InvalidParams)); 595 596 auto ReplacementsOrError = Server->formatFile(*Code, File); 597 if (ReplacementsOrError) 598 Reply(replacementsToEdits(*Code, ReplacementsOrError.get())); 599 else 600 Reply(ReplacementsOrError.takeError()); 601 } 602 603 /// The functions constructs a flattened view of the DocumentSymbol hierarchy. 604 /// Used by the clients that do not support the hierarchical view. 605 static std::vector<SymbolInformation> 606 flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols, 607 const URIForFile &FileURI) { 608 609 std::vector<SymbolInformation> Results; 610 std::function<void(const DocumentSymbol &, llvm::StringRef)> Process = 611 [&](const DocumentSymbol &S, llvm::Optional<llvm::StringRef> ParentName) { 612 SymbolInformation SI; 613 SI.containerName = ParentName ? "" : *ParentName; 614 SI.name = S.name; 615 SI.kind = S.kind; 616 SI.location.range = S.range; 617 SI.location.uri = FileURI; 618 619 Results.push_back(std::move(SI)); 620 std::string FullName = 621 !ParentName ? S.name : (ParentName->str() + "::" + S.name); 622 for (auto &C : S.children) 623 Process(C, /*ParentName=*/FullName); 624 }; 625 for (auto &S : Symbols) 626 Process(S, /*ParentName=*/""); 627 return Results; 628 } 629 630 void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params, 631 Callback<llvm::json::Value> Reply) { 632 URIForFile FileURI = Params.textDocument.uri; 633 Server->documentSymbols( 634 Params.textDocument.uri.file(), 635 Bind( 636 [this, FileURI](decltype(Reply) Reply, 637 llvm::Expected<std::vector<DocumentSymbol>> Items) { 638 if (!Items) 639 return Reply(Items.takeError()); 640 adjustSymbolKinds(*Items, SupportedSymbolKinds); 641 if (SupportsHierarchicalDocumentSymbol) 642 return Reply(std::move(*Items)); 643 else 644 return Reply(flattenSymbolHierarchy(*Items, FileURI)); 645 }, 646 std::move(Reply))); 647 } 648 649 static llvm::Optional<Command> asCommand(const CodeAction &Action) { 650 Command Cmd; 651 if (Action.command && Action.edit) 652 return None; // Not representable. (We never emit these anyway). 653 if (Action.command) { 654 Cmd = *Action.command; 655 } else if (Action.edit) { 656 Cmd.command = Command::CLANGD_APPLY_FIX_COMMAND; 657 Cmd.workspaceEdit = *Action.edit; 658 } else { 659 return None; 660 } 661 Cmd.title = Action.title; 662 if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND) 663 Cmd.title = "Apply fix: " + Cmd.title; 664 return Cmd; 665 } 666 667 void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, 668 Callback<llvm::json::Value> Reply) { 669 URIForFile File = Params.textDocument.uri; 670 auto Code = DraftMgr.getDraft(File.file()); 671 if (!Code) 672 return Reply(llvm::make_error<LSPError>( 673 "onCodeAction called for non-added file", ErrorCode::InvalidParams)); 674 // We provide a code action for Fixes on the specified diagnostics. 675 std::vector<CodeAction> FixIts; 676 for (const Diagnostic &D : Params.context.diagnostics) { 677 for (auto &F : getFixes(File.file(), D)) { 678 FixIts.push_back(toCodeAction(F, Params.textDocument.uri)); 679 FixIts.back().diagnostics = {D}; 680 } 681 } 682 683 // Now enumerate the semantic code actions. 684 auto ConsumeActions = 685 [this](decltype(Reply) Reply, URIForFile File, std::string Code, 686 Range Selection, std::vector<CodeAction> FixIts, 687 llvm::Expected<std::vector<ClangdServer::TweakRef>> Tweaks) { 688 if (!Tweaks) 689 return Reply(Tweaks.takeError()); 690 691 std::vector<CodeAction> Actions = std::move(FixIts); 692 Actions.reserve(Actions.size() + Tweaks->size()); 693 for (const auto &T : *Tweaks) 694 Actions.push_back(toCodeAction(T, File, Selection)); 695 696 if (SupportsCodeAction) 697 return Reply(llvm::json::Array(Actions)); 698 std::vector<Command> Commands; 699 for (const auto &Action : Actions) { 700 if (auto Command = asCommand(Action)) 701 Commands.push_back(std::move(*Command)); 702 } 703 return Reply(llvm::json::Array(Commands)); 704 }; 705 706 Server->enumerateTweaks(File.file(), Params.range, 707 Bind(ConsumeActions, std::move(Reply), File, 708 std::move(*Code), Params.range, 709 std::move(FixIts))); 710 } 711 712 void ClangdLSPServer::onCompletion(const CompletionParams &Params, 713 Callback<CompletionList> Reply) { 714 if (!shouldRunCompletion(Params)) 715 return Reply(llvm::make_error<IgnoreCompletionError>()); 716 Server->codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts, 717 Bind( 718 [this](decltype(Reply) Reply, 719 llvm::Expected<CodeCompleteResult> List) { 720 if (!List) 721 return Reply(List.takeError()); 722 CompletionList LSPList; 723 LSPList.isIncomplete = List->HasMore; 724 for (const auto &R : List->Completions) { 725 CompletionItem C = R.render(CCOpts); 726 C.kind = adjustKindToCapability( 727 C.kind, SupportedCompletionItemKinds); 728 LSPList.items.push_back(std::move(C)); 729 } 730 return Reply(std::move(LSPList)); 731 }, 732 std::move(Reply))); 733 } 734 735 void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams &Params, 736 Callback<SignatureHelp> Reply) { 737 Server->signatureHelp(Params.textDocument.uri.file(), Params.position, 738 std::move(Reply)); 739 } 740 741 // Go to definition has a toggle function: if def and decl are distinct, then 742 // the first press gives you the def, the second gives you the matching def. 743 // getToggle() returns the counterpart location that under the cursor. 744 // 745 // We return the toggled location alone (ignoring other symbols) to encourage 746 // editors to "bounce" quickly between locations, without showing a menu. 747 static Location *getToggle(const TextDocumentPositionParams &Point, 748 LocatedSymbol &Sym) { 749 // Toggle only makes sense with two distinct locations. 750 if (!Sym.Definition || *Sym.Definition == Sym.PreferredDeclaration) 751 return nullptr; 752 if (Sym.Definition->uri.file() == Point.textDocument.uri.file() && 753 Sym.Definition->range.contains(Point.position)) 754 return &Sym.PreferredDeclaration; 755 if (Sym.PreferredDeclaration.uri.file() == Point.textDocument.uri.file() && 756 Sym.PreferredDeclaration.range.contains(Point.position)) 757 return &*Sym.Definition; 758 return nullptr; 759 } 760 761 void ClangdLSPServer::onGoToDefinition(const TextDocumentPositionParams &Params, 762 Callback<std::vector<Location>> Reply) { 763 Server->locateSymbolAt( 764 Params.textDocument.uri.file(), Params.position, 765 Bind( 766 [&, Params](decltype(Reply) Reply, 767 llvm::Expected<std::vector<LocatedSymbol>> Symbols) { 768 if (!Symbols) 769 return Reply(Symbols.takeError()); 770 std::vector<Location> Defs; 771 for (auto &S : *Symbols) { 772 if (Location *Toggle = getToggle(Params, S)) 773 return Reply(std::vector<Location>{std::move(*Toggle)}); 774 Defs.push_back(S.Definition.getValueOr(S.PreferredDeclaration)); 775 } 776 Reply(std::move(Defs)); 777 }, 778 std::move(Reply))); 779 } 780 781 void ClangdLSPServer::onGoToDeclaration( 782 const TextDocumentPositionParams &Params, 783 Callback<std::vector<Location>> Reply) { 784 Server->locateSymbolAt( 785 Params.textDocument.uri.file(), Params.position, 786 Bind( 787 [&, Params](decltype(Reply) Reply, 788 llvm::Expected<std::vector<LocatedSymbol>> Symbols) { 789 if (!Symbols) 790 return Reply(Symbols.takeError()); 791 std::vector<Location> Decls; 792 for (auto &S : *Symbols) { 793 if (Location *Toggle = getToggle(Params, S)) 794 return Reply(std::vector<Location>{std::move(*Toggle)}); 795 Decls.push_back(std::move(S.PreferredDeclaration)); 796 } 797 Reply(std::move(Decls)); 798 }, 799 std::move(Reply))); 800 } 801 802 void ClangdLSPServer::onSwitchSourceHeader(const TextDocumentIdentifier &Params, 803 Callback<std::string> Reply) { 804 llvm::Optional<Path> Result = Server->switchSourceHeader(Params.uri.file()); 805 Reply(Result ? URI::createFile(*Result).toString() : ""); 806 } 807 808 void ClangdLSPServer::onDocumentHighlight( 809 const TextDocumentPositionParams &Params, 810 Callback<std::vector<DocumentHighlight>> Reply) { 811 Server->findDocumentHighlights(Params.textDocument.uri.file(), 812 Params.position, std::move(Reply)); 813 } 814 815 void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params, 816 Callback<llvm::Optional<Hover>> Reply) { 817 Server->findHover(Params.textDocument.uri.file(), Params.position, 818 std::move(Reply)); 819 } 820 821 void ClangdLSPServer::onTypeHierarchy( 822 const TypeHierarchyParams &Params, 823 Callback<Optional<TypeHierarchyItem>> Reply) { 824 Server->typeHierarchy(Params.textDocument.uri.file(), Params.position, 825 Params.resolve, Params.direction, std::move(Reply)); 826 } 827 828 void ClangdLSPServer::applyConfiguration( 829 const ConfigurationSettings &Settings) { 830 // Per-file update to the compilation database. 831 bool ShouldReparseOpenFiles = false; 832 for (auto &Entry : Settings.compilationDatabaseChanges) { 833 /// The opened files need to be reparsed only when some existing 834 /// entries are changed. 835 PathRef File = Entry.first; 836 auto Old = CDB->getCompileCommand(File); 837 auto New = 838 tooling::CompileCommand(std::move(Entry.second.workingDirectory), File, 839 std::move(Entry.second.compilationCommand), 840 /*Output=*/""); 841 if (Old != New) { 842 CDB->setCompileCommand(File, std::move(New)); 843 ShouldReparseOpenFiles = true; 844 } 845 } 846 if (ShouldReparseOpenFiles) 847 reparseOpenedFiles(); 848 } 849 850 void ClangdLSPServer::publishDiagnostics( 851 const URIForFile &File, std::vector<clangd::Diagnostic> Diagnostics) { 852 // Publish diagnostics. 853 notify("textDocument/publishDiagnostics", 854 llvm::json::Object{ 855 {"uri", File}, 856 {"diagnostics", std::move(Diagnostics)}, 857 }); 858 } 859 860 // FIXME: This function needs to be properly tested. 861 void ClangdLSPServer::onChangeConfiguration( 862 const DidChangeConfigurationParams &Params) { 863 applyConfiguration(Params.settings); 864 } 865 866 void ClangdLSPServer::onReference(const ReferenceParams &Params, 867 Callback<std::vector<Location>> Reply) { 868 Server->findReferences(Params.textDocument.uri.file(), Params.position, 869 CCOpts.Limit, std::move(Reply)); 870 } 871 872 void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params, 873 Callback<std::vector<SymbolDetails>> Reply) { 874 Server->symbolInfo(Params.textDocument.uri.file(), Params.position, 875 std::move(Reply)); 876 } 877 878 ClangdLSPServer::ClangdLSPServer(class Transport &Transp, 879 const FileSystemProvider &FSProvider, 880 const clangd::CodeCompleteOptions &CCOpts, 881 llvm::Optional<Path> CompileCommandsDir, 882 bool UseDirBasedCDB, 883 const ClangdServer::Options &Opts) 884 : Transp(Transp), MsgHandler(new MessageHandler(*this)), 885 FSProvider(FSProvider), CCOpts(CCOpts), 886 SupportedSymbolKinds(defaultSymbolKinds()), 887 SupportedCompletionItemKinds(defaultCompletionItemKinds()), 888 UseDirBasedCDB(UseDirBasedCDB), 889 CompileCommandsDir(std::move(CompileCommandsDir)), 890 ClangdServerOpts(Opts) { 891 // clang-format off 892 MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize); 893 MsgHandler->bind("shutdown", &ClangdLSPServer::onShutdown); 894 MsgHandler->bind("sync", &ClangdLSPServer::onSync); 895 MsgHandler->bind("textDocument/rangeFormatting", &ClangdLSPServer::onDocumentRangeFormatting); 896 MsgHandler->bind("textDocument/onTypeFormatting", &ClangdLSPServer::onDocumentOnTypeFormatting); 897 MsgHandler->bind("textDocument/formatting", &ClangdLSPServer::onDocumentFormatting); 898 MsgHandler->bind("textDocument/codeAction", &ClangdLSPServer::onCodeAction); 899 MsgHandler->bind("textDocument/completion", &ClangdLSPServer::onCompletion); 900 MsgHandler->bind("textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp); 901 MsgHandler->bind("textDocument/definition", &ClangdLSPServer::onGoToDefinition); 902 MsgHandler->bind("textDocument/declaration", &ClangdLSPServer::onGoToDeclaration); 903 MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference); 904 MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader); 905 MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename); 906 MsgHandler->bind("textDocument/hover", &ClangdLSPServer::onHover); 907 MsgHandler->bind("textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol); 908 MsgHandler->bind("workspace/executeCommand", &ClangdLSPServer::onCommand); 909 MsgHandler->bind("textDocument/documentHighlight", &ClangdLSPServer::onDocumentHighlight); 910 MsgHandler->bind("workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol); 911 MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen); 912 MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose); 913 MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange); 914 MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); 915 MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); 916 MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); 917 MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); 918 // clang-format on 919 } 920 921 ClangdLSPServer::~ClangdLSPServer() = default; 922 923 bool ClangdLSPServer::run() { 924 // Run the Language Server loop. 925 bool CleanExit = true; 926 if (auto Err = Transp.loop(*MsgHandler)) { 927 elog("Transport error: {0}", std::move(Err)); 928 CleanExit = false; 929 } 930 931 // Destroy ClangdServer to ensure all worker threads finish. 932 Server.reset(); 933 return CleanExit && ShutdownRequestReceived; 934 } 935 936 std::vector<Fix> ClangdLSPServer::getFixes(llvm::StringRef File, 937 const clangd::Diagnostic &D) { 938 std::lock_guard<std::mutex> Lock(FixItsMutex); 939 auto DiagToFixItsIter = FixItsMap.find(File); 940 if (DiagToFixItsIter == FixItsMap.end()) 941 return {}; 942 943 const auto &DiagToFixItsMap = DiagToFixItsIter->second; 944 auto FixItsIter = DiagToFixItsMap.find(D); 945 if (FixItsIter == DiagToFixItsMap.end()) 946 return {}; 947 948 return FixItsIter->second; 949 } 950 951 bool ClangdLSPServer::shouldRunCompletion( 952 const CompletionParams &Params) const { 953 llvm::StringRef Trigger = Params.context.triggerCharacter; 954 if (Params.context.triggerKind != CompletionTriggerKind::TriggerCharacter || 955 (Trigger != ">" && Trigger != ":")) 956 return true; 957 958 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); 959 if (!Code) 960 return true; // completion code will log the error for untracked doc. 961 962 // A completion request is sent when the user types '>' or ':', but we only 963 // want to trigger on '->' and '::'. We check the preceeding character to make 964 // sure it matches what we expected. 965 // Running the lexer here would be more robust (e.g. we can detect comments 966 // and avoid triggering completion there), but we choose to err on the side 967 // of simplicity here. 968 auto Offset = positionToOffset(*Code, Params.position, 969 /*AllowColumnsBeyondLineLength=*/false); 970 if (!Offset) { 971 vlog("could not convert position '{0}' to offset for file '{1}'", 972 Params.position, Params.textDocument.uri.file()); 973 return true; 974 } 975 if (*Offset < 2) 976 return false; 977 978 if (Trigger == ">") 979 return (*Code)[*Offset - 2] == '-'; // trigger only on '->'. 980 if (Trigger == ":") 981 return (*Code)[*Offset - 2] == ':'; // trigger only on '::'. 982 assert(false && "unhandled trigger character"); 983 return true; 984 } 985 986 void ClangdLSPServer::onDiagnosticsReady(PathRef File, 987 std::vector<Diag> Diagnostics) { 988 auto URI = URIForFile::canonicalize(File, /*TUPath=*/File); 989 std::vector<Diagnostic> LSPDiagnostics; 990 DiagnosticToReplacementMap LocalFixIts; // Temporary storage 991 for (auto &Diag : Diagnostics) { 992 toLSPDiags(Diag, URI, DiagOpts, 993 [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) { 994 auto &FixItsForDiagnostic = LocalFixIts[Diag]; 995 llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic)); 996 LSPDiagnostics.push_back(std::move(Diag)); 997 }); 998 } 999 1000 // Cache FixIts 1001 { 1002 std::lock_guard<std::mutex> Lock(FixItsMutex); 1003 FixItsMap[File] = LocalFixIts; 1004 } 1005 1006 // Send a notification to the LSP client. 1007 publishDiagnostics(URI, std::move(LSPDiagnostics)); 1008 } 1009 1010 void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) { 1011 if (!SupportFileStatus) 1012 return; 1013 // FIXME: we don't emit "BuildingFile" and `RunningAction`, as these 1014 // two statuses are running faster in practice, which leads the UI constantly 1015 // changing, and doesn't provide much value. We may want to emit status at a 1016 // reasonable time interval (e.g. 0.5s). 1017 if (Status.Action.S == TUAction::BuildingFile || 1018 Status.Action.S == TUAction::RunningAction) 1019 return; 1020 notify("textDocument/clangd.fileStatus", Status.render(File)); 1021 } 1022 1023 void ClangdLSPServer::reparseOpenedFiles() { 1024 for (const Path &FilePath : DraftMgr.getActiveFiles()) 1025 Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath), 1026 WantDiagnostics::Auto); 1027 } 1028 1029 } // namespace clangd 1030 } // namespace clang 1031