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