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 "ProtocolHandlers.h" 13 14 using namespace clang::clangd; 15 using namespace clang; 16 17 namespace { 18 19 std::string 20 replacementsToEdits(StringRef Code, 21 const std::vector<tooling::Replacement> &Replacements) { 22 // Turn the replacements into the format specified by the Language Server 23 // Protocol. Fuse them into one big JSON array. 24 std::string Edits; 25 for (auto &R : Replacements) { 26 Range ReplacementRange = { 27 offsetToPosition(Code, R.getOffset()), 28 offsetToPosition(Code, R.getOffset() + R.getLength())}; 29 TextEdit TE = {ReplacementRange, R.getReplacementText()}; 30 Edits += TextEdit::unparse(TE); 31 Edits += ','; 32 } 33 if (!Edits.empty()) 34 Edits.pop_back(); 35 36 return Edits; 37 } 38 39 } // namespace 40 41 ClangdLSPServer::LSPDiagnosticsConsumer::LSPDiagnosticsConsumer( 42 ClangdLSPServer &Server) 43 : Server(Server) {} 44 45 void ClangdLSPServer::LSPDiagnosticsConsumer::onDiagnosticsReady( 46 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) { 47 Server.consumeDiagnostics(File, Diagnostics.Value); 48 } 49 50 class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks { 51 public: 52 LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {} 53 54 void onInitialize(StringRef ID, JSONOutput &Out) override; 55 void onShutdown(JSONOutput &Out) override; 56 void onDocumentDidOpen(DidOpenTextDocumentParams Params, 57 JSONOutput &Out) override; 58 void onDocumentDidChange(DidChangeTextDocumentParams Params, 59 JSONOutput &Out) override; 60 void onDocumentDidClose(DidCloseTextDocumentParams Params, 61 JSONOutput &Out) override; 62 void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params, 63 StringRef ID, JSONOutput &Out) override; 64 void onDocumentRangeFormatting(DocumentRangeFormattingParams Params, 65 StringRef ID, JSONOutput &Out) override; 66 void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID, 67 JSONOutput &Out) override; 68 void onCodeAction(CodeActionParams Params, StringRef ID, 69 JSONOutput &Out) override; 70 void onCompletion(TextDocumentPositionParams Params, StringRef ID, 71 JSONOutput &Out) override; 72 73 private: 74 ClangdLSPServer &LangServer; 75 }; 76 77 void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID, 78 JSONOutput &Out) { 79 Out.writeMessage( 80 R"({"jsonrpc":"2.0","id":)" + ID + 81 R"(,"result":{"capabilities":{ 82 "textDocumentSync": 1, 83 "documentFormattingProvider": true, 84 "documentRangeFormattingProvider": true, 85 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, 86 "codeActionProvider": true, 87 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]} 88 }}})"); 89 } 90 91 void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) { 92 LangServer.IsDone = true; 93 } 94 95 void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen( 96 DidOpenTextDocumentParams Params, JSONOutput &Out) { 97 LangServer.Server.addDocument(Params.textDocument.uri.file, 98 Params.textDocument.text); 99 } 100 101 void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange( 102 DidChangeTextDocumentParams Params, JSONOutput &Out) { 103 // We only support full syncing right now. 104 LangServer.Server.addDocument(Params.textDocument.uri.file, 105 Params.contentChanges[0].text); 106 } 107 108 void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose( 109 DidCloseTextDocumentParams Params, JSONOutput &Out) { 110 LangServer.Server.removeDocument(Params.textDocument.uri.file); 111 } 112 113 void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting( 114 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) { 115 auto File = Params.textDocument.uri.file; 116 std::string Code = LangServer.Server.getDocument(File); 117 std::string Edits = replacementsToEdits( 118 Code, LangServer.Server.formatOnType(File, Params.position)); 119 120 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + 121 R"(,"result":[)" + Edits + R"(]})"); 122 } 123 124 void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting( 125 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) { 126 auto File = Params.textDocument.uri.file; 127 std::string Code = LangServer.Server.getDocument(File); 128 std::string Edits = replacementsToEdits( 129 Code, LangServer.Server.formatRange(File, Params.range)); 130 131 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + 132 R"(,"result":[)" + Edits + R"(]})"); 133 } 134 135 void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting( 136 DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) { 137 auto File = Params.textDocument.uri.file; 138 std::string Code = LangServer.Server.getDocument(File); 139 std::string Edits = 140 replacementsToEdits(Code, LangServer.Server.formatFile(File)); 141 142 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + 143 R"(,"result":[)" + Edits + R"(]})"); 144 } 145 146 void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction( 147 CodeActionParams Params, StringRef ID, JSONOutput &Out) { 148 // We provide a code action for each diagnostic at the requested location 149 // which has FixIts available. 150 std::string Code = 151 LangServer.Server.getDocument(Params.textDocument.uri.file); 152 std::string Commands; 153 for (Diagnostic &D : Params.context.diagnostics) { 154 std::vector<clang::tooling::Replacement> Fixes = 155 LangServer.getFixIts(Params.textDocument.uri.file, D); 156 std::string Edits = replacementsToEdits(Code, Fixes); 157 158 if (!Edits.empty()) 159 Commands += 160 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) + 161 R"('", "command": "clangd.applyFix", "arguments": [")" + 162 llvm::yaml::escape(Params.textDocument.uri.uri) + 163 R"(", [)" + Edits + 164 R"(]]},)"; 165 } 166 if (!Commands.empty()) 167 Commands.pop_back(); 168 169 Out.writeMessage( 170 R"({"jsonrpc":"2.0","id":)" + ID.str() + 171 R"(, "result": [)" + Commands + 172 R"(]})"); 173 } 174 175 void ClangdLSPServer::LSPProtocolCallbacks::onCompletion( 176 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { 177 178 auto Items = LangServer.Server.codeComplete( 179 Params.textDocument.uri.file, 180 Position{Params.position.line, Params.position.character}).Value; 181 182 std::string Completions; 183 for (const auto &Item : Items) { 184 Completions += CompletionItem::unparse(Item); 185 Completions += ","; 186 } 187 if (!Completions.empty()) 188 Completions.pop_back(); 189 Out.writeMessage( 190 R"({"jsonrpc":"2.0","id":)" + ID.str() + 191 R"(,"result":[)" + Completions + R"(]})"); 192 } 193 194 ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously) 195 : Out(Out), DiagConsumer(*this), 196 Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {} 197 198 void ClangdLSPServer::run(std::istream &In) { 199 assert(!IsDone && "Run was called before"); 200 201 // Set up JSONRPCDispatcher. 202 LSPProtocolCallbacks Callbacks(*this); 203 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out)); 204 regiterCallbackHandlers(Dispatcher, Out, Callbacks); 205 206 // Run the Language Server loop. 207 runLanguageServerLoop(In, Out, Dispatcher, IsDone); 208 209 // Make sure IsDone is set to true after this method exits to ensure assertion 210 // at the start of the method fires if it's ever executed again. 211 IsDone = true; 212 } 213 214 std::vector<clang::tooling::Replacement> 215 ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) { 216 std::lock_guard<std::mutex> Lock(FixItsMutex); 217 auto DiagToFixItsIter = FixItsMap.find(File); 218 if (DiagToFixItsIter == FixItsMap.end()) 219 return {}; 220 221 const auto &DiagToFixItsMap = DiagToFixItsIter->second; 222 auto FixItsIter = DiagToFixItsMap.find(D); 223 if (FixItsIter == DiagToFixItsMap.end()) 224 return {}; 225 226 return FixItsIter->second; 227 } 228 229 void ClangdLSPServer::consumeDiagnostics( 230 PathRef File, std::vector<DiagWithFixIts> Diagnostics) { 231 std::string DiagnosticsJSON; 232 233 DiagnosticToReplacementMap LocalFixIts; // Temporary storage 234 for (auto &DiagWithFixes : Diagnostics) { 235 auto Diag = DiagWithFixes.Diag; 236 DiagnosticsJSON += 237 R"({"range":)" + Range::unparse(Diag.range) + 238 R"(,"severity":)" + std::to_string(Diag.severity) + 239 R"(,"message":")" + llvm::yaml::escape(Diag.message) + 240 R"("},)"; 241 242 // We convert to Replacements to become independent of the SourceManager. 243 auto &FixItsForDiagnostic = LocalFixIts[Diag]; 244 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(), 245 std::back_inserter(FixItsForDiagnostic)); 246 } 247 248 // Cache FixIts 249 { 250 // FIXME(ibiryukov): should be deleted when documents are removed 251 std::lock_guard<std::mutex> Lock(FixItsMutex); 252 FixItsMap[File] = LocalFixIts; 253 } 254 255 // Publish diagnostics. 256 if (!DiagnosticsJSON.empty()) 257 DiagnosticsJSON.pop_back(); // Drop trailing comma. 258 Out.writeMessage( 259 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" + 260 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON + 261 R"(]}})"); 262 } 263