mirror of
https://github.com/MaskRay/ccls.git
synced 2025-01-19 03:55:49 +00:00
Introduce MessageHandler abstraction. Mainly just code reorg.
Only the initialize request uses it so far, but this will enable pulling quite a bit of code out of command_line.cc.
This commit is contained in:
parent
8b5d9d33ab
commit
3599a831b1
36
src/cache_loader.cc
Normal file
36
src/cache_loader.cc
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "cache_loader.h"
|
||||||
|
|
||||||
|
#include "cache.h"
|
||||||
|
#include "indexer.h"
|
||||||
|
|
||||||
|
CacheLoader::CacheLoader(Config* config) : config_(config) {}
|
||||||
|
|
||||||
|
IndexFile* CacheLoader::TryLoad(const std::string& path) {
|
||||||
|
auto it = caches.find(path);
|
||||||
|
if (it != caches.end())
|
||||||
|
return it->second.get();
|
||||||
|
|
||||||
|
std::unique_ptr<IndexFile> cache = LoadCachedIndex(config_, path);
|
||||||
|
if (!cache)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
caches[path] = std::move(cache);
|
||||||
|
return caches[path].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IndexFile> CacheLoader::TryTakeOrLoad(const std::string& path) {
|
||||||
|
auto it = caches.find(path);
|
||||||
|
if (it != caches.end()) {
|
||||||
|
auto result = std::move(it->second);
|
||||||
|
caches.erase(it);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCachedIndex(config_, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IndexFile> CacheLoader::TakeOrLoad(const std::string& path) {
|
||||||
|
auto result = TryTakeOrLoad(path);
|
||||||
|
assert(result);
|
||||||
|
return result;
|
||||||
|
}
|
23
src/cache_loader.h
Normal file
23
src/cache_loader.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Manages loading caches from file paths for the indexer process.
|
||||||
|
struct CacheLoader {
|
||||||
|
explicit CacheLoader(Config* config);
|
||||||
|
|
||||||
|
IndexFile* TryLoad(const std::string& path);
|
||||||
|
|
||||||
|
// Takes the existing cache or loads the cache at |path|. May return nullptr
|
||||||
|
// if the cache does not exist.
|
||||||
|
std::unique_ptr<IndexFile> TryTakeOrLoad(const std::string& path);
|
||||||
|
|
||||||
|
// Takes the existing cache or loads the cache at |path|. Asserts the cache
|
||||||
|
// exists.
|
||||||
|
std::unique_ptr<IndexFile> TakeOrLoad(const std::string& path);
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<IndexFile>> caches;
|
||||||
|
Config* config_;
|
||||||
|
};
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#include "atomic_object.h"
|
#include "atomic_object.h"
|
||||||
#include "clang_index.h"
|
#include "clang_index.h"
|
||||||
#include "clang_translation_unit.h"
|
#include "clang_translation_unit.h"
|
||||||
|
12
src/code_complete_cache.cc
Normal file
12
src/code_complete_cache.cc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "code_complete_cache.h"
|
||||||
|
|
||||||
|
void CodeCompleteCache::WithLock(std::function<void()> action) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CodeCompleteCache::IsCacheValid(lsTextDocumentPositionParams position) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return cached_path_ == position.textDocument.uri.GetPath() &&
|
||||||
|
cached_completion_position_ == position.position;
|
||||||
|
}
|
25
src/code_complete_cache.h
Normal file
25
src/code_complete_cache.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "language_server_api.h"
|
||||||
|
|
||||||
|
#include <optional.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
using namespace std::experimental;
|
||||||
|
using std::experimental::nullopt;
|
||||||
|
|
||||||
|
// Cached completion information, so we can give fast completion results when
|
||||||
|
// the user erases a character. vscode will resend the completion request if
|
||||||
|
// that happens.
|
||||||
|
struct CodeCompleteCache {
|
||||||
|
// NOTE: Make sure to access these variables under |WithLock|.
|
||||||
|
optional<std::string> cached_path_;
|
||||||
|
optional<lsPosition> cached_completion_position_;
|
||||||
|
NonElidedVector<lsCompletionItem> cached_results_;
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
|
||||||
|
void WithLock(std::function<void()> action);
|
||||||
|
bool IsCacheValid(lsTextDocumentPositionParams position);
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
// TODO: cleanup includes
|
// TODO: cleanup includes
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
|
#include "cache_loader.h"
|
||||||
#include "clang_complete.h"
|
#include "clang_complete.h"
|
||||||
#include "file_consumer.h"
|
#include "file_consumer.h"
|
||||||
#include "include_complete.h"
|
#include "include_complete.h"
|
||||||
@ -9,6 +10,7 @@
|
|||||||
#include "lex_utils.h"
|
#include "lex_utils.h"
|
||||||
#include "lru_cache.h"
|
#include "lru_cache.h"
|
||||||
#include "match.h"
|
#include "match.h"
|
||||||
|
#include "message_handler.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
@ -19,6 +21,7 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
#include "threaded_queue.h"
|
#include "threaded_queue.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
|
#include "timestamp_manager.h"
|
||||||
#include "work_thread.h"
|
#include "work_thread.h"
|
||||||
#include "working_files.h"
|
#include "working_files.h"
|
||||||
|
|
||||||
@ -50,35 +53,9 @@ namespace {
|
|||||||
|
|
||||||
std::vector<std::string> kEmptyArgs;
|
std::vector<std::string> kEmptyArgs;
|
||||||
|
|
||||||
// Expected client version. We show an error if this doesn't match.
|
|
||||||
const int kExpectedClientVersion = 3;
|
|
||||||
|
|
||||||
// If true stdout will be printed to stderr.
|
// If true stdout will be printed to stderr.
|
||||||
bool g_log_stdin_stdout_to_stderr = false;
|
bool g_log_stdin_stdout_to_stderr = false;
|
||||||
|
|
||||||
// Cached completion information, so we can give fast completion results when
|
|
||||||
// the user erases a character. vscode will resend the completion request if
|
|
||||||
// that happens.
|
|
||||||
struct CodeCompleteCache {
|
|
||||||
// NOTE: Make sure to access these variables under |WithLock|.
|
|
||||||
optional<std::string> cached_path_;
|
|
||||||
optional<lsPosition> cached_completion_position_;
|
|
||||||
NonElidedVector<lsCompletionItem> cached_results_;
|
|
||||||
|
|
||||||
std::mutex mutex_;
|
|
||||||
|
|
||||||
void WithLock(std::function<void()> action) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsCacheValid(lsTextDocumentPositionParams position) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
return cached_path_ == position.textDocument.uri.GetPath() &&
|
|
||||||
cached_completion_position_ == position.position;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This function returns true if e2e timing should be displayed for the given
|
// This function returns true if e2e timing should be displayed for the given
|
||||||
// IpcId.
|
// IpcId.
|
||||||
bool ShouldDisplayIpcTiming(IpcId id) {
|
bool ShouldDisplayIpcTiming(IpcId id) {
|
||||||
@ -604,103 +581,8 @@ void FilterCompletionResponse(Out_TextDocumentComplete* complete_response,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Index_Request {
|
|
||||||
std::string path;
|
|
||||||
// TODO: make |args| a string that is parsed lazily.
|
|
||||||
std::vector<std::string> args;
|
|
||||||
bool is_interactive;
|
|
||||||
optional<std::string> contents; // Preloaded contents. Useful for tests.
|
|
||||||
|
|
||||||
Index_Request(const std::string& path,
|
|
||||||
const std::vector<std::string>& args,
|
|
||||||
bool is_interactive,
|
|
||||||
optional<std::string> contents)
|
|
||||||
: path(path),
|
|
||||||
args(args),
|
|
||||||
is_interactive(is_interactive),
|
|
||||||
contents(contents) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Index_DoIdMap {
|
|
||||||
std::unique_ptr<IndexFile> current;
|
|
||||||
std::unique_ptr<IndexFile> previous;
|
|
||||||
|
|
||||||
PerformanceImportFile perf;
|
|
||||||
bool is_interactive = false;
|
|
||||||
bool write_to_disk = false;
|
|
||||||
bool load_previous = false;
|
|
||||||
|
|
||||||
Index_DoIdMap(std::unique_ptr<IndexFile> current,
|
|
||||||
PerformanceImportFile perf,
|
|
||||||
bool is_interactive,
|
|
||||||
bool write_to_disk)
|
|
||||||
: current(std::move(current)),
|
|
||||||
perf(perf),
|
|
||||||
is_interactive(is_interactive),
|
|
||||||
write_to_disk(write_to_disk) {
|
|
||||||
assert(this->current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Index_OnIdMapped {
|
|
||||||
struct File {
|
|
||||||
std::unique_ptr<IndexFile> file;
|
|
||||||
std::unique_ptr<IdMap> ids;
|
|
||||||
|
|
||||||
File(std::unique_ptr<IndexFile> file, std::unique_ptr<IdMap> ids)
|
|
||||||
: file(std::move(file)), ids(std::move(ids)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<File> previous;
|
|
||||||
std::unique_ptr<File> current;
|
|
||||||
|
|
||||||
PerformanceImportFile perf;
|
|
||||||
bool is_interactive;
|
|
||||||
bool write_to_disk;
|
|
||||||
|
|
||||||
Index_OnIdMapped(PerformanceImportFile perf,
|
|
||||||
bool is_interactive,
|
|
||||||
bool write_to_disk)
|
|
||||||
: perf(perf),
|
|
||||||
is_interactive(is_interactive),
|
|
||||||
write_to_disk(write_to_disk) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Index_OnIndexed {
|
|
||||||
IndexUpdate update;
|
|
||||||
PerformanceImportFile perf;
|
|
||||||
|
|
||||||
Index_OnIndexed(IndexUpdate& update, PerformanceImportFile perf)
|
|
||||||
: update(update), perf(perf) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QueueManager {
|
|
||||||
using Index_RequestQueue = ThreadedQueue<Index_Request>;
|
|
||||||
using Index_DoIdMapQueue = ThreadedQueue<Index_DoIdMap>;
|
|
||||||
using Index_OnIdMappedQueue = ThreadedQueue<Index_OnIdMapped>;
|
|
||||||
using Index_OnIndexedQueue = ThreadedQueue<Index_OnIndexed>;
|
|
||||||
|
|
||||||
Index_RequestQueue index_request;
|
|
||||||
Index_DoIdMapQueue do_id_map;
|
|
||||||
Index_DoIdMapQueue load_previous_index;
|
|
||||||
Index_OnIdMappedQueue on_id_mapped;
|
|
||||||
Index_OnIndexedQueue on_indexed;
|
|
||||||
|
|
||||||
QueueManager(MultiQueueWaiter* waiter)
|
|
||||||
: index_request(waiter),
|
|
||||||
do_id_map(waiter),
|
|
||||||
load_previous_index(waiter),
|
|
||||||
on_id_mapped(waiter),
|
|
||||||
on_indexed(waiter) {}
|
|
||||||
|
|
||||||
bool HasWork() {
|
|
||||||
return !index_request.IsEmpty() || !do_id_map.IsEmpty() ||
|
|
||||||
!load_previous_index.IsEmpty() || !on_id_mapped.IsEmpty() ||
|
|
||||||
!on_indexed.IsEmpty();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void RegisterMessageTypes() {
|
void RegisterMessageTypes() {
|
||||||
|
// TODO: use automatic registration similar to MessageHandler.
|
||||||
MessageRegistry::instance()->Register<Ipc_CancelRequest>();
|
MessageRegistry::instance()->Register<Ipc_CancelRequest>();
|
||||||
MessageRegistry::instance()->Register<Ipc_InitializeRequest>();
|
MessageRegistry::instance()->Register<Ipc_InitializeRequest>();
|
||||||
MessageRegistry::instance()->Register<Ipc_InitializedNotification>();
|
MessageRegistry::instance()->Register<Ipc_InitializedNotification>();
|
||||||
@ -736,136 +618,6 @@ void RegisterMessageTypes() {
|
|||||||
MessageRegistry::instance()->Register<Ipc_CqueryExitWhenIdle>();
|
MessageRegistry::instance()->Register<Ipc_CqueryExitWhenIdle>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manages files inside of the indexing pipeline so we don't have the same file
|
|
||||||
// being imported multiple times.
|
|
||||||
//
|
|
||||||
// NOTE: This is not thread safe and should only be used on the querydb thread.
|
|
||||||
struct ImportManager {
|
|
||||||
// Try to mark the given dependency as imported. A dependency can only ever be
|
|
||||||
// imported once.
|
|
||||||
bool TryMarkDependencyImported(const std::string& path) {
|
|
||||||
std::lock_guard<std::mutex> lock(depdency_mutex_);
|
|
||||||
return depdency_imported_.insert(path).second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to import the given file into querydb. We should only ever be
|
|
||||||
// importing a file into querydb once per file. Returns true if the file
|
|
||||||
// can be imported.
|
|
||||||
bool StartQueryDbImport(const std::string& path) {
|
|
||||||
return querydb_processing_.insert(path).second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The file has been fully imported and can be imported again later on.
|
|
||||||
void DoneQueryDbImport(const std::string& path) {
|
|
||||||
querydb_processing_.erase(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if there any any files currently being imported.
|
|
||||||
bool HasActiveQuerydbImports() { return !querydb_processing_.empty(); }
|
|
||||||
|
|
||||||
std::unordered_set<std::string> querydb_processing_;
|
|
||||||
|
|
||||||
// TODO: use std::shared_mutex so we can have multiple readers.
|
|
||||||
std::mutex depdency_mutex_;
|
|
||||||
std::unordered_set<std::string> depdency_imported_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Manages loading caches from file paths for the indexer process.
|
|
||||||
struct CacheLoader {
|
|
||||||
explicit CacheLoader(Config* config) : config_(config) {}
|
|
||||||
|
|
||||||
IndexFile* TryLoad(const std::string& path) {
|
|
||||||
auto it = caches.find(path);
|
|
||||||
if (it != caches.end())
|
|
||||||
return it->second.get();
|
|
||||||
|
|
||||||
std::unique_ptr<IndexFile> cache = LoadCachedIndex(config_, path);
|
|
||||||
if (!cache)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
caches[path] = std::move(cache);
|
|
||||||
return caches[path].get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes the existing cache or loads the cache at |path|. May return nullptr
|
|
||||||
// if the cache does not exist.
|
|
||||||
std::unique_ptr<IndexFile> TryTakeOrLoad(const std::string& path) {
|
|
||||||
auto it = caches.find(path);
|
|
||||||
if (it != caches.end()) {
|
|
||||||
auto result = std::move(it->second);
|
|
||||||
caches.erase(it);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadCachedIndex(config_, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes the existing cache or loads the cache at |path|. Asserts the cache
|
|
||||||
// exists.
|
|
||||||
std::unique_ptr<IndexFile> TakeOrLoad(const std::string& path) {
|
|
||||||
auto result = TryTakeOrLoad(path);
|
|
||||||
assert(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::unique_ptr<IndexFile>> caches;
|
|
||||||
Config* config_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Caches timestamps of cc files so we can avoid a filesystem reads. This is
|
|
||||||
// important for import perf, as during dependency checking the same files are
|
|
||||||
// checked over and over again if they are common headers.
|
|
||||||
struct TimestampManager {
|
|
||||||
optional<int64_t> GetLastCachedModificationTime(CacheLoader* cache_loader,
|
|
||||||
const std::string& path) {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(mutex_);
|
|
||||||
auto it = timestamps_.find(path);
|
|
||||||
if (it != timestamps_.end())
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
IndexFile* file = cache_loader->TryLoad(path);
|
|
||||||
if (!file)
|
|
||||||
return nullopt;
|
|
||||||
|
|
||||||
UpdateCachedModificationTime(path, file->last_modification_time);
|
|
||||||
return file->last_modification_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateCachedModificationTime(const std::string& path,
|
|
||||||
int64_t timestamp) {
|
|
||||||
std::lock_guard<std::mutex> guard(mutex_);
|
|
||||||
timestamps_[path] = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use std::shared_mutex so we can have multiple readers.
|
|
||||||
std::mutex mutex_;
|
|
||||||
std::unordered_map<std::string, int64_t> timestamps_;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IndexManager {
|
|
||||||
std::unordered_set<std::string> files_being_indexed_;
|
|
||||||
std::mutex mutex_;
|
|
||||||
|
|
||||||
// Marks a file as being indexed. Returns true if the file is not already
|
|
||||||
// being indexed.
|
|
||||||
bool MarkIndex(const std::string& path) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
return files_being_indexed_.insert(path).second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarks a file as being indexed, so it can get indexed again in the
|
|
||||||
// future.
|
|
||||||
void ClearIndex(const std::string& path) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
auto it = files_being_indexed_.find(path);
|
|
||||||
assert(it != files_being_indexed_.end());
|
|
||||||
files_being_indexed_.erase(it);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send indexing progress to client if reporting is enabled.
|
// Send indexing progress to client if reporting is enabled.
|
||||||
void EmitProgress(Config* config, QueueManager* queue) {
|
void EmitProgress(Config* config, QueueManager* queue) {
|
||||||
if (config->enableProgressReports) {
|
if (config->enableProgressReports) {
|
||||||
@ -1487,183 +1239,17 @@ bool QueryDbMainLoop(Config* config,
|
|||||||
for (auto& message : messages) {
|
for (auto& message : messages) {
|
||||||
did_work = true;
|
did_work = true;
|
||||||
|
|
||||||
switch (message->method_id) {
|
for (MessageHandler* handler : *MessageHandler::message_handlers) {
|
||||||
case IpcId::Initialize: {
|
if (handler->GetId() == message->method_id) {
|
||||||
auto request = message->As<Ipc_InitializeRequest>();
|
handler->Run(std::move(message));
|
||||||
|
|
||||||
// Log initialization parameters.
|
|
||||||
rapidjson::StringBuffer output;
|
|
||||||
Writer writer(output);
|
|
||||||
Reflect(writer, request->params.initializationOptions);
|
|
||||||
LOG_S(INFO) << "Init parameters: " << output.GetString();
|
|
||||||
|
|
||||||
if (request->params.rootUri) {
|
|
||||||
std::string project_path = request->params.rootUri->GetPath();
|
|
||||||
LOG_S(INFO) << "[querydb] Initialize in directory " << project_path
|
|
||||||
<< " with uri " << request->params.rootUri->raw_uri;
|
|
||||||
|
|
||||||
if (!request->params.initializationOptions) {
|
|
||||||
LOG_S(FATAL) << "Initialization parameters (particularily "
|
|
||||||
"cacheDirectory) are required";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
*config = *request->params.initializationOptions;
|
|
||||||
|
|
||||||
// Check client version.
|
|
||||||
if (config->clientVersion.has_value() &&
|
|
||||||
*config->clientVersion != kExpectedClientVersion) {
|
|
||||||
Out_ShowLogMessage out;
|
|
||||||
out.display_type = Out_ShowLogMessage::DisplayType::Show;
|
|
||||||
out.params.type = lsMessageType::Error;
|
|
||||||
out.params.message =
|
|
||||||
"cquery client (v" + std::to_string(*config->clientVersion) +
|
|
||||||
") and server (v" + std::to_string(kExpectedClientVersion) +
|
|
||||||
") version mismatch. Please update ";
|
|
||||||
if (config->clientVersion > kExpectedClientVersion)
|
|
||||||
out.params.message += "the cquery binary.";
|
|
||||||
else
|
|
||||||
out.params.message +=
|
|
||||||
"your extension client (VSIX file). Make sure to uninstall "
|
|
||||||
"the cquery extension and restart vscode before "
|
|
||||||
"reinstalling.";
|
|
||||||
out.Write(std::cout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure cache directory is valid.
|
|
||||||
if (config->cacheDirectory.empty()) {
|
|
||||||
LOG_S(FATAL) << "Exiting; no cache directory";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
config->cacheDirectory = NormalizePath(config->cacheDirectory);
|
|
||||||
EnsureEndsInSlash(config->cacheDirectory);
|
|
||||||
|
|
||||||
// Ensure there is a resource directory.
|
|
||||||
if (config->resourceDirectory.empty()) {
|
|
||||||
config->resourceDirectory = GetWorkingDirectory();
|
|
||||||
#if defined(_WIN32)
|
|
||||||
config->resourceDirectory +=
|
|
||||||
std::string("../../clang_resource_dir/");
|
|
||||||
#else
|
|
||||||
config->resourceDirectory += std::string("../clang_resource_dir/");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
config->resourceDirectory = NormalizePath(config->resourceDirectory);
|
|
||||||
LOG_S(INFO) << "Using -resource-dir=" << config->resourceDirectory;
|
|
||||||
|
|
||||||
// Send initialization before starting indexers, so we don't send a
|
|
||||||
// status update too early.
|
|
||||||
// TODO: query request->params.capabilities.textDocument and support
|
|
||||||
// only things the client supports.
|
|
||||||
|
|
||||||
Out_InitializeResponse out;
|
|
||||||
out.id = request->id;
|
|
||||||
|
|
||||||
// out.result.capabilities.textDocumentSync =
|
|
||||||
// lsTextDocumentSyncOptions();
|
|
||||||
// out.result.capabilities.textDocumentSync->openClose = true;
|
|
||||||
// out.result.capabilities.textDocumentSync->change =
|
|
||||||
// lsTextDocumentSyncKind::Full;
|
|
||||||
// out.result.capabilities.textDocumentSync->willSave = true;
|
|
||||||
// out.result.capabilities.textDocumentSync->willSaveWaitUntil =
|
|
||||||
// true;
|
|
||||||
out.result.capabilities.textDocumentSync =
|
|
||||||
lsTextDocumentSyncKind::Incremental;
|
|
||||||
|
|
||||||
out.result.capabilities.renameProvider = true;
|
|
||||||
|
|
||||||
out.result.capabilities.completionProvider = lsCompletionOptions();
|
|
||||||
out.result.capabilities.completionProvider->resolveProvider = false;
|
|
||||||
// vscode doesn't support trigger character sequences, so we use ':'
|
|
||||||
// for
|
|
||||||
// '::' and '>' for '->'. See
|
|
||||||
// https://github.com/Microsoft/language-server-protocol/issues/138.
|
|
||||||
out.result.capabilities.completionProvider->triggerCharacters = {
|
|
||||||
".", ":", ">", "#"};
|
|
||||||
|
|
||||||
out.result.capabilities.signatureHelpProvider =
|
|
||||||
lsSignatureHelpOptions();
|
|
||||||
// NOTE: If updating signature help tokens make sure to also update
|
|
||||||
// WorkingFile::FindClosestCallNameInBuffer.
|
|
||||||
out.result.capabilities.signatureHelpProvider->triggerCharacters = {
|
|
||||||
"(", ","};
|
|
||||||
|
|
||||||
out.result.capabilities.codeLensProvider = lsCodeLensOptions();
|
|
||||||
out.result.capabilities.codeLensProvider->resolveProvider = false;
|
|
||||||
|
|
||||||
out.result.capabilities.definitionProvider = true;
|
|
||||||
out.result.capabilities.documentHighlightProvider = true;
|
|
||||||
out.result.capabilities.hoverProvider = true;
|
|
||||||
out.result.capabilities.referencesProvider = true;
|
|
||||||
|
|
||||||
out.result.capabilities.codeActionProvider = true;
|
|
||||||
|
|
||||||
out.result.capabilities.documentSymbolProvider = true;
|
|
||||||
out.result.capabilities.workspaceSymbolProvider = true;
|
|
||||||
|
|
||||||
out.result.capabilities.documentLinkProvider =
|
|
||||||
lsDocumentLinkOptions();
|
|
||||||
out.result.capabilities.documentLinkProvider->resolveProvider = false;
|
|
||||||
|
|
||||||
IpcManager::WriteStdout(IpcId::Initialize, out);
|
|
||||||
|
|
||||||
// Set project root.
|
|
||||||
config->projectRoot =
|
|
||||||
NormalizePath(request->params.rootUri->GetPath());
|
|
||||||
EnsureEndsInSlash(config->projectRoot);
|
|
||||||
MakeDirectoryRecursive(config->cacheDirectory +
|
|
||||||
EscapeFileName(config->projectRoot));
|
|
||||||
|
|
||||||
// Start indexer threads.
|
|
||||||
if (config->indexerCount == 0) {
|
|
||||||
// If the user has not specified how many indexers to run, try to
|
|
||||||
// guess an appropriate value. Default to 80% utilization.
|
|
||||||
const float kDefaultTargetUtilization = 0.8;
|
|
||||||
config->indexerCount =
|
|
||||||
std::thread::hardware_concurrency() * kDefaultTargetUtilization;
|
|
||||||
if (config->indexerCount <= 0)
|
|
||||||
config->indexerCount = 1;
|
|
||||||
}
|
|
||||||
LOG_S(INFO) << "Starting " << config->indexerCount << " indexers";
|
|
||||||
for (int i = 0; i < config->indexerCount; ++i) {
|
|
||||||
WorkThread::StartThread("indexer" + std::to_string(i), [=]() {
|
|
||||||
return IndexMain(config, file_consumer_shared, timestamp_manager,
|
|
||||||
import_manager, project, working_files, waiter,
|
|
||||||
queue);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer time;
|
|
||||||
|
|
||||||
// Open up / load the project.
|
|
||||||
project->Load(config->extraClangArguments,
|
|
||||||
config->compilationDatabaseDirectory, project_path,
|
|
||||||
config->resourceDirectory);
|
|
||||||
time.ResetAndPrint("[perf] Loaded compilation entries (" +
|
|
||||||
std::to_string(project->entries.size()) +
|
|
||||||
" files)");
|
|
||||||
|
|
||||||
// Start scanning include directories before dispatching project
|
|
||||||
// files, because that takes a long time.
|
|
||||||
include_complete->Rescan();
|
|
||||||
|
|
||||||
time.Reset();
|
|
||||||
project->ForAllFilteredFiles(
|
|
||||||
config, [&](int i, const Project::Entry& entry) {
|
|
||||||
bool is_interactive =
|
|
||||||
working_files->GetFileByFilename(entry.filename) != nullptr;
|
|
||||||
queue->index_request.Enqueue(Index_Request(
|
|
||||||
entry.filename, entry.args, is_interactive, nullopt));
|
|
||||||
});
|
|
||||||
|
|
||||||
// We need to support multiple concurrent index processes.
|
|
||||||
time.ResetAndPrint("[perf] Dispatched initial index requests");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!message)
|
||||||
|
continue;
|
||||||
|
// FIXME: assert(!message), ie, verify that a handler was run.
|
||||||
|
|
||||||
|
switch (message->method_id) {
|
||||||
case IpcId::Exit: {
|
case IpcId::Exit: {
|
||||||
LOG_S(INFO) << "Exiting; got IpcId::Exit";
|
LOG_S(INFO) << "Exiting; got IpcId::Exit";
|
||||||
exit(0);
|
exit(0);
|
||||||
@ -3036,10 +2622,30 @@ void RunQueryDbThread(const std::string& bin_name,
|
|||||||
auto signature_cache = MakeUnique<CodeCompleteCache>();
|
auto signature_cache = MakeUnique<CodeCompleteCache>();
|
||||||
ImportManager import_manager;
|
ImportManager import_manager;
|
||||||
TimestampManager timestamp_manager;
|
TimestampManager timestamp_manager;
|
||||||
|
QueryDatabase db;
|
||||||
|
|
||||||
|
// Setup shared references.
|
||||||
|
for (MessageHandler* handler : *MessageHandler::message_handlers) {
|
||||||
|
handler->config = config;
|
||||||
|
handler->db = &db;
|
||||||
|
handler->exit_when_idle = &exit_when_idle;
|
||||||
|
handler->waiter = waiter;
|
||||||
|
handler->queue = queue;
|
||||||
|
handler->project = &project;
|
||||||
|
handler->file_consumer_shared = &file_consumer_shared;
|
||||||
|
handler->import_manager = &import_manager;
|
||||||
|
handler->timestamp_manager = ×tamp_manager;
|
||||||
|
handler->working_files = &working_files;
|
||||||
|
handler->clang_complete = &clang_complete;
|
||||||
|
handler->include_complete = &include_complete;
|
||||||
|
handler->global_code_complete_cache = global_code_complete_cache.get();
|
||||||
|
handler->non_global_code_complete_cache =
|
||||||
|
non_global_code_complete_cache.get();
|
||||||
|
handler->signature_cache = signature_cache.get();
|
||||||
|
}
|
||||||
|
|
||||||
// Run query db main loop.
|
// Run query db main loop.
|
||||||
SetCurrentThreadName("querydb");
|
SetCurrentThreadName("querydb");
|
||||||
QueryDatabase db;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
bool did_work = QueryDbMainLoop(
|
bool did_work = QueryDbMainLoop(
|
||||||
config, &db, &exit_when_idle, waiter, queue, &project,
|
config, &db, &exit_when_idle, waiter, queue, &project,
|
||||||
|
@ -107,3 +107,6 @@ MAKE_REFLECT_STRUCT(Config,
|
|||||||
|
|
||||||
clientVersion,
|
clientVersion,
|
||||||
enableSnippetInsertion);
|
enableSnippetInsertion);
|
||||||
|
|
||||||
|
// Expected client version. We show an error if this doesn't match.
|
||||||
|
constexpr const int kExpectedClientVersion = 3;
|
22
src/entry_points.h
Normal file
22
src/entry_points.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "file_consumer.h"
|
||||||
|
#include "import_manager.h"
|
||||||
|
#include "ipc_manager.h"
|
||||||
|
#include "project.h"
|
||||||
|
#include "threaded_queue.h"
|
||||||
|
#include "timestamp_manager.h"
|
||||||
|
#include "work_thread.h"
|
||||||
|
#include "working_files.h"
|
||||||
|
|
||||||
|
// Contains declarations for some of the thread-main functions.
|
||||||
|
|
||||||
|
WorkThread::Result IndexMain(Config* config,
|
||||||
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
|
TimestampManager* timestamp_manager,
|
||||||
|
ImportManager* import_manager,
|
||||||
|
Project* project,
|
||||||
|
WorkingFiles* working_files,
|
||||||
|
MultiQueueWaiter* waiter,
|
||||||
|
QueueManager* queue);
|
18
src/import_manager.cc
Normal file
18
src/import_manager.cc
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#include "import_manager.h"
|
||||||
|
|
||||||
|
bool ImportManager::TryMarkDependencyImported(const std::string& path) {
|
||||||
|
std::lock_guard<std::mutex> lock(depdency_mutex_);
|
||||||
|
return depdency_imported_.insert(path).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImportManager::StartQueryDbImport(const std::string& path) {
|
||||||
|
return querydb_processing_.insert(path).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImportManager::DoneQueryDbImport(const std::string& path) {
|
||||||
|
querydb_processing_.erase(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImportManager::HasActiveQuerydbImports() {
|
||||||
|
return !querydb_processing_.empty();
|
||||||
|
}
|
32
src/import_manager.h
Normal file
32
src/import_manager.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
// Manages files inside of the indexing pipeline so we don't have the same file
|
||||||
|
// being imported multiple times.
|
||||||
|
//
|
||||||
|
// NOTE: This is not thread safe and should only be used on the querydb thread.
|
||||||
|
struct ImportManager {
|
||||||
|
// Try to mark the given dependency as imported. A dependency can only ever be
|
||||||
|
// imported once.
|
||||||
|
bool TryMarkDependencyImported(const std::string& path);
|
||||||
|
|
||||||
|
// Try to import the given file into querydb. We should only ever be
|
||||||
|
// importing a file into querydb once per file. Returns true if the file
|
||||||
|
// can be imported.
|
||||||
|
bool StartQueryDbImport(const std::string& path);
|
||||||
|
|
||||||
|
// The file has been fully imported and can be imported again later on.
|
||||||
|
void DoneQueryDbImport(const std::string& path);
|
||||||
|
|
||||||
|
// Returns true if there any any files currently being imported.
|
||||||
|
bool HasActiveQuerydbImports();
|
||||||
|
|
||||||
|
std::unordered_set<std::string> querydb_processing_;
|
||||||
|
|
||||||
|
// TODO: use std::shared_mutex so we can have multiple readers.
|
||||||
|
std::mutex depdency_mutex_;
|
||||||
|
std::unordered_set<std::string> depdency_imported_;
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
#include "ipc_manager.h"
|
#include "ipc_manager.h"
|
||||||
|
|
||||||
#include "language_server_api.h"
|
#include "language_server_api.h"
|
||||||
|
#include "query.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
@ -28,4 +29,52 @@ void IpcManager::WriteStdout(IpcId id, lsBaseOutMessage& response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IpcManager::IpcManager(MultiQueueWaiter* waiter)
|
IpcManager::IpcManager(MultiQueueWaiter* waiter)
|
||||||
: for_stdout(waiter), for_querydb(waiter) {}
|
: for_stdout(waiter), for_querydb(waiter) {}
|
||||||
|
|
||||||
|
Index_Request::Index_Request(const std::string& path,
|
||||||
|
const std::vector<std::string>& args,
|
||||||
|
bool is_interactive,
|
||||||
|
optional<std::string> contents)
|
||||||
|
: path(path),
|
||||||
|
args(args),
|
||||||
|
is_interactive(is_interactive),
|
||||||
|
contents(contents) {}
|
||||||
|
|
||||||
|
Index_DoIdMap::Index_DoIdMap(std::unique_ptr<IndexFile> current,
|
||||||
|
PerformanceImportFile perf,
|
||||||
|
bool is_interactive,
|
||||||
|
bool write_to_disk)
|
||||||
|
: current(std::move(current)),
|
||||||
|
perf(perf),
|
||||||
|
is_interactive(is_interactive),
|
||||||
|
write_to_disk(write_to_disk) {
|
||||||
|
assert(this->current);
|
||||||
|
}
|
||||||
|
|
||||||
|
Index_OnIdMapped::File::File(std::unique_ptr<IndexFile> file,
|
||||||
|
std::unique_ptr<IdMap> ids)
|
||||||
|
: file(std::move(file)), ids(std::move(ids)) {}
|
||||||
|
|
||||||
|
Index_OnIdMapped::Index_OnIdMapped(PerformanceImportFile perf,
|
||||||
|
bool is_interactive,
|
||||||
|
bool write_to_disk)
|
||||||
|
: perf(perf),
|
||||||
|
is_interactive(is_interactive),
|
||||||
|
write_to_disk(write_to_disk) {}
|
||||||
|
|
||||||
|
Index_OnIndexed::Index_OnIndexed(IndexUpdate& update,
|
||||||
|
PerformanceImportFile perf)
|
||||||
|
: update(update), perf(perf) {}
|
||||||
|
|
||||||
|
QueueManager::QueueManager(MultiQueueWaiter* waiter)
|
||||||
|
: index_request(waiter),
|
||||||
|
do_id_map(waiter),
|
||||||
|
load_previous_index(waiter),
|
||||||
|
on_id_mapped(waiter),
|
||||||
|
on_indexed(waiter) {}
|
||||||
|
|
||||||
|
bool QueueManager::HasWork() {
|
||||||
|
return !index_request.IsEmpty() || !do_id_map.IsEmpty() ||
|
||||||
|
!load_previous_index.IsEmpty() || !on_id_mapped.IsEmpty() ||
|
||||||
|
!on_indexed.IsEmpty();
|
||||||
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
#include "performance.h"
|
||||||
|
#include "query.h"
|
||||||
#include "threaded_queue.h"
|
#include "threaded_queue.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
// TODO/FIXME: Merge IpcManager and QueueManager.
|
||||||
|
|
||||||
struct lsBaseOutMessage;
|
struct lsBaseOutMessage;
|
||||||
|
|
||||||
struct IpcManager {
|
struct IpcManager {
|
||||||
@ -26,3 +30,75 @@ struct IpcManager {
|
|||||||
|
|
||||||
static IpcManager* instance_;
|
static IpcManager* instance_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Index_Request {
|
||||||
|
std::string path;
|
||||||
|
// TODO: make |args| a string that is parsed lazily.
|
||||||
|
std::vector<std::string> args;
|
||||||
|
bool is_interactive;
|
||||||
|
optional<std::string> contents; // Preloaded contents. Useful for tests.
|
||||||
|
|
||||||
|
Index_Request(const std::string& path,
|
||||||
|
const std::vector<std::string>& args,
|
||||||
|
bool is_interactive,
|
||||||
|
optional<std::string> contents);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Index_DoIdMap {
|
||||||
|
std::unique_ptr<IndexFile> current;
|
||||||
|
std::unique_ptr<IndexFile> previous;
|
||||||
|
|
||||||
|
PerformanceImportFile perf;
|
||||||
|
bool is_interactive = false;
|
||||||
|
bool write_to_disk = false;
|
||||||
|
bool load_previous = false;
|
||||||
|
|
||||||
|
Index_DoIdMap(std::unique_ptr<IndexFile> current,
|
||||||
|
PerformanceImportFile perf,
|
||||||
|
bool is_interactive,
|
||||||
|
bool write_to_disk);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Index_OnIdMapped {
|
||||||
|
struct File {
|
||||||
|
std::unique_ptr<IndexFile> file;
|
||||||
|
std::unique_ptr<IdMap> ids;
|
||||||
|
|
||||||
|
File(std::unique_ptr<IndexFile> file, std::unique_ptr<IdMap> ids);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<File> previous;
|
||||||
|
std::unique_ptr<File> current;
|
||||||
|
|
||||||
|
PerformanceImportFile perf;
|
||||||
|
bool is_interactive;
|
||||||
|
bool write_to_disk;
|
||||||
|
|
||||||
|
Index_OnIdMapped(PerformanceImportFile perf,
|
||||||
|
bool is_interactive,
|
||||||
|
bool write_to_disk);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Index_OnIndexed {
|
||||||
|
IndexUpdate update;
|
||||||
|
PerformanceImportFile perf;
|
||||||
|
|
||||||
|
Index_OnIndexed(IndexUpdate& update, PerformanceImportFile perf);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct QueueManager {
|
||||||
|
using Index_RequestQueue = ThreadedQueue<Index_Request>;
|
||||||
|
using Index_DoIdMapQueue = ThreadedQueue<Index_DoIdMap>;
|
||||||
|
using Index_OnIdMappedQueue = ThreadedQueue<Index_OnIdMapped>;
|
||||||
|
using Index_OnIndexedQueue = ThreadedQueue<Index_OnIndexed>;
|
||||||
|
|
||||||
|
Index_RequestQueue index_request;
|
||||||
|
Index_DoIdMapQueue do_id_map;
|
||||||
|
Index_DoIdMapQueue load_previous_index;
|
||||||
|
Index_OnIdMappedQueue on_id_mapped;
|
||||||
|
Index_OnIndexedQueue on_indexed;
|
||||||
|
|
||||||
|
QueueManager(MultiQueueWaiter* waiter);
|
||||||
|
|
||||||
|
bool HasWork();
|
||||||
|
};
|
||||||
|
12
src/message_handler.cc
Normal file
12
src/message_handler.cc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "message_handler.h"
|
||||||
|
|
||||||
|
MessageHandler::MessageHandler() {
|
||||||
|
// Dynamically allocate |message_handlers|, otherwise there will be static
|
||||||
|
// initialization order races.
|
||||||
|
if (!message_handlers)
|
||||||
|
message_handlers = new std::vector<MessageHandler*>();
|
||||||
|
message_handlers->push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
std::vector<MessageHandler*>* MessageHandler::message_handlers = nullptr;
|
64
src/message_handler.h
Normal file
64
src/message_handler.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cache_loader.h"
|
||||||
|
#include "clang_complete.h"
|
||||||
|
#include "code_complete_cache.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "import_manager.h"
|
||||||
|
#include "include_complete.h"
|
||||||
|
#include "ipc_manager.h"
|
||||||
|
#include "project.h"
|
||||||
|
#include "query.h"
|
||||||
|
#include "threaded_queue.h"
|
||||||
|
#include "timestamp_manager.h"
|
||||||
|
#include "working_files.h"
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// struct FooHandler : MessageHandler {
|
||||||
|
// // ...
|
||||||
|
// };
|
||||||
|
// REGISTER_MESSAGE_HANDLER(FooHandler);
|
||||||
|
//
|
||||||
|
// Then there will be a global FooHandler instance in
|
||||||
|
// |MessageHandler::message_handlers|.
|
||||||
|
|
||||||
|
#define REGISTER_MESSAGE_HANDLER(type) \
|
||||||
|
static type type##message_handler_instance_;
|
||||||
|
|
||||||
|
struct MessageHandler {
|
||||||
|
Config* config = nullptr;
|
||||||
|
QueryDatabase* db = nullptr;
|
||||||
|
bool* exit_when_idle = nullptr;
|
||||||
|
MultiQueueWaiter* waiter = nullptr;
|
||||||
|
QueueManager* queue = nullptr;
|
||||||
|
Project* project = nullptr;
|
||||||
|
FileConsumer::SharedState* file_consumer_shared = nullptr;
|
||||||
|
ImportManager* import_manager = nullptr;
|
||||||
|
TimestampManager* timestamp_manager = nullptr;
|
||||||
|
WorkingFiles* working_files = nullptr;
|
||||||
|
ClangCompleteManager* clang_complete = nullptr;
|
||||||
|
IncludeComplete* include_complete = nullptr;
|
||||||
|
CodeCompleteCache* global_code_complete_cache = nullptr;
|
||||||
|
CodeCompleteCache* non_global_code_complete_cache = nullptr;
|
||||||
|
CodeCompleteCache* signature_cache = nullptr;
|
||||||
|
|
||||||
|
virtual IpcId GetId() const = 0;
|
||||||
|
virtual void Run(std::unique_ptr<BaseIpcMessage> message) = 0;
|
||||||
|
|
||||||
|
static std::vector<MessageHandler*>* message_handlers;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MessageHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TMessage>
|
||||||
|
struct BaseMessageHandler : MessageHandler {
|
||||||
|
virtual void Run(TMessage* message) = 0;
|
||||||
|
|
||||||
|
// MessageHandler:
|
||||||
|
IpcId GetId() const override { return TMessage::kIpcId; }
|
||||||
|
void Run(std::unique_ptr<BaseIpcMessage> message) override {
|
||||||
|
Run(message->As<TMessage>());
|
||||||
|
}
|
||||||
|
};
|
176
src/messages/initialize.cc
Normal file
176
src/messages/initialize.cc
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
#include "entry_points.h"
|
||||||
|
#include "message_handler.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
#include <loguru.hpp>
|
||||||
|
|
||||||
|
struct InitializeHandler : BaseMessageHandler<Ipc_InitializeRequest> {
|
||||||
|
void Run(Ipc_InitializeRequest* request) override {
|
||||||
|
// Log initialization parameters.
|
||||||
|
rapidjson::StringBuffer output;
|
||||||
|
Writer writer(output);
|
||||||
|
Reflect(writer, request->params.initializationOptions);
|
||||||
|
LOG_S(INFO) << "Init parameters: " << output.GetString();
|
||||||
|
|
||||||
|
if (request->params.rootUri) {
|
||||||
|
std::string project_path = request->params.rootUri->GetPath();
|
||||||
|
LOG_S(INFO) << "[querydb] Initialize in directory " << project_path
|
||||||
|
<< " with uri " << request->params.rootUri->raw_uri;
|
||||||
|
|
||||||
|
if (!request->params.initializationOptions) {
|
||||||
|
LOG_S(FATAL) << "Initialization parameters (particularily "
|
||||||
|
"cacheDirectory) are required";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
*config = *request->params.initializationOptions;
|
||||||
|
|
||||||
|
// Check client version.
|
||||||
|
if (config->clientVersion.has_value() &&
|
||||||
|
*config->clientVersion != kExpectedClientVersion) {
|
||||||
|
Out_ShowLogMessage out;
|
||||||
|
out.display_type = Out_ShowLogMessage::DisplayType::Show;
|
||||||
|
out.params.type = lsMessageType::Error;
|
||||||
|
out.params.message =
|
||||||
|
"cquery client (v" + std::to_string(*config->clientVersion) +
|
||||||
|
") and server (v" + std::to_string(kExpectedClientVersion) +
|
||||||
|
") version mismatch. Please update ";
|
||||||
|
if (config->clientVersion > kExpectedClientVersion)
|
||||||
|
out.params.message += "the cquery binary.";
|
||||||
|
else
|
||||||
|
out.params.message +=
|
||||||
|
"your extension client (VSIX file). Make sure to uninstall "
|
||||||
|
"the cquery extension and restart vscode before "
|
||||||
|
"reinstalling.";
|
||||||
|
out.Write(std::cout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure cache directory is valid.
|
||||||
|
if (config->cacheDirectory.empty()) {
|
||||||
|
LOG_S(FATAL) << "Exiting; no cache directory";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
config->cacheDirectory = NormalizePath(config->cacheDirectory);
|
||||||
|
EnsureEndsInSlash(config->cacheDirectory);
|
||||||
|
|
||||||
|
// Ensure there is a resource directory.
|
||||||
|
if (config->resourceDirectory.empty()) {
|
||||||
|
config->resourceDirectory = GetWorkingDirectory();
|
||||||
|
#if defined(_WIN32)
|
||||||
|
config->resourceDirectory += std::string("../../clang_resource_dir/");
|
||||||
|
#else
|
||||||
|
config->resourceDirectory += std::string("../clang_resource_dir/");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
config->resourceDirectory = NormalizePath(config->resourceDirectory);
|
||||||
|
LOG_S(INFO) << "Using -resource-dir=" << config->resourceDirectory;
|
||||||
|
|
||||||
|
// Send initialization before starting indexers, so we don't send a
|
||||||
|
// status update too early.
|
||||||
|
// TODO: query request->params.capabilities.textDocument and support
|
||||||
|
// only things the client supports.
|
||||||
|
|
||||||
|
Out_InitializeResponse out;
|
||||||
|
out.id = request->id;
|
||||||
|
|
||||||
|
// out.result.capabilities.textDocumentSync =
|
||||||
|
// lsTextDocumentSyncOptions();
|
||||||
|
// out.result.capabilities.textDocumentSync->openClose = true;
|
||||||
|
// out.result.capabilities.textDocumentSync->change =
|
||||||
|
// lsTextDocumentSyncKind::Full;
|
||||||
|
// out.result.capabilities.textDocumentSync->willSave = true;
|
||||||
|
// out.result.capabilities.textDocumentSync->willSaveWaitUntil =
|
||||||
|
// true;
|
||||||
|
out.result.capabilities.textDocumentSync =
|
||||||
|
lsTextDocumentSyncKind::Incremental;
|
||||||
|
|
||||||
|
out.result.capabilities.renameProvider = true;
|
||||||
|
|
||||||
|
out.result.capabilities.completionProvider = lsCompletionOptions();
|
||||||
|
out.result.capabilities.completionProvider->resolveProvider = false;
|
||||||
|
// vscode doesn't support trigger character sequences, so we use ':'
|
||||||
|
// for
|
||||||
|
// '::' and '>' for '->'. See
|
||||||
|
// https://github.com/Microsoft/language-server-protocol/issues/138.
|
||||||
|
out.result.capabilities.completionProvider->triggerCharacters = {
|
||||||
|
".", ":", ">", "#"};
|
||||||
|
|
||||||
|
out.result.capabilities.signatureHelpProvider = lsSignatureHelpOptions();
|
||||||
|
// NOTE: If updating signature help tokens make sure to also update
|
||||||
|
// WorkingFile::FindClosestCallNameInBuffer.
|
||||||
|
out.result.capabilities.signatureHelpProvider->triggerCharacters = {"(",
|
||||||
|
","};
|
||||||
|
|
||||||
|
out.result.capabilities.codeLensProvider = lsCodeLensOptions();
|
||||||
|
out.result.capabilities.codeLensProvider->resolveProvider = false;
|
||||||
|
|
||||||
|
out.result.capabilities.definitionProvider = true;
|
||||||
|
out.result.capabilities.documentHighlightProvider = true;
|
||||||
|
out.result.capabilities.hoverProvider = true;
|
||||||
|
out.result.capabilities.referencesProvider = true;
|
||||||
|
|
||||||
|
out.result.capabilities.codeActionProvider = true;
|
||||||
|
|
||||||
|
out.result.capabilities.documentSymbolProvider = true;
|
||||||
|
out.result.capabilities.workspaceSymbolProvider = true;
|
||||||
|
|
||||||
|
out.result.capabilities.documentLinkProvider = lsDocumentLinkOptions();
|
||||||
|
out.result.capabilities.documentLinkProvider->resolveProvider = false;
|
||||||
|
|
||||||
|
IpcManager::WriteStdout(IpcId::Initialize, out);
|
||||||
|
|
||||||
|
// Set project root.
|
||||||
|
config->projectRoot = NormalizePath(request->params.rootUri->GetPath());
|
||||||
|
EnsureEndsInSlash(config->projectRoot);
|
||||||
|
MakeDirectoryRecursive(config->cacheDirectory +
|
||||||
|
EscapeFileName(config->projectRoot));
|
||||||
|
|
||||||
|
// Start indexer threads.
|
||||||
|
if (config->indexerCount == 0) {
|
||||||
|
// If the user has not specified how many indexers to run, try to
|
||||||
|
// guess an appropriate value. Default to 80% utilization.
|
||||||
|
const float kDefaultTargetUtilization = 0.8;
|
||||||
|
config->indexerCount =
|
||||||
|
std::thread::hardware_concurrency() * kDefaultTargetUtilization;
|
||||||
|
if (config->indexerCount <= 0)
|
||||||
|
config->indexerCount = 1;
|
||||||
|
}
|
||||||
|
LOG_S(INFO) << "Starting " << config->indexerCount << " indexers";
|
||||||
|
for (int i = 0; i < config->indexerCount; ++i) {
|
||||||
|
WorkThread::StartThread("indexer" + std::to_string(i), [=]() {
|
||||||
|
return IndexMain(config, file_consumer_shared, timestamp_manager,
|
||||||
|
import_manager, project, working_files, waiter,
|
||||||
|
queue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer time;
|
||||||
|
|
||||||
|
// Open up / load the project.
|
||||||
|
project->Load(config->extraClangArguments,
|
||||||
|
config->compilationDatabaseDirectory, project_path,
|
||||||
|
config->resourceDirectory);
|
||||||
|
time.ResetAndPrint("[perf] Loaded compilation entries (" +
|
||||||
|
std::to_string(project->entries.size()) + " files)");
|
||||||
|
|
||||||
|
// Start scanning include directories before dispatching project
|
||||||
|
// files, because that takes a long time.
|
||||||
|
include_complete->Rescan();
|
||||||
|
|
||||||
|
time.Reset();
|
||||||
|
project->ForAllFilteredFiles(
|
||||||
|
config, [&](int i, const Project::Entry& entry) {
|
||||||
|
bool is_interactive =
|
||||||
|
working_files->GetFileByFilename(entry.filename) != nullptr;
|
||||||
|
queue->index_request.Enqueue(Index_Request(
|
||||||
|
entry.filename, entry.args, is_interactive, nullopt));
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to support multiple concurrent index processes.
|
||||||
|
time.ResetAndPrint("[perf] Dispatched initial index requests");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
REGISTER_MESSAGE_HANDLER(InitializeHandler);
|
26
src/timestamp_manager.cc
Normal file
26
src/timestamp_manager.cc
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include "timestamp_manager.h"
|
||||||
|
|
||||||
|
#include "indexer.h"
|
||||||
|
|
||||||
|
optional<int64_t> TimestampManager::GetLastCachedModificationTime(
|
||||||
|
CacheLoader* cache_loader,
|
||||||
|
const std::string& path) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
auto it = timestamps_.find(path);
|
||||||
|
if (it != timestamps_.end())
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
IndexFile* file = cache_loader->TryLoad(path);
|
||||||
|
if (!file)
|
||||||
|
return nullopt;
|
||||||
|
|
||||||
|
UpdateCachedModificationTime(path, file->last_modification_time);
|
||||||
|
return file->last_modification_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimestampManager::UpdateCachedModificationTime(const std::string& path,
|
||||||
|
int64_t timestamp) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
timestamps_[path] = timestamp;
|
||||||
|
}
|
25
src/timestamp_manager.h
Normal file
25
src/timestamp_manager.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cache_loader.h"
|
||||||
|
|
||||||
|
#include <optional.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using namespace std::experimental;
|
||||||
|
using std::experimental::nullopt;
|
||||||
|
|
||||||
|
// Caches timestamps of cc files so we can avoid a filesystem reads. This is
|
||||||
|
// important for import perf, as during dependency checking the same files are
|
||||||
|
// checked over and over again if they are common headers.
|
||||||
|
struct TimestampManager {
|
||||||
|
optional<int64_t> GetLastCachedModificationTime(CacheLoader* cache_loader,
|
||||||
|
const std::string& path);
|
||||||
|
|
||||||
|
void UpdateCachedModificationTime(const std::string& path, int64_t timestamp);
|
||||||
|
|
||||||
|
// TODO: use std::shared_mutex so we can have multiple readers.
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, int64_t> timestamps_;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user