1 //===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===---------------------------------------------------------------------===// 9 10 #include "ClangdLSPServer.h" 11 #include "Diagnostics.h" 12 #include "JSONRPCDispatcher.h" 13 #include "SourceCode.h" 14 #include "URI.h" 15 #include "llvm/Support/Errc.h" 16 #include "llvm/Support/FormatVariadic.h" 17 #include "llvm/Support/Path.h" 18 19 using namespace clang::clangd; 20 using namespace clang; 21 22 namespace { 23 24 /// \brief Supports a test URI scheme with relaxed constraints for lit tests. 25 /// The path in a test URI will be combined with a platform-specific fake 26 /// directory to form an absolute path. For example, test:///a.cpp is resolved 27 /// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix. 28 class TestScheme : public URIScheme { 29 public: 30 llvm::Expected<std::string> 31 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, 32 llvm::StringRef /*HintPath*/) const override { 33 using namespace llvm::sys; 34 // Still require "/" in body to mimic file scheme, as we want lengths of an 35 // equivalent URI in both schemes to be the same. 36 if (!Body.startswith("/")) 37 return llvm::make_error<llvm::StringError>( 38 "Expect URI body to be an absolute path starting with '/': " + Body, 39 llvm::inconvertibleErrorCode()); 40 Body = Body.ltrim('/'); 41 #ifdef _WIN32 42 constexpr char TestDir[] = "C:\\clangd-test"; 43 #else 44 constexpr char TestDir[] = "/clangd-test"; 45 #endif 46 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end()); 47 path::native(Path); 48 auto Err = fs::make_absolute(TestDir, Path); 49 if (Err) 50 llvm_unreachable("Failed to make absolute path in test scheme."); 51 return std::string(Path.begin(), Path.end()); 52 } 53 54 llvm::Expected<URI> 55 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { 56 llvm_unreachable("Clangd must never create a test URI."); 57 } 58 }; 59 60 static URISchemeRegistry::Add<TestScheme> 61 X("test", "Test scheme for clangd lit tests."); 62 63 TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) { 64 Range ReplacementRange = { 65 offsetToPosition(Code, R.getOffset()), 66 offsetToPosition(Code, R.getOffset() + R.getLength())}; 67 return {ReplacementRange, R.getReplacementText()}; 68 } 69 70 std::vector<TextEdit> 71 replacementsToEdits(StringRef Code, 72 const std::vector<tooling::Replacement> &Replacements) { 73 // Turn the replacements into the format specified by the Language Server 74 // Protocol. Fuse them into one big JSON array. 75 std::vector<TextEdit> Edits; 76 for (const auto &R : Replacements) 77 Edits.push_back(replacementToEdit(Code, R)); 78 return Edits; 79 } 80 81 std::vector<TextEdit> replacementsToEdits(StringRef Code, 82 const tooling::Replacements &Repls) { 83 std::vector<TextEdit> Edits; 84 for (const auto &R : Repls) 85 Edits.push_back(replacementToEdit(Code, R)); 86 return Edits; 87 } 88 89 SymbolKindBitset defaultSymbolKinds() { 90 SymbolKindBitset Defaults; 91 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array); 92 ++I) 93 Defaults.set(I); 94 return Defaults; 95 } 96 97 } // namespace 98 99 void ClangdLSPServer::onInitialize(InitializeParams &Params) { 100 if (Params.rootUri && *Params.rootUri) 101 Server.setRootPath(Params.rootUri->file()); 102 else if (Params.rootPath && !Params.rootPath->empty()) 103 Server.setRootPath(*Params.rootPath); 104 105 CCOpts.EnableSnippets = 106 Params.capabilities.textDocument.completion.completionItem.snippetSupport; 107 108 if (Params.capabilities.workspace && Params.capabilities.workspace->symbol && 109 Params.capabilities.workspace->symbol->symbolKind) { 110 for (SymbolKind Kind : 111 *Params.capabilities.workspace->symbol->symbolKind->valueSet) { 112 SupportedSymbolKinds.set(static_cast<size_t>(Kind)); 113 } 114 } 115 116 reply(json::obj{ 117 {{"capabilities", 118 json::obj{ 119 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental}, 120 {"documentFormattingProvider", true}, 121 {"documentRangeFormattingProvider", true}, 122 {"documentOnTypeFormattingProvider", 123 json::obj{ 124 {"firstTriggerCharacter", "}"}, 125 {"moreTriggerCharacter", {}}, 126 }}, 127 {"codeActionProvider", true}, 128 {"completionProvider", 129 json::obj{ 130 {"resolveProvider", false}, 131 {"triggerCharacters", {".", ">", ":"}}, 132 }}, 133 {"signatureHelpProvider", 134 json::obj{ 135 {"triggerCharacters", {"(", ","}}, 136 }}, 137 {"definitionProvider", true}, 138 {"documentHighlightProvider", true}, 139 {"hoverProvider", true}, 140 {"renameProvider", true}, 141 {"workspaceSymbolProvider", true}, 142 {"executeCommandProvider", 143 json::obj{ 144 {"commands", 145 {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, 146 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}}, 147 }}, 148 }}}}); 149 } 150 151 void ClangdLSPServer::onShutdown(ShutdownParams &Params) { 152 // Do essentially nothing, just say we're ready to exit. 153 ShutdownRequestReceived = true; 154 reply(nullptr); 155 } 156 157 void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; } 158 159 void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) { 160 if (Params.metadata && !Params.metadata->extraFlags.empty()) 161 CDB.setExtraFlagsForFile(Params.textDocument.uri.file(), 162 std::move(Params.metadata->extraFlags)); 163 164 PathRef File = Params.textDocument.uri.file(); 165 std::string &Contents = Params.textDocument.text; 166 167 DraftMgr.addDraft(File, Contents); 168 Server.addDocument(File, Contents, WantDiagnostics::Yes); 169 } 170 171 void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) { 172 auto WantDiags = WantDiagnostics::Auto; 173 if (Params.wantDiagnostics.hasValue()) 174 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes 175 : WantDiagnostics::No; 176 177 PathRef File = Params.textDocument.uri.file(); 178 llvm::Expected<std::string> Contents = 179 DraftMgr.updateDraft(File, Params.contentChanges); 180 if (!Contents) { 181 // If this fails, we are most likely going to be not in sync anymore with 182 // the client. It is better to remove the draft and let further operations 183 // fail rather than giving wrong results. 184 DraftMgr.removeDraft(File); 185 Server.removeDocument(File); 186 log(llvm::toString(Contents.takeError())); 187 return; 188 } 189 190 Server.addDocument(File, *Contents, WantDiags); 191 } 192 193 void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) { 194 Server.onFileEvent(Params); 195 } 196 197 void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { 198 auto ApplyEdit = [](WorkspaceEdit WE) { 199 ApplyWorkspaceEditParams Edit; 200 Edit.edit = std::move(WE); 201 // We don't need the response so id == 1 is OK. 202 // Ideally, we would wait for the response and if there is no error, we 203 // would reply success/failure to the original RPC. 204 call("workspace/applyEdit", Edit); 205 }; 206 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && 207 Params.workspaceEdit) { 208 // The flow for "apply-fix" : 209 // 1. We publish a diagnostic, including fixits 210 // 2. The user clicks on the diagnostic, the editor asks us for code actions 211 // 3. We send code actions, with the fixit embedded as context 212 // 4. The user selects the fixit, the editor asks us to apply it 213 // 5. We unwrap the changes and send them back to the editor 214 // 6. The editor applies the changes (applyEdit), and sends us a reply (but 215 // we ignore it) 216 217 reply("Fix applied."); 218 ApplyEdit(*Params.workspaceEdit); 219 } else if (Params.command == 220 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) { 221 auto &FileURI = Params.includeInsertion->textDocument.uri; 222 auto Code = DraftMgr.getDraft(FileURI.file()); 223 if (!Code) 224 return replyError(ErrorCode::InvalidParams, 225 ("command " + 226 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE + 227 " called on non-added file " + FileURI.file()) 228 .str()); 229 llvm::StringRef DeclaringHeader = Params.includeInsertion->declaringHeader; 230 if (DeclaringHeader.empty()) 231 return replyError( 232 ErrorCode::InvalidParams, 233 "declaringHeader must be provided for include insertion."); 234 llvm::StringRef PreferredHeader = Params.includeInsertion->preferredHeader; 235 auto Replaces = Server.insertInclude( 236 FileURI.file(), *Code, DeclaringHeader, 237 PreferredHeader.empty() ? DeclaringHeader : PreferredHeader); 238 if (!Replaces) { 239 std::string ErrMsg = 240 ("Failed to generate include insertion edits for adding " + 241 DeclaringHeader + " (" + PreferredHeader + ") into " + 242 FileURI.file()) 243 .str(); 244 log(ErrMsg + ":" + llvm::toString(Replaces.takeError())); 245 replyError(ErrorCode::InternalError, ErrMsg); 246 return; 247 } 248 auto Edits = replacementsToEdits(*Code, *Replaces); 249 WorkspaceEdit WE; 250 WE.changes = {{FileURI.uri(), Edits}}; 251 252 reply(("Inserted header " + DeclaringHeader + " (" + PreferredHeader + ")") 253 .str()); 254 ApplyEdit(std::move(WE)); 255 } else { 256 // We should not get here because ExecuteCommandParams would not have 257 // parsed in the first place and this handler should not be called. But if 258 // more commands are added, this will be here has a safe guard. 259 replyError( 260 ErrorCode::InvalidParams, 261 llvm::formatv("Unsupported command \"{0}\".", Params.command).str()); 262 } 263 } 264 265 void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) { 266 Server.workspaceSymbols( 267 Params.query, CCOpts.Limit, 268 [this](llvm::Expected<std::vector<SymbolInformation>> Items) { 269 if (!Items) 270 return replyError(ErrorCode::InternalError, 271 llvm::toString(Items.takeError())); 272 for (auto &Sym : *Items) 273 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); 274 275 reply(json::ary(*Items)); 276 }); 277 } 278 279 void ClangdLSPServer::onRename(RenameParams &Params) { 280 Path File = Params.textDocument.uri.file(); 281 llvm::Optional<std::string> Code = DraftMgr.getDraft(File); 282 if (!Code) 283 return replyError(ErrorCode::InvalidParams, 284 "onRename called for non-added file"); 285 286 Server.rename( 287 File, Params.position, Params.newName, 288 [File, Code, 289 Params](llvm::Expected<std::vector<tooling::Replacement>> Replacements) { 290 if (!Replacements) 291 return replyError(ErrorCode::InternalError, 292 llvm::toString(Replacements.takeError())); 293 294 std::vector<TextEdit> Edits = replacementsToEdits(*Code, *Replacements); 295 WorkspaceEdit WE; 296 WE.changes = {{Params.textDocument.uri.uri(), Edits}}; 297 reply(WE); 298 }); 299 } 300 301 void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) { 302 PathRef File = Params.textDocument.uri.file(); 303 DraftMgr.removeDraft(File); 304 Server.removeDocument(File); 305 } 306 307 void ClangdLSPServer::onDocumentOnTypeFormatting( 308 DocumentOnTypeFormattingParams &Params) { 309 auto File = Params.textDocument.uri.file(); 310 auto Code = DraftMgr.getDraft(File); 311 if (!Code) 312 return replyError(ErrorCode::InvalidParams, 313 "onDocumentOnTypeFormatting called for non-added file"); 314 315 auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position); 316 if (ReplacementsOrError) 317 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get()))); 318 else 319 replyError(ErrorCode::UnknownErrorCode, 320 llvm::toString(ReplacementsOrError.takeError())); 321 } 322 323 void ClangdLSPServer::onDocumentRangeFormatting( 324 DocumentRangeFormattingParams &Params) { 325 auto File = Params.textDocument.uri.file(); 326 auto Code = DraftMgr.getDraft(File); 327 if (!Code) 328 return replyError(ErrorCode::InvalidParams, 329 "onDocumentRangeFormatting called for non-added file"); 330 331 auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range); 332 if (ReplacementsOrError) 333 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get()))); 334 else 335 replyError(ErrorCode::UnknownErrorCode, 336 llvm::toString(ReplacementsOrError.takeError())); 337 } 338 339 void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) { 340 auto File = Params.textDocument.uri.file(); 341 auto Code = DraftMgr.getDraft(File); 342 if (!Code) 343 return replyError(ErrorCode::InvalidParams, 344 "onDocumentFormatting called for non-added file"); 345 346 auto ReplacementsOrError = Server.formatFile(*Code, File); 347 if (ReplacementsOrError) 348 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get()))); 349 else 350 replyError(ErrorCode::UnknownErrorCode, 351 llvm::toString(ReplacementsOrError.takeError())); 352 } 353 354 void ClangdLSPServer::onCodeAction(CodeActionParams &Params) { 355 // We provide a code action for each diagnostic at the requested location 356 // which has FixIts available. 357 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); 358 if (!Code) 359 return replyError(ErrorCode::InvalidParams, 360 "onCodeAction called for non-added file"); 361 362 json::ary Commands; 363 for (Diagnostic &D : Params.context.diagnostics) { 364 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) { 365 WorkspaceEdit WE; 366 std::vector<TextEdit> Edits(F.Edits.begin(), F.Edits.end()); 367 WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}}; 368 Commands.push_back(json::obj{ 369 {"title", llvm::formatv("Apply fix: {0}", F.Message)}, 370 {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}, 371 {"arguments", {WE}}, 372 }); 373 } 374 } 375 reply(std::move(Commands)); 376 } 377 378 void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) { 379 Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts, 380 [](llvm::Expected<CompletionList> List) { 381 if (!List) 382 return replyError(ErrorCode::InvalidParams, 383 llvm::toString(List.takeError())); 384 reply(*List); 385 }); 386 } 387 388 void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) { 389 Server.signatureHelp(Params.textDocument.uri.file(), Params.position, 390 [](llvm::Expected<SignatureHelp> SignatureHelp) { 391 if (!SignatureHelp) 392 return replyError( 393 ErrorCode::InvalidParams, 394 llvm::toString(SignatureHelp.takeError())); 395 reply(*SignatureHelp); 396 }); 397 } 398 399 void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) { 400 Server.findDefinitions( 401 Params.textDocument.uri.file(), Params.position, 402 [](llvm::Expected<std::vector<Location>> Items) { 403 if (!Items) 404 return replyError(ErrorCode::InvalidParams, 405 llvm::toString(Items.takeError())); 406 reply(json::ary(*Items)); 407 }); 408 } 409 410 void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) { 411 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file()); 412 reply(Result ? URI::createFile(*Result).toString() : ""); 413 } 414 415 void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) { 416 Server.findDocumentHighlights( 417 Params.textDocument.uri.file(), Params.position, 418 [](llvm::Expected<std::vector<DocumentHighlight>> Highlights) { 419 if (!Highlights) 420 return replyError(ErrorCode::InternalError, 421 llvm::toString(Highlights.takeError())); 422 reply(json::ary(*Highlights)); 423 }); 424 } 425 426 void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) { 427 Server.findHover(Params.textDocument.uri.file(), Params.position, 428 [](llvm::Expected<Hover> H) { 429 if (!H) { 430 replyError(ErrorCode::InternalError, 431 llvm::toString(H.takeError())); 432 return; 433 } 434 435 reply(*H); 436 }); 437 } 438 439 // FIXME: This function needs to be properly tested. 440 void ClangdLSPServer::onChangeConfiguration( 441 DidChangeConfigurationParams &Params) { 442 ClangdConfigurationParamsChange &Settings = Params.settings; 443 444 // Compilation database change. 445 if (Settings.compilationDatabasePath.hasValue()) { 446 CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue()); 447 reparseOpenedFiles(); 448 } 449 } 450 451 ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, 452 const clangd::CodeCompleteOptions &CCOpts, 453 llvm::Optional<Path> CompileCommandsDir, 454 const ClangdServer::Options &Opts) 455 : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts), 456 SupportedSymbolKinds(defaultSymbolKinds()), 457 Server(CDB, FSProvider, /*DiagConsumer=*/*this, Opts) {} 458 459 bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) { 460 assert(!IsDone && "Run was called before"); 461 462 // Set up JSONRPCDispatcher. 463 JSONRPCDispatcher Dispatcher([](const json::Expr &Params) { 464 replyError(ErrorCode::MethodNotFound, "method not found"); 465 }); 466 registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this); 467 468 // Run the Language Server loop. 469 runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone); 470 471 // Make sure IsDone is set to true after this method exits to ensure assertion 472 // at the start of the method fires if it's ever executed again. 473 IsDone = true; 474 475 return ShutdownRequestReceived; 476 } 477 478 std::vector<Fix> ClangdLSPServer::getFixes(StringRef File, 479 const clangd::Diagnostic &D) { 480 std::lock_guard<std::mutex> Lock(FixItsMutex); 481 auto DiagToFixItsIter = FixItsMap.find(File); 482 if (DiagToFixItsIter == FixItsMap.end()) 483 return {}; 484 485 const auto &DiagToFixItsMap = DiagToFixItsIter->second; 486 auto FixItsIter = DiagToFixItsMap.find(D); 487 if (FixItsIter == DiagToFixItsMap.end()) 488 return {}; 489 490 return FixItsIter->second; 491 } 492 493 void ClangdLSPServer::onDiagnosticsReady(PathRef File, 494 std::vector<Diag> Diagnostics) { 495 json::ary DiagnosticsJSON; 496 497 DiagnosticToReplacementMap LocalFixIts; // Temporary storage 498 for (auto &Diag : Diagnostics) { 499 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) { 500 DiagnosticsJSON.push_back(json::obj{ 501 {"range", Diag.range}, 502 {"severity", Diag.severity}, 503 {"message", Diag.message}, 504 }); 505 506 auto &FixItsForDiagnostic = LocalFixIts[Diag]; 507 std::copy(Fixes.begin(), Fixes.end(), 508 std::back_inserter(FixItsForDiagnostic)); 509 }); 510 } 511 512 // Cache FixIts 513 { 514 // FIXME(ibiryukov): should be deleted when documents are removed 515 std::lock_guard<std::mutex> Lock(FixItsMutex); 516 FixItsMap[File] = LocalFixIts; 517 } 518 519 // Publish diagnostics. 520 Out.writeMessage(json::obj{ 521 {"jsonrpc", "2.0"}, 522 {"method", "textDocument/publishDiagnostics"}, 523 {"params", 524 json::obj{ 525 {"uri", URIForFile{File}}, 526 {"diagnostics", std::move(DiagnosticsJSON)}, 527 }}, 528 }); 529 } 530 531 void ClangdLSPServer::reparseOpenedFiles() { 532 for (const Path &FilePath : DraftMgr.getActiveFiles()) 533 Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath), 534 WantDiagnostics::Auto, 535 /*SkipCache=*/true); 536 } 537