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