150
|
1 //===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===//
|
|
2 //
|
|
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
4 // See https://llvm.org/LICENSE.txt for license information.
|
|
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
6 //
|
|
7 //===----------------------------------------------------------------------===//
|
|
8
|
|
9 #include "ClangdLSPServer.h"
|
173
|
10 #include "CodeComplete.h"
|
150
|
11 #include "Diagnostics.h"
|
|
12 #include "DraftStore.h"
|
|
13 #include "GlobalCompilationDatabase.h"
|
|
14 #include "Protocol.h"
|
|
15 #include "SemanticHighlighting.h"
|
|
16 #include "SourceCode.h"
|
173
|
17 #include "TUScheduler.h"
|
150
|
18 #include "URI.h"
|
|
19 #include "refactor/Tweak.h"
|
173
|
20 #include "support/Context.h"
|
|
21 #include "support/Trace.h"
|
|
22 #include "clang/Basic/Version.h"
|
150
|
23 #include "clang/Tooling/Core/Replacement.h"
|
|
24 #include "llvm/ADT/ArrayRef.h"
|
|
25 #include "llvm/ADT/Optional.h"
|
|
26 #include "llvm/ADT/ScopeExit.h"
|
|
27 #include "llvm/ADT/StringRef.h"
|
|
28 #include "llvm/ADT/iterator_range.h"
|
|
29 #include "llvm/Support/Errc.h"
|
|
30 #include "llvm/Support/Error.h"
|
|
31 #include "llvm/Support/FormatVariadic.h"
|
|
32 #include "llvm/Support/JSON.h"
|
|
33 #include "llvm/Support/Path.h"
|
|
34 #include "llvm/Support/SHA1.h"
|
|
35 #include "llvm/Support/ScopedPrinter.h"
|
|
36 #include <cstddef>
|
|
37 #include <memory>
|
|
38 #include <mutex>
|
|
39 #include <string>
|
|
40 #include <vector>
|
|
41
|
|
42 namespace clang {
|
|
43 namespace clangd {
|
|
44 namespace {
|
173
|
45
|
|
46 // Tracks end-to-end latency of high level lsp calls. Measurements are in
|
|
47 // seconds.
|
|
48 constexpr trace::Metric LSPLatency("lsp_latency", trace::Metric::Distribution,
|
|
49 "method_name");
|
|
50
|
|
51 // LSP defines file versions as numbers that increase.
|
|
52 // ClangdServer treats them as opaque and therefore uses strings instead.
|
|
53 std::string encodeVersion(int64_t LSPVersion) {
|
|
54 return llvm::to_string(LSPVersion);
|
|
55 }
|
|
56 llvm::Optional<int64_t> decodeVersion(llvm::StringRef Encoded) {
|
|
57 int64_t Result;
|
|
58 if (llvm::to_integer(Encoded, Result, 10))
|
|
59 return Result;
|
|
60 else if (!Encoded.empty()) // Empty can be e.g. diagnostics on close.
|
|
61 elog("unexpected non-numeric version {0}", Encoded);
|
|
62 return llvm::None;
|
|
63 }
|
|
64
|
150
|
65 /// Transforms a tweak into a code action that would apply it if executed.
|
|
66 /// EXPECTS: T.prepare() was called and returned true.
|
|
67 CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File,
|
|
68 Range Selection) {
|
|
69 CodeAction CA;
|
|
70 CA.title = T.Title;
|
|
71 switch (T.Intent) {
|
|
72 case Tweak::Refactor:
|
|
73 CA.kind = std::string(CodeAction::REFACTOR_KIND);
|
|
74 break;
|
|
75 case Tweak::Info:
|
|
76 CA.kind = std::string(CodeAction::INFO_KIND);
|
|
77 break;
|
|
78 }
|
|
79 // This tweak may have an expensive second stage, we only run it if the user
|
|
80 // actually chooses it in the UI. We reply with a command that would run the
|
|
81 // corresponding tweak.
|
|
82 // FIXME: for some tweaks, computing the edits is cheap and we could send them
|
|
83 // directly.
|
|
84 CA.command.emplace();
|
|
85 CA.command->title = T.Title;
|
|
86 CA.command->command = std::string(Command::CLANGD_APPLY_TWEAK);
|
|
87 CA.command->tweakArgs.emplace();
|
|
88 CA.command->tweakArgs->file = File;
|
|
89 CA.command->tweakArgs->tweakID = T.ID;
|
|
90 CA.command->tweakArgs->selection = Selection;
|
|
91 return CA;
|
|
92 }
|
|
93
|
|
94 void adjustSymbolKinds(llvm::MutableArrayRef<DocumentSymbol> Syms,
|
|
95 SymbolKindBitset Kinds) {
|
|
96 for (auto &S : Syms) {
|
|
97 S.kind = adjustKindToCapability(S.kind, Kinds);
|
|
98 adjustSymbolKinds(S.children, Kinds);
|
|
99 }
|
|
100 }
|
|
101
|
|
102 SymbolKindBitset defaultSymbolKinds() {
|
|
103 SymbolKindBitset Defaults;
|
|
104 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array);
|
|
105 ++I)
|
|
106 Defaults.set(I);
|
|
107 return Defaults;
|
|
108 }
|
|
109
|
|
110 CompletionItemKindBitset defaultCompletionItemKinds() {
|
|
111 CompletionItemKindBitset Defaults;
|
|
112 for (size_t I = CompletionItemKindMin;
|
|
113 I <= static_cast<size_t>(CompletionItemKind::Reference); ++I)
|
|
114 Defaults.set(I);
|
|
115 return Defaults;
|
|
116 }
|
|
117
|
|
118 // Build a lookup table (HighlightingKind => {TextMate Scopes}), which is sent
|
|
119 // to the LSP client.
|
|
120 std::vector<std::vector<std::string>> buildHighlightScopeLookupTable() {
|
|
121 std::vector<std::vector<std::string>> LookupTable;
|
|
122 // HighlightingKind is using as the index.
|
|
123 for (int KindValue = 0; KindValue <= (int)HighlightingKind::LastKind;
|
|
124 ++KindValue)
|
|
125 LookupTable.push_back(
|
|
126 {std::string(toTextMateScope((HighlightingKind)(KindValue)))});
|
|
127 return LookupTable;
|
|
128 }
|
|
129
|
|
130 // Makes sure edits in \p FE are applicable to latest file contents reported by
|
|
131 // editor. If not generates an error message containing information about files
|
|
132 // that needs to be saved.
|
|
133 llvm::Error validateEdits(const DraftStore &DraftMgr, const FileEdits &FE) {
|
|
134 size_t InvalidFileCount = 0;
|
|
135 llvm::StringRef LastInvalidFile;
|
|
136 for (const auto &It : FE) {
|
|
137 if (auto Draft = DraftMgr.getDraft(It.first())) {
|
|
138 // If the file is open in user's editor, make sure the version we
|
|
139 // saw and current version are compatible as this is the text that
|
|
140 // will be replaced by editors.
|
173
|
141 if (!It.second.canApplyTo(Draft->Contents)) {
|
150
|
142 ++InvalidFileCount;
|
|
143 LastInvalidFile = It.first();
|
|
144 }
|
|
145 }
|
|
146 }
|
|
147 if (!InvalidFileCount)
|
|
148 return llvm::Error::success();
|
|
149 if (InvalidFileCount == 1)
|
|
150 return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
151 "File must be saved first: " +
|
|
152 LastInvalidFile);
|
|
153 return llvm::createStringError(
|
|
154 llvm::inconvertibleErrorCode(),
|
|
155 "Files must be saved first: " + LastInvalidFile + " (and " +
|
|
156 llvm::to_string(InvalidFileCount - 1) + " others)");
|
|
157 }
|
|
158
|
|
159 } // namespace
|
|
160
|
|
161 // MessageHandler dispatches incoming LSP messages.
|
|
162 // It handles cross-cutting concerns:
|
|
163 // - serializes/deserializes protocol objects to JSON
|
|
164 // - logging of inbound messages
|
|
165 // - cancellation handling
|
|
166 // - basic call tracing
|
|
167 // MessageHandler ensures that initialize() is called before any other handler.
|
|
168 class ClangdLSPServer::MessageHandler : public Transport::MessageHandler {
|
|
169 public:
|
|
170 MessageHandler(ClangdLSPServer &Server) : Server(Server) {}
|
|
171
|
|
172 bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
|
|
173 WithContext HandlerContext(handlerContext());
|
|
174 log("<-- {0}", Method);
|
|
175 if (Method == "exit")
|
|
176 return false;
|
|
177 if (!Server.Server)
|
|
178 elog("Notification {0} before initialization", Method);
|
|
179 else if (Method == "$/cancelRequest")
|
|
180 onCancel(std::move(Params));
|
|
181 else if (auto Handler = Notifications.lookup(Method))
|
|
182 Handler(std::move(Params));
|
|
183 else
|
|
184 log("unhandled notification {0}", Method);
|
|
185 return true;
|
|
186 }
|
|
187
|
|
188 bool onCall(llvm::StringRef Method, llvm::json::Value Params,
|
|
189 llvm::json::Value ID) override {
|
|
190 WithContext HandlerContext(handlerContext());
|
|
191 // Calls can be canceled by the client. Add cancellation context.
|
|
192 WithContext WithCancel(cancelableRequestContext(ID));
|
173
|
193 trace::Span Tracer(Method, LSPLatency);
|
150
|
194 SPAN_ATTACH(Tracer, "Params", Params);
|
|
195 ReplyOnce Reply(ID, Method, &Server, Tracer.Args);
|
|
196 log("<-- {0}({1})", Method, ID);
|
|
197 if (!Server.Server && Method != "initialize") {
|
|
198 elog("Call {0} before initialization.", Method);
|
|
199 Reply(llvm::make_error<LSPError>("server not initialized",
|
|
200 ErrorCode::ServerNotInitialized));
|
|
201 } else if (auto Handler = Calls.lookup(Method))
|
|
202 Handler(std::move(Params), std::move(Reply));
|
|
203 else
|
|
204 Reply(llvm::make_error<LSPError>("method not found",
|
|
205 ErrorCode::MethodNotFound));
|
|
206 return true;
|
|
207 }
|
|
208
|
|
209 bool onReply(llvm::json::Value ID,
|
|
210 llvm::Expected<llvm::json::Value> Result) override {
|
|
211 WithContext HandlerContext(handlerContext());
|
|
212
|
|
213 Callback<llvm::json::Value> ReplyHandler = nullptr;
|
|
214 if (auto IntID = ID.getAsInteger()) {
|
|
215 std::lock_guard<std::mutex> Mutex(CallMutex);
|
|
216 // Find a corresponding callback for the request ID;
|
|
217 for (size_t Index = 0; Index < ReplyCallbacks.size(); ++Index) {
|
|
218 if (ReplyCallbacks[Index].first == *IntID) {
|
|
219 ReplyHandler = std::move(ReplyCallbacks[Index].second);
|
|
220 ReplyCallbacks.erase(ReplyCallbacks.begin() +
|
|
221 Index); // remove the entry
|
|
222 break;
|
|
223 }
|
|
224 }
|
|
225 }
|
|
226
|
|
227 if (!ReplyHandler) {
|
|
228 // No callback being found, use a default log callback.
|
|
229 ReplyHandler = [&ID](llvm::Expected<llvm::json::Value> Result) {
|
|
230 elog("received a reply with ID {0}, but there was no such call", ID);
|
|
231 if (!Result)
|
|
232 llvm::consumeError(Result.takeError());
|
|
233 };
|
|
234 }
|
|
235
|
|
236 // Log and run the reply handler.
|
|
237 if (Result) {
|
|
238 log("<-- reply({0})", ID);
|
|
239 ReplyHandler(std::move(Result));
|
|
240 } else {
|
|
241 auto Err = Result.takeError();
|
|
242 log("<-- reply({0}) error: {1}", ID, Err);
|
|
243 ReplyHandler(std::move(Err));
|
|
244 }
|
|
245 return true;
|
|
246 }
|
|
247
|
|
248 // Bind an LSP method name to a call.
|
|
249 template <typename Param, typename Result>
|
|
250 void bind(const char *Method,
|
|
251 void (ClangdLSPServer::*Handler)(const Param &, Callback<Result>)) {
|
|
252 Calls[Method] = [Method, Handler, this](llvm::json::Value RawParams,
|
|
253 ReplyOnce Reply) {
|
|
254 Param P;
|
|
255 if (fromJSON(RawParams, P)) {
|
|
256 (Server.*Handler)(P, std::move(Reply));
|
|
257 } else {
|
|
258 elog("Failed to decode {0} request.", Method);
|
|
259 Reply(llvm::make_error<LSPError>("failed to decode request",
|
|
260 ErrorCode::InvalidRequest));
|
|
261 }
|
|
262 };
|
|
263 }
|
|
264
|
|
265 // Bind a reply callback to a request. The callback will be invoked when
|
|
266 // clangd receives the reply from the LSP client.
|
|
267 // Return a call id of the request.
|
|
268 llvm::json::Value bindReply(Callback<llvm::json::Value> Reply) {
|
|
269 llvm::Optional<std::pair<int, Callback<llvm::json::Value>>> OldestCB;
|
|
270 int ID;
|
|
271 {
|
|
272 std::lock_guard<std::mutex> Mutex(CallMutex);
|
|
273 ID = NextCallID++;
|
|
274 ReplyCallbacks.emplace_back(ID, std::move(Reply));
|
|
275
|
|
276 // If the queue overflows, we assume that the client didn't reply the
|
|
277 // oldest request, and run the corresponding callback which replies an
|
|
278 // error to the client.
|
|
279 if (ReplyCallbacks.size() > MaxReplayCallbacks) {
|
|
280 elog("more than {0} outstanding LSP calls, forgetting about {1}",
|
|
281 MaxReplayCallbacks, ReplyCallbacks.front().first);
|
|
282 OldestCB = std::move(ReplyCallbacks.front());
|
|
283 ReplyCallbacks.pop_front();
|
|
284 }
|
|
285 }
|
|
286 if (OldestCB)
|
|
287 OldestCB->second(llvm::createStringError(
|
|
288 llvm::inconvertibleErrorCode(),
|
|
289 llvm::formatv("failed to receive a client reply for request ({0})",
|
|
290 OldestCB->first)));
|
|
291 return ID;
|
|
292 }
|
|
293
|
|
294 // Bind an LSP method name to a notification.
|
|
295 template <typename Param>
|
|
296 void bind(const char *Method,
|
|
297 void (ClangdLSPServer::*Handler)(const Param &)) {
|
|
298 Notifications[Method] = [Method, Handler,
|
|
299 this](llvm::json::Value RawParams) {
|
|
300 Param P;
|
|
301 if (!fromJSON(RawParams, P)) {
|
|
302 elog("Failed to decode {0} request.", Method);
|
|
303 return;
|
|
304 }
|
173
|
305 trace::Span Tracer(Method, LSPLatency);
|
150
|
306 SPAN_ATTACH(Tracer, "Params", RawParams);
|
|
307 (Server.*Handler)(P);
|
|
308 };
|
|
309 }
|
|
310
|
|
311 private:
|
|
312 // Function object to reply to an LSP call.
|
|
313 // Each instance must be called exactly once, otherwise:
|
|
314 // - the bug is logged, and (in debug mode) an assert will fire
|
|
315 // - if there was no reply, an error reply is sent
|
|
316 // - if there were multiple replies, only the first is sent
|
|
317 class ReplyOnce {
|
|
318 std::atomic<bool> Replied = {false};
|
|
319 std::chrono::steady_clock::time_point Start;
|
|
320 llvm::json::Value ID;
|
|
321 std::string Method;
|
|
322 ClangdLSPServer *Server; // Null when moved-from.
|
|
323 llvm::json::Object *TraceArgs;
|
|
324
|
|
325 public:
|
|
326 ReplyOnce(const llvm::json::Value &ID, llvm::StringRef Method,
|
|
327 ClangdLSPServer *Server, llvm::json::Object *TraceArgs)
|
|
328 : Start(std::chrono::steady_clock::now()), ID(ID), Method(Method),
|
|
329 Server(Server), TraceArgs(TraceArgs) {
|
|
330 assert(Server);
|
|
331 }
|
|
332 ReplyOnce(ReplyOnce &&Other)
|
|
333 : Replied(Other.Replied.load()), Start(Other.Start),
|
|
334 ID(std::move(Other.ID)), Method(std::move(Other.Method)),
|
|
335 Server(Other.Server), TraceArgs(Other.TraceArgs) {
|
|
336 Other.Server = nullptr;
|
|
337 }
|
|
338 ReplyOnce &operator=(ReplyOnce &&) = delete;
|
|
339 ReplyOnce(const ReplyOnce &) = delete;
|
|
340 ReplyOnce &operator=(const ReplyOnce &) = delete;
|
|
341
|
|
342 ~ReplyOnce() {
|
|
343 // There's one legitimate reason to never reply to a request: clangd's
|
|
344 // request handler send a call to the client (e.g. applyEdit) and the
|
|
345 // client never replied. In this case, the ReplyOnce is owned by
|
|
346 // ClangdLSPServer's reply callback table and is destroyed along with the
|
|
347 // server. We don't attempt to send a reply in this case, there's little
|
|
348 // to be gained from doing so.
|
|
349 if (Server && !Server->IsBeingDestroyed && !Replied) {
|
|
350 elog("No reply to message {0}({1})", Method, ID);
|
|
351 assert(false && "must reply to all calls!");
|
|
352 (*this)(llvm::make_error<LSPError>("server failed to reply",
|
|
353 ErrorCode::InternalError));
|
|
354 }
|
|
355 }
|
|
356
|
|
357 void operator()(llvm::Expected<llvm::json::Value> Reply) {
|
|
358 assert(Server && "moved-from!");
|
|
359 if (Replied.exchange(true)) {
|
|
360 elog("Replied twice to message {0}({1})", Method, ID);
|
|
361 assert(false && "must reply to each call only once!");
|
|
362 return;
|
|
363 }
|
|
364 auto Duration = std::chrono::steady_clock::now() - Start;
|
|
365 if (Reply) {
|
|
366 log("--> reply:{0}({1}) {2:ms}", Method, ID, Duration);
|
|
367 if (TraceArgs)
|
|
368 (*TraceArgs)["Reply"] = *Reply;
|
|
369 std::lock_guard<std::mutex> Lock(Server->TranspWriter);
|
|
370 Server->Transp.reply(std::move(ID), std::move(Reply));
|
|
371 } else {
|
|
372 llvm::Error Err = Reply.takeError();
|
|
373 log("--> reply:{0}({1}) {2:ms}, error: {3}", Method, ID, Duration, Err);
|
|
374 if (TraceArgs)
|
|
375 (*TraceArgs)["Error"] = llvm::to_string(Err);
|
|
376 std::lock_guard<std::mutex> Lock(Server->TranspWriter);
|
|
377 Server->Transp.reply(std::move(ID), std::move(Err));
|
|
378 }
|
|
379 }
|
|
380 };
|
|
381
|
|
382 llvm::StringMap<std::function<void(llvm::json::Value)>> Notifications;
|
|
383 llvm::StringMap<std::function<void(llvm::json::Value, ReplyOnce)>> Calls;
|
|
384
|
|
385 // Method calls may be cancelled by ID, so keep track of their state.
|
|
386 // This needs a mutex: handlers may finish on a different thread, and that's
|
|
387 // when we clean up entries in the map.
|
|
388 mutable std::mutex RequestCancelersMutex;
|
|
389 llvm::StringMap<std::pair<Canceler, /*Cookie*/ unsigned>> RequestCancelers;
|
|
390 unsigned NextRequestCookie = 0; // To disambiguate reused IDs, see below.
|
|
391 void onCancel(const llvm::json::Value &Params) {
|
|
392 const llvm::json::Value *ID = nullptr;
|
|
393 if (auto *O = Params.getAsObject())
|
|
394 ID = O->get("id");
|
|
395 if (!ID) {
|
|
396 elog("Bad cancellation request: {0}", Params);
|
|
397 return;
|
|
398 }
|
|
399 auto StrID = llvm::to_string(*ID);
|
|
400 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
|
|
401 auto It = RequestCancelers.find(StrID);
|
|
402 if (It != RequestCancelers.end())
|
|
403 It->second.first(); // Invoke the canceler.
|
|
404 }
|
|
405
|
|
406 Context handlerContext() const {
|
|
407 return Context::current().derive(
|
|
408 kCurrentOffsetEncoding,
|
|
409 Server.NegotiatedOffsetEncoding.getValueOr(OffsetEncoding::UTF16));
|
|
410 }
|
|
411
|
|
412 // We run cancelable requests in a context that does two things:
|
|
413 // - allows cancellation using RequestCancelers[ID]
|
|
414 // - cleans up the entry in RequestCancelers when it's no longer needed
|
|
415 // If a client reuses an ID, the last wins and the first cannot be canceled.
|
|
416 Context cancelableRequestContext(const llvm::json::Value &ID) {
|
173
|
417 auto Task = cancelableTask(
|
|
418 /*Reason=*/static_cast<int>(ErrorCode::RequestCancelled));
|
150
|
419 auto StrID = llvm::to_string(ID); // JSON-serialize ID for map key.
|
|
420 auto Cookie = NextRequestCookie++; // No lock, only called on main thread.
|
|
421 {
|
|
422 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
|
|
423 RequestCancelers[StrID] = {std::move(Task.second), Cookie};
|
|
424 }
|
|
425 // When the request ends, we can clean up the entry we just added.
|
|
426 // The cookie lets us check that it hasn't been overwritten due to ID
|
|
427 // reuse.
|
|
428 return Task.first.derive(llvm::make_scope_exit([this, StrID, Cookie] {
|
|
429 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
|
|
430 auto It = RequestCancelers.find(StrID);
|
|
431 if (It != RequestCancelers.end() && It->second.second == Cookie)
|
|
432 RequestCancelers.erase(It);
|
|
433 }));
|
|
434 }
|
|
435
|
|
436 // The maximum number of callbacks held in clangd.
|
|
437 //
|
|
438 // We bound the maximum size to the pending map to prevent memory leakage
|
|
439 // for cases where LSP clients don't reply for the request.
|
|
440 // This has to go after RequestCancellers and RequestCancellersMutex since it
|
|
441 // can contain a callback that has a cancelable context.
|
|
442 static constexpr int MaxReplayCallbacks = 100;
|
|
443 mutable std::mutex CallMutex;
|
|
444 int NextCallID = 0; /* GUARDED_BY(CallMutex) */
|
|
445 std::deque<std::pair</*RequestID*/ int,
|
|
446 /*ReplyHandler*/ Callback<llvm::json::Value>>>
|
|
447 ReplyCallbacks; /* GUARDED_BY(CallMutex) */
|
|
448
|
|
449 ClangdLSPServer &Server;
|
|
450 };
|
|
451 constexpr int ClangdLSPServer::MessageHandler::MaxReplayCallbacks;
|
|
452
|
|
453 // call(), notify(), and reply() wrap the Transport, adding logging and locking.
|
|
454 void ClangdLSPServer::callRaw(StringRef Method, llvm::json::Value Params,
|
|
455 Callback<llvm::json::Value> CB) {
|
|
456 auto ID = MsgHandler->bindReply(std::move(CB));
|
|
457 log("--> {0}({1})", Method, ID);
|
|
458 std::lock_guard<std::mutex> Lock(TranspWriter);
|
|
459 Transp.call(Method, std::move(Params), ID);
|
|
460 }
|
|
461
|
|
462 void ClangdLSPServer::notify(llvm::StringRef Method, llvm::json::Value Params) {
|
|
463 log("--> {0}", Method);
|
|
464 std::lock_guard<std::mutex> Lock(TranspWriter);
|
|
465 Transp.notify(Method, std::move(Params));
|
|
466 }
|
|
467
|
173
|
468 static std::vector<llvm::StringRef> semanticTokenTypes() {
|
|
469 std::vector<llvm::StringRef> Types;
|
|
470 for (unsigned I = 0; I <= static_cast<unsigned>(HighlightingKind::LastKind);
|
|
471 ++I)
|
|
472 Types.push_back(toSemanticTokenType(static_cast<HighlightingKind>(I)));
|
|
473 return Types;
|
|
474 }
|
|
475
|
150
|
476 void ClangdLSPServer::onInitialize(const InitializeParams &Params,
|
|
477 Callback<llvm::json::Value> Reply) {
|
|
478 // Determine character encoding first as it affects constructed ClangdServer.
|
|
479 if (Params.capabilities.offsetEncoding && !NegotiatedOffsetEncoding) {
|
|
480 NegotiatedOffsetEncoding = OffsetEncoding::UTF16; // fallback
|
|
481 for (OffsetEncoding Supported : *Params.capabilities.offsetEncoding)
|
|
482 if (Supported != OffsetEncoding::UnsupportedEncoding) {
|
|
483 NegotiatedOffsetEncoding = Supported;
|
|
484 break;
|
|
485 }
|
|
486 }
|
|
487
|
173
|
488 ClangdServerOpts.TheiaSemanticHighlighting =
|
|
489 Params.capabilities.TheiaSemanticHighlighting;
|
|
490 if (Params.capabilities.TheiaSemanticHighlighting &&
|
|
491 Params.capabilities.SemanticTokens) {
|
|
492 log("Client supports legacy semanticHighlights notification and standard "
|
|
493 "semanticTokens request, choosing the latter (no notifications).");
|
|
494 ClangdServerOpts.TheiaSemanticHighlighting = false;
|
|
495 }
|
|
496
|
150
|
497 if (Params.rootUri && *Params.rootUri)
|
|
498 ClangdServerOpts.WorkspaceRoot = std::string(Params.rootUri->file());
|
|
499 else if (Params.rootPath && !Params.rootPath->empty())
|
|
500 ClangdServerOpts.WorkspaceRoot = *Params.rootPath;
|
|
501 if (Server)
|
|
502 return Reply(llvm::make_error<LSPError>("server already initialized",
|
|
503 ErrorCode::InvalidRequest));
|
|
504 if (const auto &Dir = Params.initializationOptions.compilationDatabasePath)
|
|
505 CompileCommandsDir = Dir;
|
|
506 if (UseDirBasedCDB) {
|
|
507 BaseCDB = std::make_unique<DirectoryBasedGlobalCompilationDatabase>(
|
|
508 CompileCommandsDir);
|
|
509 BaseCDB = getQueryDriverDatabase(
|
|
510 llvm::makeArrayRef(ClangdServerOpts.QueryDriverGlobs),
|
|
511 std::move(BaseCDB));
|
|
512 }
|
|
513 auto Mangler = CommandMangler::detect();
|
|
514 if (ClangdServerOpts.ResourceDir)
|
|
515 Mangler.ResourceDir = *ClangdServerOpts.ResourceDir;
|
|
516 CDB.emplace(BaseCDB.get(), Params.initializationOptions.fallbackFlags,
|
|
517 tooling::ArgumentsAdjuster(Mangler));
|
|
518 {
|
|
519 // Switch caller's context with LSPServer's background context. Since we
|
|
520 // rather want to propagate information from LSPServer's context into the
|
|
521 // Server, CDB, etc.
|
|
522 WithContext MainContext(BackgroundContext.clone());
|
|
523 llvm::Optional<WithContextValue> WithOffsetEncoding;
|
|
524 if (NegotiatedOffsetEncoding)
|
|
525 WithOffsetEncoding.emplace(kCurrentOffsetEncoding,
|
|
526 *NegotiatedOffsetEncoding);
|
|
527 Server.emplace(*CDB, FSProvider, ClangdServerOpts,
|
|
528 static_cast<ClangdServer::Callbacks *>(this));
|
|
529 }
|
|
530 applyConfiguration(Params.initializationOptions.ConfigSettings);
|
|
531
|
|
532 CCOpts.EnableSnippets = Params.capabilities.CompletionSnippets;
|
|
533 CCOpts.IncludeFixIts = Params.capabilities.CompletionFixes;
|
|
534 if (!CCOpts.BundleOverloads.hasValue())
|
|
535 CCOpts.BundleOverloads = Params.capabilities.HasSignatureHelp;
|
173
|
536 CCOpts.DocumentationFormat =
|
|
537 Params.capabilities.CompletionDocumentationFormat;
|
150
|
538 DiagOpts.EmbedFixesInDiagnostics = Params.capabilities.DiagnosticFixes;
|
|
539 DiagOpts.SendDiagnosticCategory = Params.capabilities.DiagnosticCategory;
|
|
540 DiagOpts.EmitRelatedLocations =
|
|
541 Params.capabilities.DiagnosticRelatedInformation;
|
|
542 if (Params.capabilities.WorkspaceSymbolKinds)
|
|
543 SupportedSymbolKinds |= *Params.capabilities.WorkspaceSymbolKinds;
|
|
544 if (Params.capabilities.CompletionItemKinds)
|
|
545 SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds;
|
|
546 SupportsCodeAction = Params.capabilities.CodeActionStructure;
|
|
547 SupportsHierarchicalDocumentSymbol =
|
|
548 Params.capabilities.HierarchicalDocumentSymbol;
|
|
549 SupportFileStatus = Params.initializationOptions.FileStatus;
|
|
550 HoverContentFormat = Params.capabilities.HoverContentFormat;
|
|
551 SupportsOffsetsInSignatureHelp = Params.capabilities.OffsetsInSignatureHelp;
|
|
552 if (Params.capabilities.WorkDoneProgress)
|
|
553 BackgroundIndexProgressState = BackgroundIndexProgress::Empty;
|
|
554 BackgroundIndexSkipCreate = Params.capabilities.ImplicitProgressCreation;
|
|
555
|
|
556 // Per LSP, renameProvider can be either boolean or RenameOptions.
|
|
557 // RenameOptions will be specified if the client states it supports prepare.
|
|
558 llvm::json::Value RenameProvider =
|
|
559 llvm::json::Object{{"prepareProvider", true}};
|
|
560 if (!Params.capabilities.RenamePrepareSupport) // Only boolean allowed per LSP
|
|
561 RenameProvider = true;
|
|
562
|
|
563 // Per LSP, codeActionProvide can be either boolean or CodeActionOptions.
|
|
564 // CodeActionOptions is only valid if the client supports action literal
|
|
565 // via textDocument.codeAction.codeActionLiteralSupport.
|
|
566 llvm::json::Value CodeActionProvider = true;
|
|
567 if (Params.capabilities.CodeActionStructure)
|
|
568 CodeActionProvider = llvm::json::Object{
|
|
569 {"codeActionKinds",
|
|
570 {CodeAction::QUICKFIX_KIND, CodeAction::REFACTOR_KIND,
|
|
571 CodeAction::INFO_KIND}}};
|
|
572
|
|
573 llvm::json::Object Result{
|
173
|
574 {{"serverInfo",
|
|
575 llvm::json::Object{{"name", "clangd"},
|
|
576 {"version", getClangToolFullVersion("clangd")}}},
|
|
577 {"capabilities",
|
150
|
578 llvm::json::Object{
|
173
|
579 {"textDocumentSync",
|
|
580 llvm::json::Object{
|
|
581 {"openClose", true},
|
|
582 {"change", (int)TextDocumentSyncKind::Incremental},
|
|
583 {"save", true},
|
|
584 }},
|
150
|
585 {"documentFormattingProvider", true},
|
|
586 {"documentRangeFormattingProvider", true},
|
|
587 {"documentOnTypeFormattingProvider",
|
|
588 llvm::json::Object{
|
|
589 {"firstTriggerCharacter", "\n"},
|
|
590 {"moreTriggerCharacter", {}},
|
|
591 }},
|
|
592 {"codeActionProvider", std::move(CodeActionProvider)},
|
|
593 {"completionProvider",
|
|
594 llvm::json::Object{
|
173
|
595 {"allCommitCharacters", " \t()[]{}<>:;,+-/*%^&#?.=\"'|"},
|
150
|
596 {"resolveProvider", false},
|
173
|
597 // We do extra checks, e.g. that > is part of ->.
|
|
598 {"triggerCharacters", {".", "<", ">", ":", "\"", "/"}},
|
|
599 }},
|
|
600 {"semanticTokensProvider",
|
|
601 llvm::json::Object{
|
|
602 {"documentProvider", llvm::json::Object{{"edits", true}}},
|
|
603 {"rangeProvider", false},
|
|
604 {"legend",
|
|
605 llvm::json::Object{{"tokenTypes", semanticTokenTypes()},
|
|
606 {"tokenModifiers", llvm::json::Array()}}},
|
150
|
607 }},
|
|
608 {"signatureHelpProvider",
|
|
609 llvm::json::Object{
|
|
610 {"triggerCharacters", {"(", ","}},
|
|
611 }},
|
|
612 {"declarationProvider", true},
|
|
613 {"definitionProvider", true},
|
|
614 {"documentHighlightProvider", true},
|
|
615 {"documentLinkProvider",
|
|
616 llvm::json::Object{
|
|
617 {"resolveProvider", false},
|
|
618 }},
|
|
619 {"hoverProvider", true},
|
|
620 {"renameProvider", std::move(RenameProvider)},
|
|
621 {"selectionRangeProvider", true},
|
|
622 {"documentSymbolProvider", true},
|
|
623 {"workspaceSymbolProvider", true},
|
|
624 {"referencesProvider", true},
|
|
625 {"executeCommandProvider",
|
|
626 llvm::json::Object{
|
|
627 {"commands",
|
|
628 {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
|
|
629 ExecuteCommandParams::CLANGD_APPLY_TWEAK}},
|
|
630 }},
|
|
631 {"typeHierarchyProvider", true},
|
|
632 }}}};
|
|
633 if (NegotiatedOffsetEncoding)
|
|
634 Result["offsetEncoding"] = *NegotiatedOffsetEncoding;
|
173
|
635 if (ClangdServerOpts.TheiaSemanticHighlighting)
|
150
|
636 Result.getObject("capabilities")
|
|
637 ->insert(
|
|
638 {"semanticHighlighting",
|
|
639 llvm::json::Object{{"scopes", buildHighlightScopeLookupTable()}}});
|
|
640 Reply(std::move(Result));
|
|
641 }
|
|
642
|
173
|
643 void ClangdLSPServer::onInitialized(const InitializedParams &Params) {}
|
|
644
|
150
|
645 void ClangdLSPServer::onShutdown(const ShutdownParams &Params,
|
|
646 Callback<std::nullptr_t> Reply) {
|
|
647 // Do essentially nothing, just say we're ready to exit.
|
|
648 ShutdownRequestReceived = true;
|
|
649 Reply(nullptr);
|
|
650 }
|
|
651
|
|
652 // sync is a clangd extension: it blocks until all background work completes.
|
|
653 // It blocks the calling thread, so no messages are processed until it returns!
|
|
654 void ClangdLSPServer::onSync(const NoParams &Params,
|
|
655 Callback<std::nullptr_t> Reply) {
|
|
656 if (Server->blockUntilIdleForTest(/*TimeoutSeconds=*/60))
|
|
657 Reply(nullptr);
|
|
658 else
|
|
659 Reply(llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
660 "Not idle after a minute"));
|
|
661 }
|
|
662
|
|
663 void ClangdLSPServer::onDocumentDidOpen(
|
|
664 const DidOpenTextDocumentParams &Params) {
|
|
665 PathRef File = Params.textDocument.uri.file();
|
|
666
|
|
667 const std::string &Contents = Params.textDocument.text;
|
|
668
|
173
|
669 auto Version = DraftMgr.addDraft(File, Params.textDocument.version, Contents);
|
|
670 Server->addDocument(File, Contents, encodeVersion(Version),
|
|
671 WantDiagnostics::Yes);
|
150
|
672 }
|
|
673
|
|
674 void ClangdLSPServer::onDocumentDidChange(
|
|
675 const DidChangeTextDocumentParams &Params) {
|
|
676 auto WantDiags = WantDiagnostics::Auto;
|
|
677 if (Params.wantDiagnostics.hasValue())
|
|
678 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
|
|
679 : WantDiagnostics::No;
|
|
680
|
|
681 PathRef File = Params.textDocument.uri.file();
|
173
|
682 llvm::Expected<DraftStore::Draft> Draft = DraftMgr.updateDraft(
|
|
683 File, Params.textDocument.version, Params.contentChanges);
|
|
684 if (!Draft) {
|
150
|
685 // If this fails, we are most likely going to be not in sync anymore with
|
|
686 // the client. It is better to remove the draft and let further operations
|
|
687 // fail rather than giving wrong results.
|
|
688 DraftMgr.removeDraft(File);
|
|
689 Server->removeDocument(File);
|
173
|
690 elog("Failed to update {0}: {1}", File, Draft.takeError());
|
150
|
691 return;
|
|
692 }
|
|
693
|
173
|
694 Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version),
|
|
695 WantDiags, Params.forceRebuild);
|
|
696 }
|
|
697
|
|
698 void ClangdLSPServer::onDocumentDidSave(
|
|
699 const DidSaveTextDocumentParams &Params) {
|
|
700 reparseOpenFilesIfNeeded([](llvm::StringRef) { return true; });
|
150
|
701 }
|
|
702
|
|
703 void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
|
173
|
704 // We could also reparse all open files here. However:
|
|
705 // - this could be frequent, and revalidating all the preambles isn't free
|
|
706 // - this is useful e.g. when switching git branches, but we're likely to see
|
|
707 // fresh headers but still have the old-branch main-file content
|
150
|
708 Server->onFileEvent(Params);
|
|
709 }
|
|
710
|
|
711 void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params,
|
|
712 Callback<llvm::json::Value> Reply) {
|
|
713 auto ApplyEdit = [this](WorkspaceEdit WE, std::string SuccessMessage,
|
|
714 decltype(Reply) Reply) {
|
|
715 ApplyWorkspaceEditParams Edit;
|
|
716 Edit.edit = std::move(WE);
|
|
717 call<ApplyWorkspaceEditResponse>(
|
|
718 "workspace/applyEdit", std::move(Edit),
|
|
719 [Reply = std::move(Reply), SuccessMessage = std::move(SuccessMessage)](
|
|
720 llvm::Expected<ApplyWorkspaceEditResponse> Response) mutable {
|
|
721 if (!Response)
|
|
722 return Reply(Response.takeError());
|
|
723 if (!Response->applied) {
|
|
724 std::string Reason = Response->failureReason
|
|
725 ? *Response->failureReason
|
|
726 : "unknown reason";
|
|
727 return Reply(llvm::createStringError(
|
|
728 llvm::inconvertibleErrorCode(),
|
|
729 ("edits were not applied: " + Reason).c_str()));
|
|
730 }
|
|
731 return Reply(SuccessMessage);
|
|
732 });
|
|
733 };
|
|
734
|
|
735 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
|
|
736 Params.workspaceEdit) {
|
|
737 // The flow for "apply-fix" :
|
|
738 // 1. We publish a diagnostic, including fixits
|
|
739 // 2. The user clicks on the diagnostic, the editor asks us for code actions
|
|
740 // 3. We send code actions, with the fixit embedded as context
|
|
741 // 4. The user selects the fixit, the editor asks us to apply it
|
|
742 // 5. We unwrap the changes and send them back to the editor
|
|
743 // 6. The editor applies the changes (applyEdit), and sends us a reply
|
|
744 // 7. We unwrap the reply and send a reply to the editor.
|
|
745 ApplyEdit(*Params.workspaceEdit, "Fix applied.", std::move(Reply));
|
|
746 } else if (Params.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK &&
|
|
747 Params.tweakArgs) {
|
|
748 auto Code = DraftMgr.getDraft(Params.tweakArgs->file.file());
|
|
749 if (!Code)
|
|
750 return Reply(llvm::createStringError(
|
|
751 llvm::inconvertibleErrorCode(),
|
|
752 "trying to apply a code action for a non-added file"));
|
|
753
|
|
754 auto Action = [this, ApplyEdit, Reply = std::move(Reply),
|
|
755 File = Params.tweakArgs->file, Code = std::move(*Code)](
|
|
756 llvm::Expected<Tweak::Effect> R) mutable {
|
|
757 if (!R)
|
|
758 return Reply(R.takeError());
|
|
759
|
|
760 assert(R->ShowMessage ||
|
|
761 (!R->ApplyEdits.empty() && "tweak has no effect"));
|
|
762
|
|
763 if (R->ShowMessage) {
|
|
764 ShowMessageParams Msg;
|
|
765 Msg.message = *R->ShowMessage;
|
|
766 Msg.type = MessageType::Info;
|
|
767 notify("window/showMessage", Msg);
|
|
768 }
|
|
769 // When no edit is specified, make sure we Reply().
|
|
770 if (R->ApplyEdits.empty())
|
|
771 return Reply("Tweak applied.");
|
|
772
|
|
773 if (auto Err = validateEdits(DraftMgr, R->ApplyEdits))
|
|
774 return Reply(std::move(Err));
|
|
775
|
|
776 WorkspaceEdit WE;
|
|
777 WE.changes.emplace();
|
|
778 for (const auto &It : R->ApplyEdits) {
|
|
779 (*WE.changes)[URI::createFile(It.first()).toString()] =
|
|
780 It.second.asTextEdits();
|
|
781 }
|
|
782 // ApplyEdit will take care of calling Reply().
|
|
783 return ApplyEdit(std::move(WE), "Tweak applied.", std::move(Reply));
|
|
784 };
|
|
785 Server->applyTweak(Params.tweakArgs->file.file(),
|
|
786 Params.tweakArgs->selection, Params.tweakArgs->tweakID,
|
|
787 std::move(Action));
|
|
788 } else {
|
|
789 // We should not get here because ExecuteCommandParams would not have
|
|
790 // parsed in the first place and this handler should not be called. But if
|
|
791 // more commands are added, this will be here has a safe guard.
|
|
792 Reply(llvm::make_error<LSPError>(
|
|
793 llvm::formatv("Unsupported command \"{0}\".", Params.command).str(),
|
|
794 ErrorCode::InvalidParams));
|
|
795 }
|
|
796 }
|
|
797
|
|
798 void ClangdLSPServer::onWorkspaceSymbol(
|
|
799 const WorkspaceSymbolParams &Params,
|
|
800 Callback<std::vector<SymbolInformation>> Reply) {
|
|
801 Server->workspaceSymbols(
|
|
802 Params.query, CCOpts.Limit,
|
|
803 [Reply = std::move(Reply),
|
|
804 this](llvm::Expected<std::vector<SymbolInformation>> Items) mutable {
|
|
805 if (!Items)
|
|
806 return Reply(Items.takeError());
|
|
807 for (auto &Sym : *Items)
|
|
808 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
|
|
809
|
|
810 Reply(std::move(*Items));
|
|
811 });
|
|
812 }
|
|
813
|
|
814 void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params,
|
|
815 Callback<llvm::Optional<Range>> Reply) {
|
|
816 Server->prepareRename(Params.textDocument.uri.file(), Params.position,
|
173
|
817 RenameOpts, std::move(Reply));
|
150
|
818 }
|
|
819
|
|
820 void ClangdLSPServer::onRename(const RenameParams &Params,
|
|
821 Callback<WorkspaceEdit> Reply) {
|
|
822 Path File = std::string(Params.textDocument.uri.file());
|
173
|
823 if (!DraftMgr.getDraft(File))
|
150
|
824 return Reply(llvm::make_error<LSPError>(
|
|
825 "onRename called for non-added file", ErrorCode::InvalidParams));
|
|
826 Server->rename(
|
173
|
827 File, Params.position, Params.newName, RenameOpts,
|
150
|
828 [File, Params, Reply = std::move(Reply),
|
|
829 this](llvm::Expected<FileEdits> Edits) mutable {
|
|
830 if (!Edits)
|
|
831 return Reply(Edits.takeError());
|
|
832 if (auto Err = validateEdits(DraftMgr, *Edits))
|
|
833 return Reply(std::move(Err));
|
|
834 WorkspaceEdit Result;
|
|
835 Result.changes.emplace();
|
|
836 for (const auto &Rep : *Edits) {
|
|
837 (*Result.changes)[URI::createFile(Rep.first()).toString()] =
|
|
838 Rep.second.asTextEdits();
|
|
839 }
|
|
840 Reply(Result);
|
|
841 });
|
|
842 }
|
|
843
|
|
844 void ClangdLSPServer::onDocumentDidClose(
|
|
845 const DidCloseTextDocumentParams &Params) {
|
|
846 PathRef File = Params.textDocument.uri.file();
|
|
847 DraftMgr.removeDraft(File);
|
|
848 Server->removeDocument(File);
|
|
849
|
|
850 {
|
|
851 std::lock_guard<std::mutex> Lock(FixItsMutex);
|
|
852 FixItsMap.erase(File);
|
|
853 }
|
|
854 {
|
|
855 std::lock_guard<std::mutex> HLock(HighlightingsMutex);
|
|
856 FileToHighlightings.erase(File);
|
|
857 }
|
173
|
858 {
|
|
859 std::lock_guard<std::mutex> HLock(SemanticTokensMutex);
|
|
860 LastSemanticTokens.erase(File);
|
|
861 }
|
150
|
862 // clangd will not send updates for this file anymore, so we empty out the
|
|
863 // list of diagnostics shown on the client (e.g. in the "Problems" pane of
|
|
864 // VSCode). Note that this cannot race with actual diagnostics responses
|
|
865 // because removeDocument() guarantees no diagnostic callbacks will be
|
|
866 // executed after it returns.
|
173
|
867 PublishDiagnosticsParams Notification;
|
|
868 Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File);
|
|
869 publishDiagnostics(Notification);
|
150
|
870 }
|
|
871
|
|
872 void ClangdLSPServer::onDocumentOnTypeFormatting(
|
|
873 const DocumentOnTypeFormattingParams &Params,
|
|
874 Callback<std::vector<TextEdit>> Reply) {
|
|
875 auto File = Params.textDocument.uri.file();
|
|
876 auto Code = DraftMgr.getDraft(File);
|
|
877 if (!Code)
|
|
878 return Reply(llvm::make_error<LSPError>(
|
|
879 "onDocumentOnTypeFormatting called for non-added file",
|
|
880 ErrorCode::InvalidParams));
|
|
881
|
173
|
882 Reply(Server->formatOnType(Code->Contents, File, Params.position, Params.ch));
|
150
|
883 }
|
|
884
|
|
885 void ClangdLSPServer::onDocumentRangeFormatting(
|
|
886 const DocumentRangeFormattingParams &Params,
|
|
887 Callback<std::vector<TextEdit>> Reply) {
|
|
888 auto File = Params.textDocument.uri.file();
|
|
889 auto Code = DraftMgr.getDraft(File);
|
|
890 if (!Code)
|
|
891 return Reply(llvm::make_error<LSPError>(
|
|
892 "onDocumentRangeFormatting called for non-added file",
|
|
893 ErrorCode::InvalidParams));
|
|
894
|
173
|
895 auto ReplacementsOrError =
|
|
896 Server->formatRange(Code->Contents, File, Params.range);
|
150
|
897 if (ReplacementsOrError)
|
173
|
898 Reply(replacementsToEdits(Code->Contents, ReplacementsOrError.get()));
|
150
|
899 else
|
|
900 Reply(ReplacementsOrError.takeError());
|
|
901 }
|
|
902
|
|
903 void ClangdLSPServer::onDocumentFormatting(
|
|
904 const DocumentFormattingParams &Params,
|
|
905 Callback<std::vector<TextEdit>> Reply) {
|
|
906 auto File = Params.textDocument.uri.file();
|
|
907 auto Code = DraftMgr.getDraft(File);
|
|
908 if (!Code)
|
|
909 return Reply(llvm::make_error<LSPError>(
|
|
910 "onDocumentFormatting called for non-added file",
|
|
911 ErrorCode::InvalidParams));
|
|
912
|
173
|
913 auto ReplacementsOrError = Server->formatFile(Code->Contents, File);
|
150
|
914 if (ReplacementsOrError)
|
173
|
915 Reply(replacementsToEdits(Code->Contents, ReplacementsOrError.get()));
|
150
|
916 else
|
|
917 Reply(ReplacementsOrError.takeError());
|
|
918 }
|
|
919
|
|
920 /// The functions constructs a flattened view of the DocumentSymbol hierarchy.
|
|
921 /// Used by the clients that do not support the hierarchical view.
|
|
922 static std::vector<SymbolInformation>
|
|
923 flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols,
|
|
924 const URIForFile &FileURI) {
|
|
925
|
|
926 std::vector<SymbolInformation> Results;
|
|
927 std::function<void(const DocumentSymbol &, llvm::StringRef)> Process =
|
|
928 [&](const DocumentSymbol &S, llvm::Optional<llvm::StringRef> ParentName) {
|
|
929 SymbolInformation SI;
|
|
930 SI.containerName = std::string(ParentName ? "" : *ParentName);
|
|
931 SI.name = S.name;
|
|
932 SI.kind = S.kind;
|
|
933 SI.location.range = S.range;
|
|
934 SI.location.uri = FileURI;
|
|
935
|
|
936 Results.push_back(std::move(SI));
|
|
937 std::string FullName =
|
|
938 !ParentName ? S.name : (ParentName->str() + "::" + S.name);
|
|
939 for (auto &C : S.children)
|
|
940 Process(C, /*ParentName=*/FullName);
|
|
941 };
|
|
942 for (auto &S : Symbols)
|
|
943 Process(S, /*ParentName=*/"");
|
|
944 return Results;
|
|
945 }
|
|
946
|
|
947 void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params,
|
|
948 Callback<llvm::json::Value> Reply) {
|
|
949 URIForFile FileURI = Params.textDocument.uri;
|
|
950 Server->documentSymbols(
|
|
951 Params.textDocument.uri.file(),
|
|
952 [this, FileURI, Reply = std::move(Reply)](
|
|
953 llvm::Expected<std::vector<DocumentSymbol>> Items) mutable {
|
|
954 if (!Items)
|
|
955 return Reply(Items.takeError());
|
|
956 adjustSymbolKinds(*Items, SupportedSymbolKinds);
|
|
957 if (SupportsHierarchicalDocumentSymbol)
|
|
958 return Reply(std::move(*Items));
|
|
959 else
|
|
960 return Reply(flattenSymbolHierarchy(*Items, FileURI));
|
|
961 });
|
|
962 }
|
|
963
|
|
964 static llvm::Optional<Command> asCommand(const CodeAction &Action) {
|
|
965 Command Cmd;
|
|
966 if (Action.command && Action.edit)
|
|
967 return None; // Not representable. (We never emit these anyway).
|
|
968 if (Action.command) {
|
|
969 Cmd = *Action.command;
|
|
970 } else if (Action.edit) {
|
|
971 Cmd.command = std::string(Command::CLANGD_APPLY_FIX_COMMAND);
|
|
972 Cmd.workspaceEdit = *Action.edit;
|
|
973 } else {
|
|
974 return None;
|
|
975 }
|
|
976 Cmd.title = Action.title;
|
|
977 if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND)
|
|
978 Cmd.title = "Apply fix: " + Cmd.title;
|
|
979 return Cmd;
|
|
980 }
|
|
981
|
|
982 void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
|
|
983 Callback<llvm::json::Value> Reply) {
|
|
984 URIForFile File = Params.textDocument.uri;
|
|
985 auto Code = DraftMgr.getDraft(File.file());
|
|
986 if (!Code)
|
|
987 return Reply(llvm::make_error<LSPError>(
|
|
988 "onCodeAction called for non-added file", ErrorCode::InvalidParams));
|
|
989 // We provide a code action for Fixes on the specified diagnostics.
|
|
990 std::vector<CodeAction> FixIts;
|
|
991 for (const Diagnostic &D : Params.context.diagnostics) {
|
|
992 for (auto &F : getFixes(File.file(), D)) {
|
|
993 FixIts.push_back(toCodeAction(F, Params.textDocument.uri));
|
|
994 FixIts.back().diagnostics = {D};
|
|
995 }
|
|
996 }
|
|
997
|
|
998 // Now enumerate the semantic code actions.
|
|
999 auto ConsumeActions =
|
|
1000 [Reply = std::move(Reply), File, Code = std::move(*Code),
|
|
1001 Selection = Params.range, FixIts = std::move(FixIts), this](
|
|
1002 llvm::Expected<std::vector<ClangdServer::TweakRef>> Tweaks) mutable {
|
|
1003 if (!Tweaks)
|
|
1004 return Reply(Tweaks.takeError());
|
|
1005
|
|
1006 std::vector<CodeAction> Actions = std::move(FixIts);
|
|
1007 Actions.reserve(Actions.size() + Tweaks->size());
|
|
1008 for (const auto &T : *Tweaks)
|
|
1009 Actions.push_back(toCodeAction(T, File, Selection));
|
|
1010
|
|
1011 if (SupportsCodeAction)
|
|
1012 return Reply(llvm::json::Array(Actions));
|
|
1013 std::vector<Command> Commands;
|
|
1014 for (const auto &Action : Actions) {
|
|
1015 if (auto Command = asCommand(Action))
|
|
1016 Commands.push_back(std::move(*Command));
|
|
1017 }
|
|
1018 return Reply(llvm::json::Array(Commands));
|
|
1019 };
|
|
1020
|
|
1021 Server->enumerateTweaks(File.file(), Params.range, std::move(ConsumeActions));
|
|
1022 }
|
|
1023
|
|
1024 void ClangdLSPServer::onCompletion(const CompletionParams &Params,
|
|
1025 Callback<CompletionList> Reply) {
|
|
1026 if (!shouldRunCompletion(Params)) {
|
|
1027 // Clients sometimes auto-trigger completions in undesired places (e.g.
|
|
1028 // 'a >^ '), we return empty results in those cases.
|
|
1029 vlog("ignored auto-triggered completion, preceding char did not match");
|
|
1030 return Reply(CompletionList());
|
|
1031 }
|
|
1032 Server->codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
|
|
1033 [Reply = std::move(Reply),
|
|
1034 this](llvm::Expected<CodeCompleteResult> List) mutable {
|
|
1035 if (!List)
|
|
1036 return Reply(List.takeError());
|
|
1037 CompletionList LSPList;
|
|
1038 LSPList.isIncomplete = List->HasMore;
|
|
1039 for (const auto &R : List->Completions) {
|
|
1040 CompletionItem C = R.render(CCOpts);
|
|
1041 C.kind = adjustKindToCapability(
|
|
1042 C.kind, SupportedCompletionItemKinds);
|
|
1043 LSPList.items.push_back(std::move(C));
|
|
1044 }
|
|
1045 return Reply(std::move(LSPList));
|
|
1046 });
|
|
1047 }
|
|
1048
|
|
1049 void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams &Params,
|
|
1050 Callback<SignatureHelp> Reply) {
|
|
1051 Server->signatureHelp(Params.textDocument.uri.file(), Params.position,
|
|
1052 [Reply = std::move(Reply), this](
|
|
1053 llvm::Expected<SignatureHelp> Signature) mutable {
|
|
1054 if (!Signature)
|
|
1055 return Reply(Signature.takeError());
|
|
1056 if (SupportsOffsetsInSignatureHelp)
|
|
1057 return Reply(std::move(*Signature));
|
|
1058 // Strip out the offsets from signature help for
|
|
1059 // clients that only support string labels.
|
|
1060 for (auto &SigInfo : Signature->signatures) {
|
|
1061 for (auto &Param : SigInfo.parameters)
|
|
1062 Param.labelOffsets.reset();
|
|
1063 }
|
|
1064 return Reply(std::move(*Signature));
|
|
1065 });
|
|
1066 }
|
|
1067
|
|
1068 // Go to definition has a toggle function: if def and decl are distinct, then
|
|
1069 // the first press gives you the def, the second gives you the matching def.
|
|
1070 // getToggle() returns the counterpart location that under the cursor.
|
|
1071 //
|
|
1072 // We return the toggled location alone (ignoring other symbols) to encourage
|
|
1073 // editors to "bounce" quickly between locations, without showing a menu.
|
|
1074 static Location *getToggle(const TextDocumentPositionParams &Point,
|
|
1075 LocatedSymbol &Sym) {
|
|
1076 // Toggle only makes sense with two distinct locations.
|
|
1077 if (!Sym.Definition || *Sym.Definition == Sym.PreferredDeclaration)
|
|
1078 return nullptr;
|
|
1079 if (Sym.Definition->uri.file() == Point.textDocument.uri.file() &&
|
|
1080 Sym.Definition->range.contains(Point.position))
|
|
1081 return &Sym.PreferredDeclaration;
|
|
1082 if (Sym.PreferredDeclaration.uri.file() == Point.textDocument.uri.file() &&
|
|
1083 Sym.PreferredDeclaration.range.contains(Point.position))
|
|
1084 return &*Sym.Definition;
|
|
1085 return nullptr;
|
|
1086 }
|
|
1087
|
|
1088 void ClangdLSPServer::onGoToDefinition(const TextDocumentPositionParams &Params,
|
|
1089 Callback<std::vector<Location>> Reply) {
|
|
1090 Server->locateSymbolAt(
|
|
1091 Params.textDocument.uri.file(), Params.position,
|
|
1092 [Params, Reply = std::move(Reply)](
|
|
1093 llvm::Expected<std::vector<LocatedSymbol>> Symbols) mutable {
|
|
1094 if (!Symbols)
|
|
1095 return Reply(Symbols.takeError());
|
|
1096 std::vector<Location> Defs;
|
|
1097 for (auto &S : *Symbols) {
|
|
1098 if (Location *Toggle = getToggle(Params, S))
|
|
1099 return Reply(std::vector<Location>{std::move(*Toggle)});
|
|
1100 Defs.push_back(S.Definition.getValueOr(S.PreferredDeclaration));
|
|
1101 }
|
|
1102 Reply(std::move(Defs));
|
|
1103 });
|
|
1104 }
|
|
1105
|
|
1106 void ClangdLSPServer::onGoToDeclaration(
|
|
1107 const TextDocumentPositionParams &Params,
|
|
1108 Callback<std::vector<Location>> Reply) {
|
|
1109 Server->locateSymbolAt(
|
|
1110 Params.textDocument.uri.file(), Params.position,
|
|
1111 [Params, Reply = std::move(Reply)](
|
|
1112 llvm::Expected<std::vector<LocatedSymbol>> Symbols) mutable {
|
|
1113 if (!Symbols)
|
|
1114 return Reply(Symbols.takeError());
|
|
1115 std::vector<Location> Decls;
|
|
1116 for (auto &S : *Symbols) {
|
|
1117 if (Location *Toggle = getToggle(Params, S))
|
|
1118 return Reply(std::vector<Location>{std::move(*Toggle)});
|
|
1119 Decls.push_back(std::move(S.PreferredDeclaration));
|
|
1120 }
|
|
1121 Reply(std::move(Decls));
|
|
1122 });
|
|
1123 }
|
|
1124
|
|
1125 void ClangdLSPServer::onSwitchSourceHeader(
|
|
1126 const TextDocumentIdentifier &Params,
|
|
1127 Callback<llvm::Optional<URIForFile>> Reply) {
|
|
1128 Server->switchSourceHeader(
|
|
1129 Params.uri.file(),
|
|
1130 [Reply = std::move(Reply),
|
|
1131 Params](llvm::Expected<llvm::Optional<clangd::Path>> Path) mutable {
|
|
1132 if (!Path)
|
|
1133 return Reply(Path.takeError());
|
|
1134 if (*Path)
|
|
1135 return Reply(URIForFile::canonicalize(**Path, Params.uri.file()));
|
|
1136 return Reply(llvm::None);
|
|
1137 });
|
|
1138 }
|
|
1139
|
|
1140 void ClangdLSPServer::onDocumentHighlight(
|
|
1141 const TextDocumentPositionParams &Params,
|
|
1142 Callback<std::vector<DocumentHighlight>> Reply) {
|
|
1143 Server->findDocumentHighlights(Params.textDocument.uri.file(),
|
|
1144 Params.position, std::move(Reply));
|
|
1145 }
|
|
1146
|
|
1147 void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
|
|
1148 Callback<llvm::Optional<Hover>> Reply) {
|
|
1149 Server->findHover(Params.textDocument.uri.file(), Params.position,
|
|
1150 [Reply = std::move(Reply), this](
|
|
1151 llvm::Expected<llvm::Optional<HoverInfo>> H) mutable {
|
|
1152 if (!H)
|
|
1153 return Reply(H.takeError());
|
|
1154 if (!*H)
|
|
1155 return Reply(llvm::None);
|
|
1156
|
|
1157 Hover R;
|
|
1158 R.contents.kind = HoverContentFormat;
|
|
1159 R.range = (*H)->SymRange;
|
|
1160 switch (HoverContentFormat) {
|
|
1161 case MarkupKind::PlainText:
|
|
1162 R.contents.value = (*H)->present().asPlainText();
|
|
1163 return Reply(std::move(R));
|
|
1164 case MarkupKind::Markdown:
|
|
1165 R.contents.value = (*H)->present().asMarkdown();
|
|
1166 return Reply(std::move(R));
|
|
1167 };
|
|
1168 llvm_unreachable("unhandled MarkupKind");
|
|
1169 });
|
|
1170 }
|
|
1171
|
|
1172 void ClangdLSPServer::onTypeHierarchy(
|
|
1173 const TypeHierarchyParams &Params,
|
|
1174 Callback<Optional<TypeHierarchyItem>> Reply) {
|
|
1175 Server->typeHierarchy(Params.textDocument.uri.file(), Params.position,
|
|
1176 Params.resolve, Params.direction, std::move(Reply));
|
|
1177 }
|
|
1178
|
|
1179 void ClangdLSPServer::onResolveTypeHierarchy(
|
|
1180 const ResolveTypeHierarchyItemParams &Params,
|
|
1181 Callback<Optional<TypeHierarchyItem>> Reply) {
|
|
1182 Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction,
|
|
1183 std::move(Reply));
|
|
1184 }
|
|
1185
|
|
1186 void ClangdLSPServer::applyConfiguration(
|
|
1187 const ConfigurationSettings &Settings) {
|
|
1188 // Per-file update to the compilation database.
|
|
1189 llvm::StringSet<> ModifiedFiles;
|
|
1190 for (auto &Entry : Settings.compilationDatabaseChanges) {
|
|
1191 PathRef File = Entry.first;
|
|
1192 auto Old = CDB->getCompileCommand(File);
|
|
1193 auto New =
|
|
1194 tooling::CompileCommand(std::move(Entry.second.workingDirectory), File,
|
|
1195 std::move(Entry.second.compilationCommand),
|
|
1196 /*Output=*/"");
|
|
1197 if (Old != New) {
|
|
1198 CDB->setCompileCommand(File, std::move(New));
|
|
1199 ModifiedFiles.insert(File);
|
|
1200 }
|
|
1201 }
|
|
1202
|
173
|
1203 reparseOpenFilesIfNeeded(
|
|
1204 [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; });
|
150
|
1205 }
|
|
1206
|
173
|
1207 void ClangdLSPServer::publishTheiaSemanticHighlighting(
|
|
1208 const TheiaSemanticHighlightingParams &Params) {
|
150
|
1209 notify("textDocument/semanticHighlighting", Params);
|
|
1210 }
|
|
1211
|
|
1212 void ClangdLSPServer::publishDiagnostics(
|
173
|
1213 const PublishDiagnosticsParams &Params) {
|
|
1214 notify("textDocument/publishDiagnostics", Params);
|
150
|
1215 }
|
|
1216
|
|
1217 // FIXME: This function needs to be properly tested.
|
|
1218 void ClangdLSPServer::onChangeConfiguration(
|
|
1219 const DidChangeConfigurationParams &Params) {
|
|
1220 applyConfiguration(Params.settings);
|
|
1221 }
|
|
1222
|
|
1223 void ClangdLSPServer::onReference(const ReferenceParams &Params,
|
|
1224 Callback<std::vector<Location>> Reply) {
|
|
1225 Server->findReferences(Params.textDocument.uri.file(), Params.position,
|
|
1226 CCOpts.Limit,
|
|
1227 [Reply = std::move(Reply)](
|
|
1228 llvm::Expected<ReferencesResult> Refs) mutable {
|
|
1229 if (!Refs)
|
|
1230 return Reply(Refs.takeError());
|
|
1231 return Reply(std::move(Refs->References));
|
|
1232 });
|
|
1233 }
|
|
1234
|
|
1235 void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params,
|
|
1236 Callback<std::vector<SymbolDetails>> Reply) {
|
|
1237 Server->symbolInfo(Params.textDocument.uri.file(), Params.position,
|
|
1238 std::move(Reply));
|
|
1239 }
|
|
1240
|
|
1241 void ClangdLSPServer::onSelectionRange(
|
|
1242 const SelectionRangeParams &Params,
|
|
1243 Callback<std::vector<SelectionRange>> Reply) {
|
|
1244 Server->semanticRanges(
|
173
|
1245 Params.textDocument.uri.file(), Params.positions,
|
150
|
1246 [Reply = std::move(Reply)](
|
173
|
1247 llvm::Expected<std::vector<SelectionRange>> Ranges) mutable {
|
|
1248 if (!Ranges)
|
150
|
1249 return Reply(Ranges.takeError());
|
173
|
1250 return Reply(std::move(*Ranges));
|
150
|
1251 });
|
|
1252 }
|
|
1253
|
|
1254 void ClangdLSPServer::onDocumentLink(
|
|
1255 const DocumentLinkParams &Params,
|
|
1256 Callback<std::vector<DocumentLink>> Reply) {
|
|
1257
|
|
1258 // TODO(forster): This currently resolves all targets eagerly. This is slow,
|
|
1259 // because it blocks on the preamble/AST being built. We could respond to the
|
|
1260 // request faster by using string matching or the lexer to find the includes
|
|
1261 // and resolving the targets lazily.
|
|
1262 Server->documentLinks(
|
|
1263 Params.textDocument.uri.file(),
|
|
1264 [Reply = std::move(Reply)](
|
|
1265 llvm::Expected<std::vector<DocumentLink>> Links) mutable {
|
|
1266 if (!Links) {
|
|
1267 return Reply(Links.takeError());
|
|
1268 }
|
|
1269 return Reply(std::move(Links));
|
|
1270 });
|
|
1271 }
|
|
1272
|
173
|
1273 // Increment a numeric string: "" -> 1 -> 2 -> ... -> 9 -> 10 -> 11 ...
|
|
1274 static void increment(std::string &S) {
|
|
1275 for (char &C : llvm::reverse(S)) {
|
|
1276 if (C != '9') {
|
|
1277 ++C;
|
|
1278 return;
|
|
1279 }
|
|
1280 C = '0';
|
|
1281 }
|
|
1282 S.insert(S.begin(), '1');
|
|
1283 }
|
|
1284
|
|
1285 void ClangdLSPServer::onSemanticTokens(const SemanticTokensParams &Params,
|
|
1286 Callback<SemanticTokens> CB) {
|
|
1287 Server->semanticHighlights(
|
|
1288 Params.textDocument.uri.file(),
|
|
1289 [this, File(Params.textDocument.uri.file().str()), CB(std::move(CB))](
|
|
1290 llvm::Expected<std::vector<HighlightingToken>> HT) mutable {
|
|
1291 if (!HT)
|
|
1292 return CB(HT.takeError());
|
|
1293 SemanticTokens Result;
|
|
1294 Result.tokens = toSemanticTokens(*HT);
|
|
1295 {
|
|
1296 std::lock_guard<std::mutex> Lock(SemanticTokensMutex);
|
|
1297 auto &Last = LastSemanticTokens[File];
|
|
1298
|
|
1299 Last.tokens = Result.tokens;
|
|
1300 increment(Last.resultId);
|
|
1301 Result.resultId = Last.resultId;
|
|
1302 }
|
|
1303 CB(std::move(Result));
|
|
1304 });
|
|
1305 }
|
|
1306
|
|
1307 void ClangdLSPServer::onSemanticTokensEdits(
|
|
1308 const SemanticTokensEditsParams &Params,
|
|
1309 Callback<SemanticTokensOrEdits> CB) {
|
|
1310 Server->semanticHighlights(
|
|
1311 Params.textDocument.uri.file(),
|
|
1312 [this, PrevResultID(Params.previousResultId),
|
|
1313 File(Params.textDocument.uri.file().str()), CB(std::move(CB))](
|
|
1314 llvm::Expected<std::vector<HighlightingToken>> HT) mutable {
|
|
1315 if (!HT)
|
|
1316 return CB(HT.takeError());
|
|
1317 std::vector<SemanticToken> Toks = toSemanticTokens(*HT);
|
|
1318
|
|
1319 SemanticTokensOrEdits Result;
|
|
1320 {
|
|
1321 std::lock_guard<std::mutex> Lock(SemanticTokensMutex);
|
|
1322 auto &Last = LastSemanticTokens[File];
|
|
1323
|
|
1324 if (PrevResultID == Last.resultId) {
|
|
1325 Result.edits = diffTokens(Last.tokens, Toks);
|
|
1326 } else {
|
|
1327 vlog("semanticTokens/edits: wanted edits vs {0} but last result "
|
|
1328 "had ID {1}. Returning full token list.",
|
|
1329 PrevResultID, Last.resultId);
|
|
1330 Result.tokens = Toks;
|
|
1331 }
|
|
1332
|
|
1333 Last.tokens = std::move(Toks);
|
|
1334 increment(Last.resultId);
|
|
1335 Result.resultId = Last.resultId;
|
|
1336 }
|
|
1337
|
|
1338 CB(std::move(Result));
|
|
1339 });
|
|
1340 }
|
|
1341
|
150
|
1342 ClangdLSPServer::ClangdLSPServer(
|
|
1343 class Transport &Transp, const FileSystemProvider &FSProvider,
|
|
1344 const clangd::CodeCompleteOptions &CCOpts,
|
173
|
1345 const clangd::RenameOptions &RenameOpts,
|
150
|
1346 llvm::Optional<Path> CompileCommandsDir, bool UseDirBasedCDB,
|
|
1347 llvm::Optional<OffsetEncoding> ForcedOffsetEncoding,
|
|
1348 const ClangdServer::Options &Opts)
|
|
1349 : BackgroundContext(Context::current().clone()), Transp(Transp),
|
|
1350 MsgHandler(new MessageHandler(*this)), FSProvider(FSProvider),
|
173
|
1351 CCOpts(CCOpts), RenameOpts(RenameOpts),
|
|
1352 SupportedSymbolKinds(defaultSymbolKinds()),
|
150
|
1353 SupportedCompletionItemKinds(defaultCompletionItemKinds()),
|
|
1354 UseDirBasedCDB(UseDirBasedCDB),
|
|
1355 CompileCommandsDir(std::move(CompileCommandsDir)), ClangdServerOpts(Opts),
|
|
1356 NegotiatedOffsetEncoding(ForcedOffsetEncoding) {
|
|
1357 // clang-format off
|
|
1358 MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize);
|
173
|
1359 MsgHandler->bind("initialized", &ClangdLSPServer::onInitialized);
|
150
|
1360 MsgHandler->bind("shutdown", &ClangdLSPServer::onShutdown);
|
|
1361 MsgHandler->bind("sync", &ClangdLSPServer::onSync);
|
|
1362 MsgHandler->bind("textDocument/rangeFormatting", &ClangdLSPServer::onDocumentRangeFormatting);
|
|
1363 MsgHandler->bind("textDocument/onTypeFormatting", &ClangdLSPServer::onDocumentOnTypeFormatting);
|
|
1364 MsgHandler->bind("textDocument/formatting", &ClangdLSPServer::onDocumentFormatting);
|
|
1365 MsgHandler->bind("textDocument/codeAction", &ClangdLSPServer::onCodeAction);
|
|
1366 MsgHandler->bind("textDocument/completion", &ClangdLSPServer::onCompletion);
|
|
1367 MsgHandler->bind("textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp);
|
|
1368 MsgHandler->bind("textDocument/definition", &ClangdLSPServer::onGoToDefinition);
|
|
1369 MsgHandler->bind("textDocument/declaration", &ClangdLSPServer::onGoToDeclaration);
|
|
1370 MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference);
|
|
1371 MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader);
|
|
1372 MsgHandler->bind("textDocument/prepareRename", &ClangdLSPServer::onPrepareRename);
|
|
1373 MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename);
|
|
1374 MsgHandler->bind("textDocument/hover", &ClangdLSPServer::onHover);
|
|
1375 MsgHandler->bind("textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol);
|
|
1376 MsgHandler->bind("workspace/executeCommand", &ClangdLSPServer::onCommand);
|
|
1377 MsgHandler->bind("textDocument/documentHighlight", &ClangdLSPServer::onDocumentHighlight);
|
|
1378 MsgHandler->bind("workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol);
|
|
1379 MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen);
|
|
1380 MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose);
|
|
1381 MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange);
|
173
|
1382 MsgHandler->bind("textDocument/didSave", &ClangdLSPServer::onDocumentDidSave);
|
150
|
1383 MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
|
|
1384 MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration);
|
|
1385 MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo);
|
|
1386 MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy);
|
|
1387 MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy);
|
|
1388 MsgHandler->bind("textDocument/selectionRange", &ClangdLSPServer::onSelectionRange);
|
|
1389 MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink);
|
173
|
1390 MsgHandler->bind("textDocument/semanticTokens", &ClangdLSPServer::onSemanticTokens);
|
|
1391 MsgHandler->bind("textDocument/semanticTokens/edits", &ClangdLSPServer::onSemanticTokensEdits);
|
150
|
1392 // clang-format on
|
|
1393 }
|
|
1394
|
173
|
1395 ClangdLSPServer::~ClangdLSPServer() {
|
|
1396 IsBeingDestroyed = true;
|
150
|
1397 // Explicitly destroy ClangdServer first, blocking on threads it owns.
|
|
1398 // This ensures they don't access any other members.
|
|
1399 Server.reset();
|
|
1400 }
|
|
1401
|
|
1402 bool ClangdLSPServer::run() {
|
|
1403 // Run the Language Server loop.
|
|
1404 bool CleanExit = true;
|
|
1405 if (auto Err = Transp.loop(*MsgHandler)) {
|
|
1406 elog("Transport error: {0}", std::move(Err));
|
|
1407 CleanExit = false;
|
|
1408 }
|
|
1409
|
|
1410 return CleanExit && ShutdownRequestReceived;
|
|
1411 }
|
|
1412
|
|
1413 std::vector<Fix> ClangdLSPServer::getFixes(llvm::StringRef File,
|
|
1414 const clangd::Diagnostic &D) {
|
|
1415 std::lock_guard<std::mutex> Lock(FixItsMutex);
|
|
1416 auto DiagToFixItsIter = FixItsMap.find(File);
|
|
1417 if (DiagToFixItsIter == FixItsMap.end())
|
|
1418 return {};
|
|
1419
|
|
1420 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
|
|
1421 auto FixItsIter = DiagToFixItsMap.find(D);
|
|
1422 if (FixItsIter == DiagToFixItsMap.end())
|
|
1423 return {};
|
|
1424
|
|
1425 return FixItsIter->second;
|
|
1426 }
|
|
1427
|
173
|
1428 // A completion request is sent when the user types '>' or ':', but we only
|
|
1429 // want to trigger on '->' and '::'. We check the preceeding text to make
|
|
1430 // sure it matches what we expected.
|
|
1431 // Running the lexer here would be more robust (e.g. we can detect comments
|
|
1432 // and avoid triggering completion there), but we choose to err on the side
|
|
1433 // of simplicity here.
|
150
|
1434 bool ClangdLSPServer::shouldRunCompletion(
|
|
1435 const CompletionParams &Params) const {
|
173
|
1436 if (Params.context.triggerKind != CompletionTriggerKind::TriggerCharacter)
|
150
|
1437 return true;
|
|
1438 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
|
|
1439 if (!Code)
|
|
1440 return true; // completion code will log the error for untracked doc.
|
173
|
1441 auto Offset = positionToOffset(Code->Contents, Params.position,
|
150
|
1442 /*AllowColumnsBeyondLineLength=*/false);
|
|
1443 if (!Offset) {
|
|
1444 vlog("could not convert position '{0}' to offset for file '{1}'",
|
|
1445 Params.position, Params.textDocument.uri.file());
|
|
1446 return true;
|
|
1447 }
|
173
|
1448 return allowImplicitCompletion(Code->Contents, *Offset);
|
150
|
1449 }
|
|
1450
|
|
1451 void ClangdLSPServer::onHighlightingsReady(
|
173
|
1452 PathRef File, llvm::StringRef Version,
|
|
1453 std::vector<HighlightingToken> Highlightings) {
|
150
|
1454 std::vector<HighlightingToken> Old;
|
|
1455 std::vector<HighlightingToken> HighlightingsCopy = Highlightings;
|
|
1456 {
|
|
1457 std::lock_guard<std::mutex> Lock(HighlightingsMutex);
|
|
1458 Old = std::move(FileToHighlightings[File]);
|
|
1459 FileToHighlightings[File] = std::move(HighlightingsCopy);
|
|
1460 }
|
|
1461 // LSP allows us to send incremental edits of highlightings. Also need to diff
|
|
1462 // to remove highlightings from tokens that should no longer have them.
|
|
1463 std::vector<LineHighlightings> Diffed = diffHighlightings(Highlightings, Old);
|
173
|
1464 TheiaSemanticHighlightingParams Notification;
|
|
1465 Notification.TextDocument.uri =
|
|
1466 URIForFile::canonicalize(File, /*TUPath=*/File);
|
|
1467 Notification.TextDocument.version = decodeVersion(Version);
|
|
1468 Notification.Lines = toTheiaSemanticHighlightingInformation(Diffed);
|
|
1469 publishTheiaSemanticHighlighting(Notification);
|
150
|
1470 }
|
|
1471
|
173
|
1472 void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version,
|
150
|
1473 std::vector<Diag> Diagnostics) {
|
173
|
1474 PublishDiagnosticsParams Notification;
|
|
1475 Notification.version = decodeVersion(Version);
|
|
1476 Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File);
|
150
|
1477 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
|
|
1478 for (auto &Diag : Diagnostics) {
|
173
|
1479 toLSPDiags(Diag, Notification.uri, DiagOpts,
|
150
|
1480 [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
|
|
1481 auto &FixItsForDiagnostic = LocalFixIts[Diag];
|
|
1482 llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic));
|
173
|
1483 Notification.diagnostics.push_back(std::move(Diag));
|
150
|
1484 });
|
|
1485 }
|
|
1486
|
|
1487 // Cache FixIts
|
|
1488 {
|
|
1489 std::lock_guard<std::mutex> Lock(FixItsMutex);
|
|
1490 FixItsMap[File] = LocalFixIts;
|
|
1491 }
|
|
1492
|
|
1493 // Send a notification to the LSP client.
|
173
|
1494 publishDiagnostics(Notification);
|
150
|
1495 }
|
|
1496
|
|
1497 void ClangdLSPServer::onBackgroundIndexProgress(
|
|
1498 const BackgroundQueue::Stats &Stats) {
|
|
1499 static const char ProgressToken[] = "backgroundIndexProgress";
|
|
1500 std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
|
|
1501
|
|
1502 auto NotifyProgress = [this](const BackgroundQueue::Stats &Stats) {
|
|
1503 if (BackgroundIndexProgressState != BackgroundIndexProgress::Live) {
|
|
1504 WorkDoneProgressBegin Begin;
|
|
1505 Begin.percentage = true;
|
|
1506 Begin.title = "indexing";
|
|
1507 progress(ProgressToken, std::move(Begin));
|
|
1508 BackgroundIndexProgressState = BackgroundIndexProgress::Live;
|
|
1509 }
|
|
1510
|
|
1511 if (Stats.Completed < Stats.Enqueued) {
|
|
1512 assert(Stats.Enqueued > Stats.LastIdle);
|
|
1513 WorkDoneProgressReport Report;
|
|
1514 Report.percentage = 100.0 * (Stats.Completed - Stats.LastIdle) /
|
|
1515 (Stats.Enqueued - Stats.LastIdle);
|
|
1516 Report.message =
|
|
1517 llvm::formatv("{0}/{1}", Stats.Completed - Stats.LastIdle,
|
|
1518 Stats.Enqueued - Stats.LastIdle);
|
|
1519 progress(ProgressToken, std::move(Report));
|
|
1520 } else {
|
|
1521 assert(Stats.Completed == Stats.Enqueued);
|
|
1522 progress(ProgressToken, WorkDoneProgressEnd());
|
|
1523 BackgroundIndexProgressState = BackgroundIndexProgress::Empty;
|
|
1524 }
|
|
1525 };
|
|
1526
|
|
1527 switch (BackgroundIndexProgressState) {
|
|
1528 case BackgroundIndexProgress::Unsupported:
|
|
1529 return;
|
|
1530 case BackgroundIndexProgress::Creating:
|
|
1531 // Cache this update for when the progress bar is available.
|
|
1532 PendingBackgroundIndexProgress = Stats;
|
|
1533 return;
|
|
1534 case BackgroundIndexProgress::Empty: {
|
|
1535 if (BackgroundIndexSkipCreate) {
|
|
1536 NotifyProgress(Stats);
|
|
1537 break;
|
|
1538 }
|
|
1539 // Cache this update for when the progress bar is available.
|
|
1540 PendingBackgroundIndexProgress = Stats;
|
|
1541 BackgroundIndexProgressState = BackgroundIndexProgress::Creating;
|
|
1542 WorkDoneProgressCreateParams CreateRequest;
|
|
1543 CreateRequest.token = ProgressToken;
|
|
1544 call<std::nullptr_t>(
|
|
1545 "window/workDoneProgress/create", CreateRequest,
|
|
1546 [this, NotifyProgress](llvm::Expected<std::nullptr_t> E) {
|
|
1547 std::lock_guard<std::mutex> Lock(BackgroundIndexProgressMutex);
|
|
1548 if (E) {
|
|
1549 NotifyProgress(this->PendingBackgroundIndexProgress);
|
|
1550 } else {
|
|
1551 elog("Failed to create background index progress bar: {0}",
|
|
1552 E.takeError());
|
|
1553 // give up forever rather than thrashing about
|
|
1554 BackgroundIndexProgressState = BackgroundIndexProgress::Unsupported;
|
|
1555 }
|
|
1556 });
|
|
1557 break;
|
|
1558 }
|
|
1559 case BackgroundIndexProgress::Live:
|
|
1560 NotifyProgress(Stats);
|
|
1561 break;
|
|
1562 }
|
|
1563 }
|
|
1564
|
|
1565 void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) {
|
|
1566 if (!SupportFileStatus)
|
|
1567 return;
|
|
1568 // FIXME: we don't emit "BuildingFile" and `RunningAction`, as these
|
|
1569 // two statuses are running faster in practice, which leads the UI constantly
|
|
1570 // changing, and doesn't provide much value. We may want to emit status at a
|
|
1571 // reasonable time interval (e.g. 0.5s).
|
173
|
1572 if (Status.PreambleActivity == PreambleAction::Idle &&
|
|
1573 (Status.ASTActivity.K == ASTAction::Building ||
|
|
1574 Status.ASTActivity.K == ASTAction::RunningAction))
|
150
|
1575 return;
|
|
1576 notify("textDocument/clangd.fileStatus", Status.render(File));
|
|
1577 }
|
|
1578
|
173
|
1579 void ClangdLSPServer::reparseOpenFilesIfNeeded(
|
|
1580 llvm::function_ref<bool(llvm::StringRef File)> Filter) {
|
150
|
1581 // Reparse only opened files that were modified.
|
|
1582 for (const Path &FilePath : DraftMgr.getActiveFiles())
|
173
|
1583 if (Filter(FilePath))
|
|
1584 if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?
|
|
1585 Server->addDocument(FilePath, std::move(Draft->Contents),
|
|
1586 encodeVersion(Draft->Version),
|
|
1587 WantDiagnostics::Auto);
|
150
|
1588 }
|
|
1589
|
|
1590 } // namespace clangd
|
|
1591 } // namespace clang
|