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