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