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