blob: 3e202f5eed698b14ced2ec0859d8523cfecc33ac [file] [log] [blame]
Ilya Biryukov38d79772017-05-16 09:38:59 +00001//===--- ClangdServer.h - Main clangd server code ----------------*- 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#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H
11#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDSERVER_H
12
13#include "ClangdUnitStore.h"
14#include "DraftStore.h"
15#include "GlobalCompilationDatabase.h"
16#include "clang/Frontend/ASTUnit.h"
17#include "clang/Tooling/CompilationDatabase.h"
18#include "clang/Tooling/Core/Replacement.h"
19#include "llvm/ADT/IntrusiveRefCntPtr.h"
20#include "llvm/ADT/Optional.h"
21#include "llvm/ADT/StringRef.h"
22
23#include "ClangdUnit.h"
24#include "Protocol.h"
25
26#include <condition_variable>
Ilya Biryukovf01af682017-05-23 13:42:59 +000027#include <functional>
Ilya Biryukov38d79772017-05-16 09:38:59 +000028#include <mutex>
29#include <string>
30#include <thread>
Ilya Biryukov22602992017-05-30 15:11:02 +000031#include <type_traits>
Ilya Biryukov38d79772017-05-16 09:38:59 +000032#include <utility>
33
34namespace clang {
35class PCHContainerOperations;
36
37namespace clangd {
38
Ilya Biryukove5128f72017-09-20 07:24:15 +000039class Logger;
40
Ilya Biryukovafb55542017-05-16 14:40:30 +000041/// Turn a [line, column] pair into an offset in Code.
42size_t positionToOffset(StringRef Code, Position P);
43
44/// Turn an offset in Code into a [line, column] pair.
45Position offsetToPosition(StringRef Code, size_t Offset);
46
Ilya Biryukov22602992017-05-30 15:11:02 +000047/// A tag supplied by the FileSytemProvider.
Ilya Biryukove36035b2017-06-13 08:24:48 +000048typedef std::string VFSTag;
Ilya Biryukov22602992017-05-30 15:11:02 +000049
50/// A value of an arbitrary type and VFSTag that was supplied by the
51/// FileSystemProvider when this value was computed.
52template <class T> class Tagged {
53public:
54 template <class U>
Ilya Biryukove36035b2017-06-13 08:24:48 +000055 Tagged(U &&Value, VFSTag Tag)
56 : Value(std::forward<U>(Value)), Tag(std::move(Tag)) {}
Ilya Biryukov22602992017-05-30 15:11:02 +000057
58 template <class U>
59 Tagged(const Tagged<U> &Other) : Value(Other.Value), Tag(Other.Tag) {}
60
61 template <class U>
Ilya Biryukove36035b2017-06-13 08:24:48 +000062 Tagged(Tagged<U> &&Other)
63 : Value(std::move(Other.Value)), Tag(std::move(Other.Tag)) {}
Ilya Biryukov22602992017-05-30 15:11:02 +000064
65 T Value;
66 VFSTag Tag;
67};
68
69template <class T>
70Tagged<typename std::decay<T>::type> make_tagged(T &&Value, VFSTag Tag) {
Ilya Biryukovc22824a2017-08-09 12:55:13 +000071 return Tagged<typename std::decay<T>::type>(std::forward<T>(Value), Tag);
Ilya Biryukov22602992017-05-30 15:11:02 +000072}
73
Ilya Biryukov38d79772017-05-16 09:38:59 +000074class DiagnosticsConsumer {
75public:
76 virtual ~DiagnosticsConsumer() = default;
77
78 /// Called by ClangdServer when \p Diagnostics for \p File are ready.
Ilya Biryukov22602992017-05-30 15:11:02 +000079 virtual void
80 onDiagnosticsReady(PathRef File,
81 Tagged<std::vector<DiagWithFixIts>> Diagnostics) = 0;
Ilya Biryukov38d79772017-05-16 09:38:59 +000082};
83
Ilya Biryukov0f62ed22017-05-26 12:26:51 +000084class FileSystemProvider {
85public:
86 virtual ~FileSystemProvider() = default;
Ilya Biryukovaf0c04b2017-06-14 09:46:44 +000087 /// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing.
88 /// Name of the file that will be parsed is passed in \p File.
89 ///
Ilya Biryukov22602992017-05-30 15:11:02 +000090 /// \return A filesystem that will be used for all file accesses in clangd.
91 /// A Tag returned by this method will be propagated to all results of clangd
92 /// that will use this filesystem.
Ilya Biryukovaf0c04b2017-06-14 09:46:44 +000093 virtual Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
94 getTaggedFileSystem(PathRef File) = 0;
Ilya Biryukov0f62ed22017-05-26 12:26:51 +000095};
96
97class RealFileSystemProvider : public FileSystemProvider {
98public:
Ilya Biryukov22602992017-05-30 15:11:02 +000099 /// \return getRealFileSystem() tagged with default tag, i.e. VFSTag()
Ilya Biryukovaf0c04b2017-06-14 09:46:44 +0000100 Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
101 getTaggedFileSystem(PathRef File) override;
Ilya Biryukov0f62ed22017-05-26 12:26:51 +0000102};
103
Ilya Biryukov38d79772017-05-16 09:38:59 +0000104class ClangdServer;
105
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000106/// Returns a number of a default async threads to use for ClangdScheduler.
107/// Returned value is always >= 1 (i.e. will not cause requests to be processed
108/// synchronously).
109unsigned getDefaultAsyncThreadsCount();
110
111/// Handles running WorkerRequests of ClangdServer on a number of worker
112/// threads.
Ilya Biryukov38d79772017-05-16 09:38:59 +0000113class ClangdScheduler {
114public:
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000115 /// If \p AsyncThreadsCount is 0, requests added using addToFront and addToEnd
116 /// will be processed synchronously on the calling thread.
117 // Otherwise, \p AsyncThreadsCount threads will be created to schedule the
118 // requests.
119 ClangdScheduler(unsigned AsyncThreadsCount);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000120 ~ClangdScheduler();
121
Ilya Biryukov02d58702017-08-01 15:51:38 +0000122 /// Add a new request to run function \p F with args \p As to the start of the
123 /// queue. The request will be run on a separate thread.
124 template <class Func, class... Args>
125 void addToFront(Func &&F, Args &&... As) {
126 if (RunSynchronously) {
127 std::forward<Func>(F)(std::forward<Args>(As)...);
128 return;
129 }
130
131 {
132 std::lock_guard<std::mutex> Lock(Mutex);
133 RequestQueue.push_front(std::async(std::launch::deferred,
134 std::forward<Func>(F),
135 std::forward<Args>(As)...));
136 }
137 RequestCV.notify_one();
138 }
139
140 /// Add a new request to run function \p F with args \p As to the end of the
141 /// queue. The request will be run on a separate thread.
142 template <class Func, class... Args> void addToEnd(Func &&F, Args &&... As) {
143 if (RunSynchronously) {
144 std::forward<Func>(F)(std::forward<Args>(As)...);
145 return;
146 }
147
148 {
149 std::lock_guard<std::mutex> Lock(Mutex);
150 RequestQueue.push_back(std::async(std::launch::deferred,
151 std::forward<Func>(F),
152 std::forward<Args>(As)...));
153 }
154 RequestCV.notify_one();
155 }
Ilya Biryukov38d79772017-05-16 09:38:59 +0000156
157private:
158 bool RunSynchronously;
159 std::mutex Mutex;
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000160 /// We run some tasks on separate threads(parsing, CppFile cleanup).
161 /// These threads looks into RequestQueue to find requests to handle and
162 /// terminate when Done is set to true.
163 std::vector<std::thread> Workers;
164 /// Setting Done to true will make the worker threads terminate.
Ilya Biryukov38d79772017-05-16 09:38:59 +0000165 bool Done = false;
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000166 /// A queue of requests. Elements of this vector are async computations (i.e.
167 /// results of calling std::async(std::launch::deferred, ...)).
Ilya Biryukov02d58702017-08-01 15:51:38 +0000168 std::deque<std::future<void>> RequestQueue;
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000169 /// Condition variable to wake up worker threads.
Ilya Biryukov38d79772017-05-16 09:38:59 +0000170 std::condition_variable RequestCV;
171};
172
173/// Provides API to manage ASTs for a collection of C++ files and request
Ilya Biryukov75337e82017-08-22 09:16:46 +0000174/// various language features.
175/// Currently supports async diagnostics, code completion, formatting and goto
176/// definition.
Ilya Biryukov38d79772017-05-16 09:38:59 +0000177class ClangdServer {
178public:
Ilya Biryukov75337e82017-08-22 09:16:46 +0000179 /// Creates a new ClangdServer instance.
180 /// To process parsing requests asynchronously, ClangdServer will spawn \p
181 /// AsyncThreadsCount worker threads. However, if \p AsyncThreadsCount is 0,
182 /// all requests will be processed on the calling thread.
183 ///
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000184 /// When \p SnippetCompletions is true, completion items will be presented
185 /// with embedded snippets. Otherwise, plaintext items will be presented.
186 ///
Ilya Biryukov75337e82017-08-22 09:16:46 +0000187 /// ClangdServer uses \p FSProvider to get an instance of vfs::FileSystem for
188 /// each parsing request. Results of code completion and diagnostics also
189 /// include a tag, that \p FSProvider returns along with the vfs::FileSystem.
190 ///
191 /// The value of \p ResourceDir will be used to search for internal headers
192 /// (overriding defaults and -resource-dir compiler flag). If \p ResourceDir
193 /// is None, ClangdServer will call CompilerInvocation::GetResourcePath() to
194 /// obtain the standard resource directory.
195 ///
196 /// ClangdServer uses \p CDB to obtain compilation arguments for parsing. Note
197 /// that ClangdServer only obtains compilation arguments once for each newly
198 /// added file (i.e., when processing a first call to addDocument) and reuses
199 /// those arguments for subsequent reparses. However, ClangdServer will check
200 /// if compilation arguments changed on calls to forceReparse().
201 ///
202 /// After each parsing request finishes, ClangdServer reports diagnostics to
203 /// \p DiagConsumer. Note that a callback to \p DiagConsumer happens on a
204 /// worker thread. Therefore, instances of \p DiagConsumer must properly
205 /// synchronize access to shared state.
Ilya Biryukove5128f72017-09-20 07:24:15 +0000206 ///
207 /// Various messages are logged using \p Logger.
Ilya Biryukov103c9512017-06-13 15:59:43 +0000208 ClangdServer(GlobalCompilationDatabase &CDB,
209 DiagnosticsConsumer &DiagConsumer,
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000210 FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000211 bool SnippetCompletions, clangd::Logger &Logger,
Ilya Biryukova46f7a92017-06-28 10:34:50 +0000212 llvm::Optional<StringRef> ResourceDir = llvm::None);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000213
Marc-Andre Laperle37de9712017-09-27 15:31:17 +0000214 /// Set the root path of the workspace.
215 void setRootPath(PathRef RootPath);
216
Ilya Biryukov38d79772017-05-16 09:38:59 +0000217 /// Add a \p File to the list of tracked C++ files or update the contents if
218 /// \p File is already tracked. Also schedules parsing of the AST for it on a
219 /// separate thread. When the parsing is complete, DiagConsumer passed in
220 /// constructor will receive onDiagnosticsReady callback.
Ilya Biryukov02d58702017-08-01 15:51:38 +0000221 /// \return A future that will become ready when the rebuild (including
222 /// diagnostics) is finished.
223 std::future<void> addDocument(PathRef File, StringRef Contents);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000224 /// Remove \p File from list of tracked files, schedule a request to free
225 /// resources associated with it.
Ilya Biryukov91dbf5b2017-08-14 08:37:32 +0000226 /// \return A future that will become ready when the file is removed and all
227 /// associated resources are freed.
Ilya Biryukov02d58702017-08-01 15:51:38 +0000228 std::future<void> removeDocument(PathRef File);
Ilya Biryukov0f62ed22017-05-26 12:26:51 +0000229 /// Force \p File to be reparsed using the latest contents.
Ilya Biryukov91dbf5b2017-08-14 08:37:32 +0000230 /// Will also check if CompileCommand, provided by GlobalCompilationDatabase
231 /// for \p File has changed. If it has, will remove currently stored Preamble
232 /// and AST and rebuild them from scratch.
Ilya Biryukov02d58702017-08-01 15:51:38 +0000233 std::future<void> forceReparse(PathRef File);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000234
Ilya Biryukov0e27ce42017-06-13 14:15:56 +0000235 /// Run code completion for \p File at \p Pos. If \p OverridenContents is not
236 /// None, they will used only for code completion, i.e. no diagnostics update
237 /// will be scheduled and a draft for \p File will not be updated.
238 /// If \p OverridenContents is None, contents of the current draft for \p File
239 /// will be used.
Ilya Biryukoved99e4c2017-07-31 17:09:29 +0000240 /// If \p UsedFS is non-null, it will be overwritten by vfs::FileSystem used
241 /// for completion.
242 /// This method should only be called for currently tracked
243 /// files.
Ilya Biryukov0e27ce42017-06-13 14:15:56 +0000244 Tagged<std::vector<CompletionItem>>
245 codeComplete(PathRef File, Position Pos,
Ilya Biryukoved99e4c2017-07-31 17:09:29 +0000246 llvm::Optional<StringRef> OverridenContents = llvm::None,
247 IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000248 /// Get definition of symbol at a specified \p Line and \p Column in \p File.
249 Tagged<std::vector<Location>> findDefinitions(PathRef File, Position Pos);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000250
Ilya Biryukovafb55542017-05-16 14:40:30 +0000251 /// Run formatting for \p Rng inside \p File.
252 std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);
253 /// Run formatting for the whole \p File.
254 std::vector<tooling::Replacement> formatFile(PathRef File);
255 /// Run formatting after a character was typed at \p Pos in \p File.
256 std::vector<tooling::Replacement> formatOnType(PathRef File, Position Pos);
257
Ilya Biryukov38d79772017-05-16 09:38:59 +0000258 /// Gets current document contents for \p File. \p File must point to a
259 /// currently tracked file.
Ilya Biryukovafb55542017-05-16 14:40:30 +0000260 /// FIXME(ibiryukov): This function is here to allow offset-to-Position
261 /// conversions in outside code, maybe there's a way to get rid of it.
Ilya Biryukov38d79772017-05-16 09:38:59 +0000262 std::string getDocument(PathRef File);
263
Ilya Biryukovf01af682017-05-23 13:42:59 +0000264 /// Only for testing purposes.
265 /// Waits until all requests to worker thread are finished and dumps AST for
266 /// \p File. \p File must be in the list of added documents.
267 std::string dumpAST(PathRef File);
268
Ilya Biryukov38d79772017-05-16 09:38:59 +0000269private:
Ilya Biryukovc5ad35f2017-08-14 08:17:24 +0000270 std::future<void>
271 scheduleReparseAndDiags(PathRef File, VersionedDraft Contents,
272 std::shared_ptr<CppFile> Resources,
273 Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS);
274
275 std::future<void> scheduleCancelRebuild(std::shared_ptr<CppFile> Resources);
276
Ilya Biryukove5128f72017-09-20 07:24:15 +0000277 clangd::Logger &Logger;
Ilya Biryukov103c9512017-06-13 15:59:43 +0000278 GlobalCompilationDatabase &CDB;
279 DiagnosticsConsumer &DiagConsumer;
280 FileSystemProvider &FSProvider;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000281 DraftStore DraftMgr;
Ilya Biryukov02d58702017-08-01 15:51:38 +0000282 CppFileCollection Units;
Ilya Biryukova46f7a92017-06-28 10:34:50 +0000283 std::string ResourceDir;
Marc-Andre Laperle37de9712017-09-27 15:31:17 +0000284 // If set, this represents the workspace path.
285 llvm::Optional<std::string> RootPath;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000286 std::shared_ptr<PCHContainerOperations> PCHs;
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000287 bool SnippetCompletions;
Ilya Biryukov47f22022017-09-20 12:58:55 +0000288 /// Used to serialize diagnostic callbacks.
289 /// FIXME(ibiryukov): get rid of an extra map and put all version counters
290 /// into CppFile.
291 std::mutex DiagnosticsMutex;
292 /// Maps from a filename to the latest version of reported diagnostics.
293 llvm::StringMap<DocVersion> ReportedDiagnosticVersions;
Ilya Biryukovf4e95d72017-09-20 19:32:06 +0000294 // WorkScheduler has to be the last member, because its destructor has to be
295 // called before all other members to stop the worker thread that references
296 // ClangdServer
297 ClangdScheduler WorkScheduler;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000298};
299
300} // namespace clangd
301} // namespace clang
302
303#endif