1 // Copyright 2017-2018 ccls Authors
2 // SPDX-License-Identifier: Apache-2.0
3
4 #include "pipeline.hh"
5
6 #include "config.hh"
7 #include "include_complete.hh"
8 #include "log.hh"
9 #include "lsp.hh"
10 #include "message_handler.hh"
11 #include "pipeline.hh"
12 #include "platform.hh"
13 #include "project.hh"
14 #include "query.hh"
15 #include "sema_manager.hh"
16
17 #include <rapidjson/document.h>
18 #include <rapidjson/writer.h>
19
20 #include <llvm/Support/Path.h>
21 #include <llvm/Support/Process.h>
22 #include <llvm/Support/Threading.h>
23
24 #include <chrono>
25 #include <inttypes.h>
26 #include <mutex>
27 #include <shared_mutex>
28 #include <thread>
29 #ifndef _WIN32
30 #include <unistd.h>
31 #endif
32 using namespace llvm;
33 namespace chrono = std::chrono;
34
35 namespace ccls {
36 namespace {
37 struct PublishDiagnosticParam {
38 DocumentUri uri;
39 std::vector<Diagnostic> diagnostics;
40 };
41 REFLECT_STRUCT(PublishDiagnosticParam, uri, diagnostics);
42
43 constexpr char index_progress_token[] = "index";
44 struct WorkDoneProgressCreateParam {
45 const char *token = index_progress_token;
46 };
47 REFLECT_STRUCT(WorkDoneProgressCreateParam, token);
48 } // namespace
49
clear()50 void VFS::clear() {
51 std::lock_guard lock(mutex);
52 state.clear();
53 }
54
loaded(const std::string & path)55 int VFS::loaded(const std::string &path) {
56 std::lock_guard lock(mutex);
57 return state[path].loaded;
58 }
59
stamp(const std::string & path,int64_t ts,int step)60 bool VFS::stamp(const std::string &path, int64_t ts, int step) {
61 std::lock_guard<std::mutex> lock(mutex);
62 State &st = state[path];
63 if (st.timestamp < ts || (st.timestamp == ts && st.step < step)) {
64 st.timestamp = ts;
65 st.step = step;
66 return true;
67 } else
68 return false;
69 }
70
71 struct MessageHandler;
72 void standaloneInitialize(MessageHandler &, const std::string &root);
73
74 namespace pipeline {
75
76 std::atomic<bool> g_quit;
77 std::atomic<int64_t> loaded_ts{0}, request_id{0};
78 IndexStats stats;
79 int64_t tick = 0;
80
81 namespace {
82
83 struct IndexRequest {
84 std::string path;
85 std::vector<const char *> args;
86 IndexMode mode;
87 bool must_exist = false;
88 RequestId id;
89 int64_t ts = tick++;
90 };
91
92 std::mutex thread_mtx;
93 std::condition_variable no_active_threads;
94 int active_threads;
95
96 MultiQueueWaiter *main_waiter;
97 MultiQueueWaiter *indexer_waiter;
98 MultiQueueWaiter *stdout_waiter;
99 ThreadedQueue<InMessage> *on_request;
100 ThreadedQueue<IndexRequest> *index_request;
101 ThreadedQueue<IndexUpdate> *on_indexed;
102 ThreadedQueue<std::string> *for_stdout;
103
104 struct InMemoryIndexFile {
105 std::string content;
106 IndexFile index;
107 };
108 std::shared_mutex g_index_mutex;
109 std::unordered_map<std::string, InMemoryIndexFile> g_index;
110
cacheInvalid(VFS * vfs,IndexFile * prev,const std::string & path,const std::vector<const char * > & args,const std::optional<std::string> & from)111 bool cacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path,
112 const std::vector<const char *> &args,
113 const std::optional<std::string> &from) {
114 {
115 std::lock_guard<std::mutex> lock(vfs->mutex);
116 if (prev->mtime < vfs->state[path].timestamp) {
117 LOG_V(1) << "timestamp changed for " << path
118 << (from ? " (via " + *from + ")" : std::string());
119 return true;
120 }
121 }
122
123 // For inferred files, allow -o a a.cc -> -o b b.cc
124 StringRef stem = sys::path::stem(path);
125 int changed = -1, size = std::min(prev->args.size(), args.size());
126 for (int i = 0; i < size; i++)
127 if (strcmp(prev->args[i], args[i]) && sys::path::stem(args[i]) != stem) {
128 changed = i;
129 break;
130 }
131 if (changed < 0 && prev->args.size() != args.size())
132 changed = size;
133 if (changed >= 0)
134 LOG_V(1) << "args changed for " << path
135 << (from ? " (via " + *from + ")" : std::string()) << "; old: "
136 << (changed < prev->args.size() ? prev->args[changed] : "")
137 << "; new: " << (changed < size ? args[changed] : "");
138 return changed >= 0;
139 };
140
appendSerializationFormat(const std::string & base)141 std::string appendSerializationFormat(const std::string &base) {
142 switch (g_config->cache.format) {
143 case SerializeFormat::Binary:
144 return base + ".blob";
145 case SerializeFormat::Json:
146 return base + ".json";
147 }
148 }
149
getCachePath(std::string src)150 std::string getCachePath(std::string src) {
151 if (g_config->cache.hierarchicalPath) {
152 std::string ret = src[0] == '/' ? src.substr(1) : src;
153 #ifdef _WIN32
154 std::replace(ret.begin(), ret.end(), ':', '@');
155 #endif
156 return g_config->cache.directory + ret;
157 }
158 for (auto &[root, _] : g_config->workspaceFolders)
159 if (StringRef(src).startswith(root)) {
160 auto len = root.size();
161 return g_config->cache.directory +
162 escapeFileName(root.substr(0, len - 1)) + '/' +
163 escapeFileName(src.substr(len));
164 }
165 return g_config->cache.directory + '@' +
166 escapeFileName(g_config->fallbackFolder.substr(
167 0, g_config->fallbackFolder.size() - 1)) +
168 '/' + escapeFileName(src);
169 }
170
rawCacheLoad(const std::string & path)171 std::unique_ptr<IndexFile> rawCacheLoad(const std::string &path) {
172 if (g_config->cache.retainInMemory) {
173 std::shared_lock lock(g_index_mutex);
174 auto it = g_index.find(path);
175 if (it != g_index.end())
176 return std::make_unique<IndexFile>(it->second.index);
177 if (g_config->cache.directory.empty())
178 return nullptr;
179 }
180
181 std::string cache_path = getCachePath(path);
182 std::optional<std::string> file_content = readContent(cache_path);
183 std::optional<std::string> serialized_indexed_content =
184 readContent(appendSerializationFormat(cache_path));
185 if (!file_content || !serialized_indexed_content)
186 return nullptr;
187
188 return ccls::deserialize(g_config->cache.format, path,
189 *serialized_indexed_content, *file_content,
190 IndexFile::kMajorVersion);
191 }
192
getFileMutex(const std::string & path)193 std::mutex &getFileMutex(const std::string &path) {
194 const int n_MUTEXES = 256;
195 static std::mutex mutexes[n_MUTEXES];
196 return mutexes[std::hash<std::string>()(path) % n_MUTEXES];
197 }
198
indexer_Parse(SemaManager * completion,WorkingFiles * wfiles,Project * project,VFS * vfs,const GroupMatch & matcher)199 bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles,
200 Project *project, VFS *vfs, const GroupMatch &matcher) {
201 std::optional<IndexRequest> opt_request = index_request->tryPopFront();
202 if (!opt_request)
203 return false;
204 auto &request = *opt_request;
205 bool loud = request.mode != IndexMode::OnChange;
206
207 // Dummy one to trigger refresh semantic highlight.
208 if (request.path.empty()) {
209 IndexUpdate dummy;
210 dummy.refresh = true;
211 on_indexed->pushBack(std::move(dummy), false);
212 return false;
213 }
214
215 struct RAII {
216 ~RAII() { stats.completed++; }
217 } raii;
218 if (!matcher.matches(request.path)) {
219 LOG_IF_S(INFO, loud) << "skip " << request.path;
220 return false;
221 }
222
223 Project::Entry entry =
224 project->findEntry(request.path, true, request.must_exist);
225 if (request.must_exist && entry.filename.empty())
226 return true;
227 if (request.args.size())
228 entry.args = request.args;
229 std::string path_to_index = entry.filename;
230 std::unique_ptr<IndexFile> prev;
231
232 bool deleted = request.mode == IndexMode::Delete,
233 no_linkage = g_config->index.initialNoLinkage ||
234 request.mode != IndexMode::Background;
235 int reparse = 0;
236 if (deleted)
237 reparse = 2;
238 else if (!(g_config->index.onChange && wfiles->getFile(path_to_index))) {
239 std::optional<int64_t> write_time = lastWriteTime(path_to_index);
240 if (!write_time) {
241 deleted = true;
242 } else {
243 if (vfs->stamp(path_to_index, *write_time, no_linkage ? 2 : 0))
244 reparse = 1;
245 if (request.path != path_to_index) {
246 std::optional<int64_t> mtime1 = lastWriteTime(request.path);
247 if (!mtime1)
248 deleted = true;
249 else if (vfs->stamp(request.path, *mtime1, no_linkage ? 2 : 0))
250 reparse = 2;
251 }
252 }
253 }
254
255 if (g_config->index.onChange) {
256 reparse = 2;
257 std::lock_guard lock(vfs->mutex);
258 vfs->state[path_to_index].step = 0;
259 if (request.path != path_to_index)
260 vfs->state[request.path].step = 0;
261 }
262 bool track = g_config->index.trackDependency > 1 ||
263 (g_config->index.trackDependency == 1 && request.ts < loaded_ts);
264 if (!reparse && !track)
265 return true;
266
267 if (reparse < 2)
268 do {
269 std::unique_lock lock(getFileMutex(path_to_index));
270 prev = rawCacheLoad(path_to_index);
271 if (!prev || prev->no_linkage < no_linkage ||
272 cacheInvalid(vfs, prev.get(), path_to_index, entry.args,
273 std::nullopt))
274 break;
275 if (track)
276 for (const auto &dep : prev->dependencies) {
277 if (auto mtime1 = lastWriteTime(dep.first.val().str())) {
278 if (dep.second < *mtime1) {
279 reparse = 2;
280 LOG_V(1) << "timestamp changed for " << path_to_index << " via "
281 << dep.first.val().str();
282 break;
283 }
284 } else {
285 reparse = 2;
286 LOG_V(1) << "timestamp changed for " << path_to_index << " via "
287 << dep.first.val().str();
288 break;
289 }
290 }
291 if (reparse == 0)
292 return true;
293 if (reparse == 2)
294 break;
295
296 if (vfs->loaded(path_to_index))
297 return true;
298 LOG_S(INFO) << "load cache for " << path_to_index;
299 auto dependencies = prev->dependencies;
300 IndexUpdate update = IndexUpdate::createDelta(nullptr, prev.get());
301 on_indexed->pushBack(std::move(update),
302 request.mode != IndexMode::Background);
303 {
304 std::lock_guard lock1(vfs->mutex);
305 VFS::State &st = vfs->state[path_to_index];
306 st.loaded++;
307 if (prev->no_linkage)
308 st.step = 2;
309 }
310 lock.unlock();
311
312 for (const auto &dep : dependencies) {
313 std::string path = dep.first.val().str();
314 if (!vfs->stamp(path, dep.second, 1))
315 continue;
316 std::lock_guard lock1(getFileMutex(path));
317 prev = rawCacheLoad(path);
318 if (!prev)
319 continue;
320 {
321 std::lock_guard lock2(vfs->mutex);
322 VFS::State &st = vfs->state[path];
323 if (st.loaded)
324 continue;
325 st.loaded++;
326 st.timestamp = prev->mtime;
327 if (prev->no_linkage)
328 st.step = 3;
329 }
330 IndexUpdate update = IndexUpdate::createDelta(nullptr, prev.get());
331 on_indexed->pushBack(std::move(update),
332 request.mode != IndexMode::Background);
333 if (entry.id >= 0) {
334 std::lock_guard lock2(project->mtx);
335 project->root2folder[entry.root].path2entry_index[path] = entry.id;
336 }
337 }
338 return true;
339 } while (0);
340
341 std::vector<std::unique_ptr<IndexFile>> indexes;
342 int n_errs = 0;
343 std::string first_error;
344 if (deleted) {
345 indexes.push_back(std::make_unique<IndexFile>(request.path, "", false));
346 if (request.path != path_to_index)
347 indexes.push_back(std::make_unique<IndexFile>(path_to_index, "", false));
348 } else {
349 std::vector<std::pair<std::string, std::string>> remapped;
350 if (g_config->index.onChange) {
351 std::string content = wfiles->getContent(path_to_index);
352 if (content.size())
353 remapped.emplace_back(path_to_index, content);
354 }
355 bool ok;
356 auto result =
357 idx::index(completion, wfiles, vfs, entry.directory, path_to_index,
358 entry.args, remapped, no_linkage, ok);
359 indexes = std::move(result.indexes);
360 n_errs = result.n_errs;
361 first_error = std::move(result.first_error);
362
363 if (!ok) {
364 if (request.id.valid()) {
365 ResponseError err;
366 err.code = ErrorCode::InternalError;
367 err.message = "failed to index " + path_to_index;
368 pipeline::replyError(request.id, err);
369 }
370 return true;
371 }
372 }
373
374 if (loud || n_errs) {
375 std::string line;
376 SmallString<64> tmp;
377 SmallString<256> msg;
378 (Twine(deleted ? "delete " : "parse ") + path_to_index).toVector(msg);
379 if (n_errs)
380 msg += (" error:" + Twine(n_errs) + " " + first_error).toStringRef(tmp);
381 if (LOG_V_ENABLED(1)) {
382 msg += "\n ";
383 for (const char *arg : entry.args)
384 (msg += ' ') += arg;
385 }
386 LOG_S(INFO) << std::string_view(msg.data(), msg.size());
387 }
388
389 for (std::unique_ptr<IndexFile> &curr : indexes) {
390 std::string path = curr->path;
391 if (!matcher.matches(path)) {
392 LOG_IF_S(INFO, loud) << "skip index for " << path;
393 continue;
394 }
395
396 if (!deleted)
397 LOG_IF_S(INFO, loud) << "store index for " << path
398 << " (delta: " << !!prev << ")";
399 {
400 std::lock_guard lock(getFileMutex(path));
401 int loaded = vfs->loaded(path), retain = g_config->cache.retainInMemory;
402 if (loaded)
403 prev = rawCacheLoad(path);
404 else
405 prev.reset();
406 if (retain > 0 && retain <= loaded + 1) {
407 std::lock_guard lock(g_index_mutex);
408 auto it = g_index.insert_or_assign(
409 path, InMemoryIndexFile{curr->file_contents, *curr});
410 std::string().swap(it.first->second.index.file_contents);
411 }
412 if (g_config->cache.directory.size()) {
413 std::string cache_path = getCachePath(path);
414 if (deleted) {
415 (void)sys::fs::remove(cache_path);
416 (void)sys::fs::remove(appendSerializationFormat(cache_path));
417 } else {
418 if (g_config->cache.hierarchicalPath)
419 sys::fs::create_directories(
420 sys::path::parent_path(cache_path, sys::path::Style::posix),
421 true);
422 writeToFile(cache_path, curr->file_contents);
423 writeToFile(appendSerializationFormat(cache_path),
424 serialize(g_config->cache.format, *curr));
425 }
426 }
427 on_indexed->pushBack(IndexUpdate::createDelta(prev.get(), curr.get()),
428 request.mode != IndexMode::Background);
429 {
430 std::lock_guard lock1(vfs->mutex);
431 vfs->state[path].loaded++;
432 }
433 if (entry.id >= 0) {
434 std::lock_guard lock(project->mtx);
435 auto &folder = project->root2folder[entry.root];
436 for (auto &dep : curr->dependencies)
437 folder.path2entry_index[dep.first.val().str()] = entry.id;
438 }
439 }
440 }
441
442 return true;
443 }
444
quit(SemaManager & manager)445 void quit(SemaManager &manager) {
446 g_quit.store(true, std::memory_order_relaxed);
447 manager.quit();
448
449 { std::lock_guard lock(index_request->mutex_); }
450 indexer_waiter->cv.notify_all();
451 { std::lock_guard lock(for_stdout->mutex_); }
452 stdout_waiter->cv.notify_one();
453 std::unique_lock lock(thread_mtx);
454 no_active_threads.wait(lock, [] { return !active_threads; });
455 }
456
457 } // namespace
458
threadEnter()459 void threadEnter() {
460 std::lock_guard lock(thread_mtx);
461 active_threads++;
462 }
463
threadLeave()464 void threadLeave() {
465 std::lock_guard lock(thread_mtx);
466 if (!--active_threads)
467 no_active_threads.notify_one();
468 }
469
init()470 void init() {
471 main_waiter = new MultiQueueWaiter;
472 on_request = new ThreadedQueue<InMessage>(main_waiter);
473 on_indexed = new ThreadedQueue<IndexUpdate>(main_waiter);
474
475 indexer_waiter = new MultiQueueWaiter;
476 index_request = new ThreadedQueue<IndexRequest>(indexer_waiter);
477
478 stdout_waiter = new MultiQueueWaiter;
479 for_stdout = new ThreadedQueue<std::string>(stdout_waiter);
480 }
481
indexer_Main(SemaManager * manager,VFS * vfs,Project * project,WorkingFiles * wfiles)482 void indexer_Main(SemaManager *manager, VFS *vfs, Project *project,
483 WorkingFiles *wfiles) {
484 GroupMatch matcher(g_config->index.whitelist, g_config->index.blacklist);
485 while (true)
486 if (!indexer_Parse(manager, wfiles, project, vfs, matcher))
487 if (indexer_waiter->wait(g_quit, index_request))
488 break;
489 }
490
main_OnIndexed(DB * db,WorkingFiles * wfiles,IndexUpdate * update)491 void main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) {
492 if (update->refresh) {
493 LOG_S(INFO)
494 << "loaded project. Refresh semantic highlight for all working file.";
495 std::lock_guard lock(wfiles->mutex);
496 for (auto &[f, wf] : wfiles->files) {
497 std::string path = lowerPathIfInsensitive(f);
498 if (db->name2file_id.find(path) == db->name2file_id.end())
499 continue;
500 QueryFile &file = db->files[db->name2file_id[path]];
501 emitSemanticHighlight(db, wf.get(), file);
502 }
503 return;
504 }
505
506 db->applyIndexUpdate(update);
507
508 // Update indexed content, skipped ranges, and semantic highlighting.
509 if (update->files_def_update) {
510 auto &def_u = *update->files_def_update;
511 if (WorkingFile *wfile = wfiles->getFile(def_u.first.path)) {
512 // FIXME With index.onChange: true, use buffer_content only for
513 // request.path
514 wfile->setIndexContent(g_config->index.onChange ? wfile->buffer_content
515 : def_u.second);
516 QueryFile &file = db->files[update->file_id];
517 emitSkippedRanges(wfile, file);
518 emitSemanticHighlight(db, wfile, file);
519 }
520 }
521 }
522
launchStdin()523 void launchStdin() {
524 threadEnter();
525 std::thread([]() {
526 set_thread_name("stdin");
527 std::string str;
528 const std::string_view kContentLength("Content-Length: ");
529 bool received_exit = false;
530 while (true) {
531 int len = 0;
532 str.clear();
533 while (true) {
534 int c = getchar();
535 if (c == EOF)
536 goto quit;
537 if (c == '\n') {
538 if (str.empty())
539 break;
540 if (!str.compare(0, kContentLength.size(), kContentLength))
541 len = atoi(str.c_str() + kContentLength.size());
542 str.clear();
543 } else if (c != '\r') {
544 str += c;
545 }
546 }
547
548 str.resize(len);
549 for (int i = 0; i < len; ++i) {
550 int c = getchar();
551 if (c == EOF)
552 goto quit;
553 str[i] = c;
554 }
555
556 auto message = std::make_unique<char[]>(len);
557 std::copy(str.begin(), str.end(), message.get());
558 auto document = std::make_unique<rapidjson::Document>();
559 document->Parse(message.get(), len);
560 assert(!document->HasParseError());
561
562 JsonReader reader{document.get()};
563 if (!reader.m->HasMember("jsonrpc") ||
564 std::string((*reader.m)["jsonrpc"].GetString()) != "2.0")
565 break;
566 RequestId id;
567 std::string method;
568 reflectMember(reader, "id", id);
569 reflectMember(reader, "method", method);
570 if (id.valid())
571 LOG_V(2) << "receive RequestMessage: " << id.value << " " << method;
572 else
573 LOG_V(2) << "receive NotificationMessage " << method;
574 if (method.empty())
575 continue;
576 received_exit = method == "exit";
577 // g_config is not available before "initialize". Use 0 in that case.
578 on_request->pushBack(
579 {id, std::move(method), std::move(message), std::move(document),
580 chrono::steady_clock::now() +
581 chrono::milliseconds(g_config ? g_config->request.timeout : 0)});
582
583 if (received_exit)
584 break;
585 }
586
587 quit:
588 if (!received_exit) {
589 const std::string_view str("{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}");
590 auto message = std::make_unique<char[]>(str.size());
591 std::copy(str.begin(), str.end(), message.get());
592 auto document = std::make_unique<rapidjson::Document>();
593 document->Parse(message.get(), str.size());
594 on_request->pushBack({RequestId(), std::string("exit"),
595 std::move(message), std::move(document),
596 chrono::steady_clock::now()});
597 }
598 threadLeave();
599 }).detach();
600 }
601
launchStdout()602 void launchStdout() {
603 threadEnter();
604 std::thread([]() {
605 set_thread_name("stdout");
606
607 while (true) {
608 std::vector<std::string> messages = for_stdout->dequeueAll();
609 for (auto &s : messages) {
610 llvm::outs() << "Content-Length: " << s.size() << "\r\n\r\n" << s;
611 llvm::outs().flush();
612 }
613 if (stdout_waiter->wait(g_quit, for_stdout))
614 break;
615 }
616 threadLeave();
617 }).detach();
618 }
619
mainLoop()620 void mainLoop() {
621 Project project;
622 WorkingFiles wfiles;
623 VFS vfs;
624
625 SemaManager manager(
626 &project, &wfiles,
627 [](const std::string &path, std::vector<Diagnostic> diagnostics) {
628 PublishDiagnosticParam params;
629 params.uri = DocumentUri::fromPath(path);
630 params.diagnostics = std::move(diagnostics);
631 notify("textDocument/publishDiagnostics", params);
632 },
633 [](const RequestId &id) {
634 if (id.valid()) {
635 ResponseError err;
636 err.code = ErrorCode::InternalError;
637 err.message = "drop older completion request";
638 replyError(id, err);
639 }
640 });
641
642 IncludeComplete include_complete(&project);
643 DB db;
644
645 // Setup shared references.
646 MessageHandler handler;
647 handler.db = &db;
648 handler.project = &project;
649 handler.vfs = &vfs;
650 handler.wfiles = &wfiles;
651 handler.manager = &manager;
652 handler.include_complete = &include_complete;
653
654 bool work_done_created = false, in_progress = false;
655 bool has_indexed = false;
656 int64_t last_completed = 0;
657 std::deque<InMessage> backlog;
658 StringMap<std::deque<InMessage *>> path2backlog;
659 while (true) {
660 if (backlog.size()) {
661 auto now = chrono::steady_clock::now();
662 handler.overdue = true;
663 while (backlog.size()) {
664 if (backlog[0].backlog_path.size()) {
665 if (now < backlog[0].deadline)
666 break;
667 handler.run(backlog[0]);
668 path2backlog[backlog[0].backlog_path].pop_front();
669 }
670 backlog.pop_front();
671 }
672 handler.overdue = false;
673 }
674
675 std::vector<InMessage> messages = on_request->dequeueAll();
676 bool did_work = messages.size();
677 for (InMessage &message : messages)
678 try {
679 handler.run(message);
680 } catch (NotIndexed &ex) {
681 backlog.push_back(std::move(message));
682 backlog.back().backlog_path = ex.path;
683 path2backlog[ex.path].push_back(&backlog.back());
684 }
685
686 bool indexed = false;
687 for (int i = 20; i--;) {
688 std::optional<IndexUpdate> update = on_indexed->tryPopFront();
689 if (!update)
690 break;
691 did_work = true;
692 indexed = true;
693 main_OnIndexed(&db, &wfiles, &*update);
694 if (update->files_def_update) {
695 auto it = path2backlog.find(update->files_def_update->first.path);
696 if (it != path2backlog.end()) {
697 for (auto &message : it->second) {
698 handler.run(*message);
699 message->backlog_path.clear();
700 }
701 path2backlog.erase(it);
702 }
703 }
704 }
705
706 int64_t completed = stats.completed.load(std::memory_order_relaxed);
707 if (completed != last_completed) {
708 if (!work_done_created) {
709 WorkDoneProgressCreateParam param;
710 request("window/workDoneProgress/create", param);
711 work_done_created = true;
712 }
713
714 int64_t enqueued = stats.enqueued.load(std::memory_order_relaxed);
715 if (completed != enqueued) {
716 if (!in_progress) {
717 WorkDoneProgressParam param;
718 param.token = index_progress_token;
719 param.value.kind = "begin";
720 param.value.title = "indexing";
721 notify("$/progress", param);
722 in_progress = true;
723 }
724 int64_t last_idle = stats.last_idle.load(std::memory_order_relaxed);
725 WorkDoneProgressParam param;
726 param.token = index_progress_token;
727 param.value.kind = "report";
728 param.value.message =
729 (Twine(completed - last_idle) + "/" + Twine(enqueued - last_idle))
730 .str();
731 param.value.percentage =
732 100 * (completed - last_idle) / (enqueued - last_idle);
733 notify("$/progress", param);
734 } else if (in_progress) {
735 stats.last_idle.store(enqueued, std::memory_order_relaxed);
736 WorkDoneProgressParam param;
737 param.token = index_progress_token;
738 param.value.kind = "end";
739 notify("$/progress", param);
740 in_progress = false;
741 }
742 last_completed = completed;
743 }
744
745 if (did_work) {
746 has_indexed |= indexed;
747 if (g_quit.load(std::memory_order_relaxed))
748 break;
749 } else {
750 if (has_indexed) {
751 freeUnusedMemory();
752 has_indexed = false;
753 }
754 if (backlog.empty())
755 main_waiter->wait(g_quit, on_indexed, on_request);
756 else
757 main_waiter->waitUntil(backlog[0].deadline, on_indexed, on_request);
758 }
759 }
760
761 quit(manager);
762 }
763
standalone(const std::string & root)764 void standalone(const std::string &root) {
765 Project project;
766 WorkingFiles wfiles;
767 VFS vfs;
768 SemaManager manager(
769 nullptr, nullptr,
770 [](const std::string &, const std::vector<Diagnostic> &) {},
771 [](const RequestId &id) {});
772 IncludeComplete complete(&project);
773
774 MessageHandler handler;
775 handler.project = &project;
776 handler.wfiles = &wfiles;
777 handler.vfs = &vfs;
778 handler.manager = &manager;
779 handler.include_complete = &complete;
780
781 standaloneInitialize(handler, root);
782 bool tty = sys::Process::StandardOutIsDisplayed();
783
784 if (tty) {
785 int entries = 0;
786 for (auto &[_, folder] : project.root2folder)
787 entries += folder.entries.size();
788 printf("entries: %4d\n", entries);
789 }
790 while (1) {
791 (void)on_indexed->dequeueAll();
792 int64_t enqueued = stats.enqueued, completed = stats.completed;
793 if (tty) {
794 printf("\rcompleted: %4" PRId64 "/%" PRId64, completed, enqueued);
795 fflush(stdout);
796 }
797 if (completed == enqueued)
798 break;
799 std::this_thread::sleep_for(std::chrono::milliseconds(100));
800 }
801 if (tty)
802 puts("");
803 quit(manager);
804 }
805
index(const std::string & path,const std::vector<const char * > & args,IndexMode mode,bool must_exist,RequestId id)806 void index(const std::string &path, const std::vector<const char *> &args,
807 IndexMode mode, bool must_exist, RequestId id) {
808 if (!path.empty())
809 stats.enqueued++;
810 index_request->pushBack({path, args, mode, must_exist, std::move(id)},
811 mode != IndexMode::Background);
812 }
813
removeCache(const std::string & path)814 void removeCache(const std::string &path) {
815 if (g_config->cache.directory.size()) {
816 std::lock_guard lock(g_index_mutex);
817 g_index.erase(path);
818 }
819 }
820
loadIndexedContent(const std::string & path)821 std::optional<std::string> loadIndexedContent(const std::string &path) {
822 if (g_config->cache.directory.empty()) {
823 std::shared_lock lock(g_index_mutex);
824 auto it = g_index.find(path);
825 if (it == g_index.end())
826 return {};
827 return it->second.content;
828 }
829 return readContent(getCachePath(path));
830 }
831
notifyOrRequest(const char * method,bool request,const std::function<void (JsonWriter &)> & fn)832 void notifyOrRequest(const char *method, bool request,
833 const std::function<void(JsonWriter &)> &fn) {
834 rapidjson::StringBuffer output;
835 rapidjson::Writer<rapidjson::StringBuffer> w(output);
836 w.StartObject();
837 w.Key("jsonrpc");
838 w.String("2.0");
839 w.Key("method");
840 w.String(method);
841 if (request) {
842 w.Key("id");
843 w.Int64(request_id.fetch_add(1, std::memory_order_relaxed));
844 }
845 w.Key("params");
846 JsonWriter writer(&w);
847 fn(writer);
848 w.EndObject();
849 LOG_V(2) << (request ? "RequestMessage: " : "NotificationMessage: ")
850 << method;
851 for_stdout->pushBack(output.GetString());
852 }
853
reply(const RequestId & id,const char * key,const std::function<void (JsonWriter &)> & fn)854 static void reply(const RequestId &id, const char *key,
855 const std::function<void(JsonWriter &)> &fn) {
856 rapidjson::StringBuffer output;
857 rapidjson::Writer<rapidjson::StringBuffer> w(output);
858 w.StartObject();
859 w.Key("jsonrpc");
860 w.String("2.0");
861 w.Key("id");
862 switch (id.type) {
863 case RequestId::kNone:
864 w.Null();
865 break;
866 case RequestId::kInt:
867 w.Int64(atoll(id.value.c_str()));
868 break;
869 case RequestId::kString:
870 w.String(id.value.c_str(), id.value.size());
871 break;
872 }
873 w.Key(key);
874 JsonWriter writer(&w);
875 fn(writer);
876 w.EndObject();
877 if (id.valid())
878 LOG_V(2) << "respond to RequestMessage: " << id.value;
879 for_stdout->pushBack(output.GetString());
880 }
881
reply(const RequestId & id,const std::function<void (JsonWriter &)> & fn)882 void reply(const RequestId &id, const std::function<void(JsonWriter &)> &fn) {
883 reply(id, "result", fn);
884 }
885
replyError(const RequestId & id,const std::function<void (JsonWriter &)> & fn)886 void replyError(const RequestId &id,
887 const std::function<void(JsonWriter &)> &fn) {
888 reply(id, "error", fn);
889 }
890 } // namespace pipeline
891 } // namespace ccls
892