mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-27 10:02:03 +00:00
README
This commit is contained in:
parent
ccb5cba720
commit
86efddf032
@ -216,6 +216,7 @@ target_sources(ccls PRIVATE
|
||||
src/import_pipeline.cc
|
||||
src/include_complete.cc
|
||||
src/method.cc
|
||||
src/language.cc
|
||||
src/lex_utils.cc
|
||||
src/lsp.cc
|
||||
src/match.cc
|
||||
|
13
README.md
13
README.md
@ -4,7 +4,7 @@ ccls is a fork of cquery (originally written by Jacob Dufault),
|
||||
a C/C++/Objective-C language server.
|
||||
|
||||
* code completion (with both signature help and snippets)
|
||||
* finding [definition](src/messages/text_document_definition.cc)/[references](src/messages/text_document_references.cc)
|
||||
* [definition](src/messages/text_document_definition.cc)/[references](src/messages/text_document_references.cc), and other cross references
|
||||
* [call (caller/callee) hierarchy](src/messages/ccls_call_hierarchy.cc), [inheritance (base/derived) hierarchy](src/messages/ccls_inheritance_hierarchy.cc), [member hierarchy](src/messages/ccls_member_hierarchy.cc)
|
||||
* [symbol rename](src/messages/text_document_rename.cc)
|
||||
* [document symbols](src/messages/text_document_document_symbol.cc) and approximate search of [workspace symbol](src/messages/workspace_symbol.cc)
|
||||
@ -12,13 +12,12 @@ a C/C++/Objective-C language server.
|
||||
* diagnostics
|
||||
* code actions (clang FixIts)
|
||||
* preprocessor skipped regions
|
||||
* #include auto-complete, undefined type include insertion, include quick-jump
|
||||
(goto definition, document links)
|
||||
* auto-implement functions without a definition
|
||||
* semantic highlighting, including support for [rainbow semantic highlighting](https://medium.com/@evnbr/coding-in-color-3a6db2743a1e)
|
||||
|
||||
# >>> [Getting started](https://github.com/MaskRay/ccls/wiki/Getting-started) (CLICK HERE) <<<
|
||||
It makes use of C++17 features, has less third-party dependencies and slimmed-down code base. Cross reference features are strenghened, (see [wiki/FAQ]). It currently uses libclang to index C++ code but will switch to Clang C++ API. Refactoring and formatting are non-goals as they can be provided by clang-format, clang-include-fixer and other Clang based tools.
|
||||
|
||||
# License
|
||||
# >>> [Getting started](../../wiki/Getting-started) (CLICK HERE) <<<
|
||||
|
||||
MIT
|
||||
* [Build](../../wiki/Build)
|
||||
* [Emacs](../../wiki/Emacs)
|
||||
* [FAQ](../../wiki/FAQ)
|
||||
|
@ -718,27 +718,20 @@ std::string IndexFile::ToString() {
|
||||
return Serialize(SerializeFormat::Json, *this);
|
||||
}
|
||||
|
||||
void Uniquify(std::vector<Usr>& ids) {
|
||||
void Uniquify(std::vector<Usr>& usrs) {
|
||||
std::unordered_set<Usr> seen;
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < ids.size(); i++)
|
||||
if (seen.insert(ids[i]).second)
|
||||
ids[n++] = ids[i];
|
||||
ids.resize(n);
|
||||
for (size_t i = 0; i < usrs.size(); i++)
|
||||
if (seen.insert(usrs[i]).second)
|
||||
usrs[n++] = usrs[i];
|
||||
usrs.resize(n);
|
||||
}
|
||||
|
||||
void Uniquify(std::vector<Use>& uses) {
|
||||
union U {
|
||||
Range range = {};
|
||||
uint64_t u64;
|
||||
};
|
||||
static_assert(sizeof(Range) == 8);
|
||||
std::unordered_set<uint64_t> seen;
|
||||
std::unordered_set<Range> seen;
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < uses.size(); i++) {
|
||||
U u;
|
||||
u.range = uses[i].range;
|
||||
if (seen.insert(u.u64).second)
|
||||
if (seen.insert(uses[i].range).second)
|
||||
uses[n++] = uses[i];
|
||||
}
|
||||
uses.resize(n);
|
||||
|
@ -37,43 +37,6 @@ MAKE_REFLECT_STRUCT(Out_Progress::Params,
|
||||
activeThreads);
|
||||
MAKE_REFLECT_STRUCT(Out_Progress, jsonrpc, method, params);
|
||||
|
||||
// Instead of processing messages forever, we only process upto
|
||||
// |kIterationSize| messages of a type at one time. While the import time
|
||||
// likely stays the same, this should reduce overall queue lengths which means
|
||||
// the user gets a usable index faster.
|
||||
struct IterationLoop {
|
||||
const int kIterationSize = 100;
|
||||
int count = 0;
|
||||
|
||||
bool Next() {
|
||||
return count++ < kIterationSize;
|
||||
}
|
||||
void Reset() {
|
||||
count = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct IModificationTimestampFetcher {
|
||||
virtual ~IModificationTimestampFetcher() = default;
|
||||
virtual std::optional<int64_t> LastWriteTime(const std::string& path) = 0;
|
||||
};
|
||||
struct RealModificationTimestampFetcher : IModificationTimestampFetcher {
|
||||
// IModificationTimestamp:
|
||||
std::optional<int64_t> LastWriteTime(const std::string& path) override {
|
||||
return ::LastWriteTime(path);
|
||||
}
|
||||
};
|
||||
struct FakeModificationTimestampFetcher : IModificationTimestampFetcher {
|
||||
std::unordered_map<std::string, std::optional<int64_t>> entries;
|
||||
|
||||
// IModificationTimestamp:
|
||||
std::optional<int64_t> LastWriteTime(const std::string& path) override {
|
||||
auto it = entries.find(path);
|
||||
assert(it != entries.end());
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
long long GetCurrentTimeInMilliseconds() {
|
||||
auto time_since_epoch = Timer::Clock::now().time_since_epoch();
|
||||
long long elapsed_milliseconds =
|
||||
@ -138,7 +101,6 @@ enum class ShouldParse { Yes, No, NoSuchFile };
|
||||
ShouldParse FileNeedsParse(
|
||||
bool is_interactive,
|
||||
TimestampManager* timestamp_manager,
|
||||
IModificationTimestampFetcher* modification_timestamp_fetcher,
|
||||
const std::shared_ptr<ICacheManager>& cache_manager,
|
||||
IndexFile* opt_previous_index,
|
||||
const std::string& path,
|
||||
@ -150,8 +112,7 @@ ShouldParse FileNeedsParse(
|
||||
return "";
|
||||
};
|
||||
|
||||
std::optional<int64_t> modification_timestamp =
|
||||
modification_timestamp_fetcher->LastWriteTime(path);
|
||||
std::optional<int64_t> modification_timestamp = LastWriteTime(path);
|
||||
|
||||
// Cannot find file.
|
||||
if (!modification_timestamp)
|
||||
@ -193,7 +154,6 @@ enum CacheLoadResult { Parse, DoNotParse };
|
||||
CacheLoadResult TryLoadFromCache(
|
||||
FileConsumerSharedState* file_consumer_shared,
|
||||
TimestampManager* timestamp_manager,
|
||||
IModificationTimestampFetcher* modification_timestamp_fetcher,
|
||||
const std::shared_ptr<ICacheManager>& cache_manager,
|
||||
bool is_interactive,
|
||||
const Project::Entry& entry,
|
||||
@ -209,10 +169,9 @@ CacheLoadResult TryLoadFromCache(
|
||||
// from cache.
|
||||
|
||||
// Check timestamps and update |file_consumer_shared|.
|
||||
ShouldParse path_state = FileNeedsParse(
|
||||
is_interactive, timestamp_manager, modification_timestamp_fetcher,
|
||||
cache_manager, previous_index, path_to_index, entry.args,
|
||||
std::nullopt);
|
||||
ShouldParse path_state =
|
||||
FileNeedsParse(is_interactive, timestamp_manager, cache_manager,
|
||||
previous_index, path_to_index, entry.args, std::nullopt);
|
||||
if (path_state == ShouldParse::Yes)
|
||||
file_consumer_shared->Reset(path_to_index);
|
||||
|
||||
@ -228,8 +187,7 @@ CacheLoadResult TryLoadFromCache(
|
||||
for (const std::string& dependency : previous_index->dependencies) {
|
||||
assert(!dependency.empty());
|
||||
|
||||
if (FileNeedsParse(is_interactive, timestamp_manager,
|
||||
modification_timestamp_fetcher, cache_manager,
|
||||
if (FileNeedsParse(is_interactive, timestamp_manager, cache_manager,
|
||||
previous_index, dependency, entry.args,
|
||||
previous_index->path) == ShouldParse::Yes) {
|
||||
needs_reparse = true;
|
||||
@ -245,7 +203,7 @@ CacheLoadResult TryLoadFromCache(
|
||||
return CacheLoadResult::Parse;
|
||||
|
||||
// No timestamps changed - load directly from cache.
|
||||
LOG_S(INFO) << "Skipping parse; no timestamp change for " << path_to_index;
|
||||
LOG_S(INFO) << "load index for " << path_to_index;
|
||||
|
||||
// TODO/FIXME: real perf
|
||||
PerformanceImportFile perf;
|
||||
@ -333,7 +291,6 @@ void ParseFile(DiagnosticsEngine* diag_engine,
|
||||
WorkingFiles* working_files,
|
||||
FileConsumerSharedState* file_consumer_shared,
|
||||
TimestampManager* timestamp_manager,
|
||||
IModificationTimestampFetcher* modification_timestamp_fetcher,
|
||||
IIndexer* indexer,
|
||||
const Index_Request& request,
|
||||
const Project::Entry& entry) {
|
||||
@ -349,11 +306,9 @@ void ParseFile(DiagnosticsEngine* diag_engine,
|
||||
|
||||
// Try to load the file from cache.
|
||||
if (TryLoadFromCache(file_consumer_shared, timestamp_manager,
|
||||
modification_timestamp_fetcher, request.cache_manager,
|
||||
request.is_interactive, entry,
|
||||
path_to_index) == CacheLoadResult::DoNotParse) {
|
||||
request.cache_manager, request.is_interactive, entry,
|
||||
path_to_index) == CacheLoadResult::DoNotParse)
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_S(INFO) << "parse " << path_to_index;
|
||||
std::vector<FileContents> file_contents = PreloadFileContents(
|
||||
@ -404,7 +359,6 @@ bool IndexMain_DoParse(
|
||||
WorkingFiles* working_files,
|
||||
FileConsumerSharedState* file_consumer_shared,
|
||||
TimestampManager* timestamp_manager,
|
||||
IModificationTimestampFetcher* modification_timestamp_fetcher,
|
||||
IIndexer* indexer) {
|
||||
auto* queue = QueueManager::instance();
|
||||
std::optional<Index_Request> request = queue->index_request.TryPopFront();
|
||||
@ -414,8 +368,7 @@ bool IndexMain_DoParse(
|
||||
Project::Entry entry;
|
||||
entry.filename = request->path;
|
||||
entry.args = request->args;
|
||||
ParseFile(diag_engine, working_files, file_consumer_shared,
|
||||
timestamp_manager, modification_timestamp_fetcher,
|
||||
ParseFile(diag_engine, working_files, file_consumer_shared, timestamp_manager,
|
||||
indexer, request.value(), entry);
|
||||
return true;
|
||||
}
|
||||
@ -424,8 +377,7 @@ bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) {
|
||||
auto* queue = QueueManager::instance();
|
||||
|
||||
bool did_work = false;
|
||||
IterationLoop loop;
|
||||
while (loop.Next()) {
|
||||
for (int i = 100; i--; ) {
|
||||
std::optional<Index_OnIdMapped> response = queue->on_id_mapped.TryPopFront();
|
||||
if (!response)
|
||||
return did_work;
|
||||
@ -434,24 +386,24 @@ bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) {
|
||||
|
||||
Timer time;
|
||||
|
||||
// Build delta update.
|
||||
IndexUpdate update = IndexUpdate::CreateDelta(response->previous.get(),
|
||||
response->current.get());
|
||||
response->perf.index_make_delta = time.ElapsedMicrosecondsAndReset();
|
||||
LOG_S(INFO) << "built index for " << response->current->path
|
||||
<< " (is_delta=" << !!response->previous << ")";
|
||||
|
||||
// Write current index to disk if requested.
|
||||
std::string path = response->current->path;
|
||||
if (response->write_to_disk) {
|
||||
LOG_S(INFO) << "store index for " << response->current->path;
|
||||
LOG_S(INFO) << "store index for " << path;
|
||||
time.Reset();
|
||||
response->cache_manager->WriteToCache(*response->current);
|
||||
response->perf.index_save_to_disk = time.ElapsedMicrosecondsAndReset();
|
||||
timestamp_manager->UpdateCachedModificationTime(
|
||||
response->current->path,
|
||||
response->current->last_modification_time);
|
||||
path, response->current->last_modification_time);
|
||||
}
|
||||
|
||||
// Build delta update.
|
||||
IndexUpdate update = IndexUpdate::CreateDelta(response->previous.get(),
|
||||
response->current.get());
|
||||
response->perf.index_make_delta = time.ElapsedMicrosecondsAndReset();
|
||||
LOG_S(INFO) << "built index for " << path
|
||||
<< " (is_delta=" << !!response->previous << ")";
|
||||
|
||||
Index_OnIndexed reply(std::move(update), response->perf);
|
||||
queue->on_indexed.PushBack(std::move(reply), response->is_interactive);
|
||||
}
|
||||
@ -533,7 +485,6 @@ void Indexer_Main(DiagnosticsEngine* diag_engine,
|
||||
Project* project,
|
||||
WorkingFiles* working_files,
|
||||
MultiQueueWaiter* waiter) {
|
||||
RealModificationTimestampFetcher modification_timestamp_fetcher;
|
||||
auto* queue = QueueManager::instance();
|
||||
// Build one index per-indexer, as building the index acquires a global lock.
|
||||
auto indexer = std::make_unique<ClangIndexer>();
|
||||
@ -555,7 +506,6 @@ void Indexer_Main(DiagnosticsEngine* diag_engine,
|
||||
// index.
|
||||
did_work = IndexMain_DoParse(diag_engine, working_files,
|
||||
file_consumer_shared, timestamp_manager,
|
||||
&modification_timestamp_fetcher,
|
||||
indexer.get()) ||
|
||||
did_work;
|
||||
|
||||
@ -614,8 +564,7 @@ bool QueryDb_ImportMain(QueryDatabase* db,
|
||||
|
||||
bool did_work = false;
|
||||
|
||||
IterationLoop loop;
|
||||
while (loop.Next()) {
|
||||
for (int i = 80; i--; ) {
|
||||
std::optional<Index_OnIndexed> response = queue->on_indexed.TryPopFront();
|
||||
if (!response)
|
||||
break;
|
||||
@ -626,153 +575,3 @@ bool QueryDb_ImportMain(QueryDatabase* db,
|
||||
|
||||
return did_work;
|
||||
}
|
||||
|
||||
TEST_SUITE("ImportPipeline") {
|
||||
struct Fixture {
|
||||
Fixture() {
|
||||
g_config = std::make_unique<Config>();
|
||||
QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter);
|
||||
|
||||
queue = QueueManager::instance();
|
||||
cache_manager = ICacheManager::MakeFake({});
|
||||
indexer = IIndexer::MakeTestIndexer({});
|
||||
diag_engine.Init();
|
||||
}
|
||||
|
||||
bool PumpOnce() {
|
||||
return IndexMain_DoParse(&diag_engine, &working_files,
|
||||
&file_consumer_shared, ×tamp_manager,
|
||||
&modification_timestamp_fetcher, indexer.get());
|
||||
}
|
||||
|
||||
void MakeRequest(const std::string& path,
|
||||
const std::vector<std::string>& args = {},
|
||||
bool is_interactive = false,
|
||||
const std::string& contents = "void foo();") {
|
||||
queue->index_request.PushBack(
|
||||
Index_Request(path, args, is_interactive, contents, cache_manager));
|
||||
}
|
||||
|
||||
MultiQueueWaiter querydb_waiter;
|
||||
MultiQueueWaiter indexer_waiter;
|
||||
MultiQueueWaiter stdout_waiter;
|
||||
|
||||
QueueManager* queue = nullptr;
|
||||
DiagnosticsEngine diag_engine;
|
||||
WorkingFiles working_files;
|
||||
FileConsumerSharedState file_consumer_shared;
|
||||
TimestampManager timestamp_manager;
|
||||
FakeModificationTimestampFetcher modification_timestamp_fetcher;
|
||||
std::shared_ptr<ICacheManager> cache_manager;
|
||||
std::unique_ptr<IIndexer> indexer;
|
||||
};
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "FileNeedsParse") {
|
||||
auto check = [&](const std::string& file, bool is_dependency = false,
|
||||
bool is_interactive = false,
|
||||
const std::vector<std::string>& old_args = {},
|
||||
const std::vector<std::string>& new_args = {}) {
|
||||
std::unique_ptr<IndexFile> opt_previous_index;
|
||||
if (!old_args.empty()) {
|
||||
opt_previous_index = std::make_unique<IndexFile>("---.cc", "<empty>");
|
||||
opt_previous_index->args = old_args;
|
||||
}
|
||||
std::optional<std::string> from;
|
||||
if (is_dependency)
|
||||
from = std::string("---.cc");
|
||||
return FileNeedsParse(is_interactive /*is_interactive*/,
|
||||
×tamp_manager, &modification_timestamp_fetcher,
|
||||
cache_manager, opt_previous_index.get(), file,
|
||||
new_args, from);
|
||||
};
|
||||
|
||||
// A file with no timestamp is not imported, since this implies the file no
|
||||
// longer exists on disk.
|
||||
modification_timestamp_fetcher.entries["bar.h"] = std::nullopt;
|
||||
REQUIRE(check("bar.h", false /*is_dependency*/) == ShouldParse::NoSuchFile);
|
||||
|
||||
// A dependency is only imported once.
|
||||
modification_timestamp_fetcher.entries["foo.h"] = 5;
|
||||
REQUIRE(check("foo.h", true /*is_dependency*/) == ShouldParse::Yes);
|
||||
REQUIRE(check("foo.h", true /*is_dependency*/) == ShouldParse::No);
|
||||
|
||||
// An interactive dependency is imported.
|
||||
REQUIRE(check("foo.h", true /*is_dependency*/) == ShouldParse::No);
|
||||
REQUIRE(check("foo.h", true /*is_dependency*/, true /*is_interactive*/) ==
|
||||
ShouldParse::Yes);
|
||||
|
||||
// A file whose timestamp has not changed is not imported. When the
|
||||
// timestamp changes (either forward or backward) it is reimported.
|
||||
auto check_timestamp_change = [&](int64_t timestamp) {
|
||||
modification_timestamp_fetcher.entries["aa.cc"] = timestamp;
|
||||
REQUIRE(check("aa.cc") == ShouldParse::Yes);
|
||||
REQUIRE(check("aa.cc") == ShouldParse::Yes);
|
||||
REQUIRE(check("aa.cc") == ShouldParse::Yes);
|
||||
timestamp_manager.UpdateCachedModificationTime("aa.cc", timestamp);
|
||||
REQUIRE(check("aa.cc") == ShouldParse::No);
|
||||
};
|
||||
check_timestamp_change(5);
|
||||
check_timestamp_change(6);
|
||||
check_timestamp_change(5);
|
||||
check_timestamp_change(4);
|
||||
|
||||
// Argument change implies reimport, even if timestamp has not changed.
|
||||
timestamp_manager.UpdateCachedModificationTime("aa.cc", 5);
|
||||
modification_timestamp_fetcher.entries["aa.cc"] = 5;
|
||||
REQUIRE(check("aa.cc", false /*is_dependency*/, false /*is_interactive*/,
|
||||
{"b"} /*old_args*/,
|
||||
{"b", "a"} /*new_args*/) == ShouldParse::Yes);
|
||||
}
|
||||
|
||||
// FIXME: validate other state like timestamp_manager, etc.
|
||||
// FIXME: add more interesting tests that are not the happy path
|
||||
// FIXME: test
|
||||
// - IndexMain_DoCreateIndexUpdate
|
||||
// - IndexMain_LoadPreviousIndex
|
||||
// - QueryDb_ImportMain
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index request with zero results") {
|
||||
indexer = IIndexer::MakeTestIndexer({IIndexer::TestEntry{"foo.cc", 0}});
|
||||
|
||||
MakeRequest("foo.cc");
|
||||
|
||||
REQUIRE(queue->index_request.Size() == 1);
|
||||
REQUIRE(queue->on_id_mapped.Size() == 0);
|
||||
PumpOnce();
|
||||
REQUIRE(queue->index_request.Size() == 0);
|
||||
REQUIRE(queue->on_id_mapped.Size() == 0);
|
||||
|
||||
REQUIRE(file_consumer_shared.used_files.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "one index request") {
|
||||
indexer = IIndexer::MakeTestIndexer({IIndexer::TestEntry{"foo.cc", 100}});
|
||||
|
||||
MakeRequest("foo.cc");
|
||||
|
||||
REQUIRE(queue->index_request.Size() == 1);
|
||||
REQUIRE(queue->on_id_mapped.Size() == 0);
|
||||
PumpOnce();
|
||||
REQUIRE(queue->index_request.Size() == 0);
|
||||
REQUIRE(queue->on_id_mapped.Size() == 100);
|
||||
|
||||
REQUIRE(file_consumer_shared.used_files.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "multiple index requests") {
|
||||
indexer = IIndexer::MakeTestIndexer(
|
||||
{IIndexer::TestEntry{"foo.cc", 100}, IIndexer::TestEntry{"bar.cc", 5}});
|
||||
|
||||
MakeRequest("foo.cc");
|
||||
MakeRequest("bar.cc");
|
||||
|
||||
REQUIRE(queue->index_request.Size() == 2);
|
||||
//REQUIRE(queue->do_id_map.Size() == 0);
|
||||
while (PumpOnce()) {
|
||||
}
|
||||
REQUIRE(queue->index_request.Size() == 0);
|
||||
//REQUIRE(queue->do_id_map.Size() == 105);
|
||||
|
||||
REQUIRE(file_consumer_shared.used_files.empty());
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,6 @@ struct FuncDef : NameMixin<FuncDef> {
|
||||
// Functions that this function calls.
|
||||
std::vector<SymbolRef> callees;
|
||||
|
||||
int file_id;
|
||||
// Type which declares this one (ie, it is a method)
|
||||
Usr declaring_type = 0;
|
||||
int16_t qual_name_offset = 0;
|
||||
@ -133,7 +132,6 @@ MAKE_REFLECT_STRUCT(FuncDef,
|
||||
comments,
|
||||
spell,
|
||||
extent,
|
||||
file_id,
|
||||
declaring_type,
|
||||
bases,
|
||||
vars,
|
||||
@ -177,7 +175,6 @@ struct TypeDef : NameMixin<TypeDef> {
|
||||
// If set, then this is the same underlying type as the given value (ie, this
|
||||
// type comes from a using or typedef statement).
|
||||
Usr alias_of = 0;
|
||||
int file_id;
|
||||
|
||||
int16_t qual_name_offset = 0;
|
||||
int16_t short_name_offset = 0;
|
||||
@ -201,7 +198,6 @@ MAKE_REFLECT_STRUCT(TypeDef,
|
||||
comments,
|
||||
spell,
|
||||
extent,
|
||||
file_id,
|
||||
alias_of,
|
||||
bases,
|
||||
types,
|
||||
@ -228,7 +224,6 @@ struct VarDef : NameMixin<VarDef> {
|
||||
Maybe<Use> spell;
|
||||
Maybe<Use> extent;
|
||||
|
||||
int file_id;
|
||||
// Type of the variable.
|
||||
Usr type = 0;
|
||||
|
||||
@ -259,7 +254,6 @@ MAKE_REFLECT_STRUCT(VarDef,
|
||||
comments,
|
||||
spell,
|
||||
extent,
|
||||
file_id,
|
||||
type,
|
||||
kind,
|
||||
storage);
|
||||
|
30
src/language.cc
Normal file
30
src/language.cc
Normal file
@ -0,0 +1,30 @@
|
||||
#include "language.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
LanguageId SourceFileLanguage(std::string_view path) {
|
||||
if (EndsWith(path, ".c"))
|
||||
return LanguageId::C;
|
||||
else if (EndsWith(path, ".cpp") || EndsWith(path, ".cc"))
|
||||
return LanguageId::Cpp;
|
||||
else if (EndsWith(path, ".mm"))
|
||||
return LanguageId::ObjCpp;
|
||||
else if (EndsWith(path, ".m"))
|
||||
return LanguageId::ObjC;
|
||||
return LanguageId::Unknown;
|
||||
}
|
||||
|
||||
const char* LanguageIdentifier(LanguageId lang) {
|
||||
switch (lang) {
|
||||
case LanguageId::C:
|
||||
return "c";
|
||||
case LanguageId::Cpp:
|
||||
return "cpp";
|
||||
case LanguageId::ObjC:
|
||||
return "objective-c";
|
||||
case LanguageId::ObjCpp:
|
||||
return "objective-cpp";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
@ -2,8 +2,13 @@
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
// Used to identify the language at a file level. The ordering is important, as
|
||||
// a file previously identified as `C`, will be changed to `Cpp` if it
|
||||
// encounters a c++ declaration.
|
||||
enum class LanguageId { Unknown = 0, C = 1, Cpp = 2, ObjC = 3, ObjCpp = 4 };
|
||||
MAKE_REFLECT_TYPE_PROXY(LanguageId);
|
||||
|
||||
LanguageId SourceFileLanguage(std::string_view path);
|
||||
const char* LanguageIdentifier(LanguageId lang);
|
||||
|
@ -108,7 +108,8 @@ bool Expand(MessageHandler* m,
|
||||
if (const auto* def = func.AnyDef())
|
||||
for (SymbolRef ref : def->callees)
|
||||
if (ref.kind == SymbolKind::Func)
|
||||
handle(Use{{ref.range, ref.usr, ref.kind, ref.role}, def->file_id},
|
||||
handle(Use{{ref.range, ref.usr, ref.kind, ref.role},
|
||||
def->spell->file_id},
|
||||
call_type);
|
||||
} else {
|
||||
for (Use use : func.uses)
|
||||
|
@ -61,19 +61,21 @@ struct Handler_TextDocumentDidOpen
|
||||
|
||||
include_complete->AddFile(working_file->filename);
|
||||
clang_complete->NotifyView(path);
|
||||
if (params.args.size())
|
||||
project->SetFlagsForFile(params.args, path);
|
||||
|
||||
// Submit new index request.
|
||||
Project::Entry entry = project->FindCompilationEntryForFile(path);
|
||||
QueueManager::instance()->index_request.PushBack(
|
||||
// Submit new index request if it is not a header file.
|
||||
if (SourceFileLanguage(path) != LanguageId::Unknown) {
|
||||
Project::Entry entry = project->FindCompilationEntryForFile(path);
|
||||
QueueManager::instance()->index_request.PushBack(
|
||||
Index_Request(
|
||||
entry.filename, params.args.size() ? params.args : entry.args,
|
||||
true /*is_interactive*/, params.textDocument.text, cache_manager),
|
||||
entry.filename, params.args.size() ? params.args : entry.args,
|
||||
true /*is_interactive*/, params.textDocument.text, cache_manager),
|
||||
true /* priority */);
|
||||
|
||||
clang_complete->FlushSession(entry.filename);
|
||||
LOG_S(INFO) << "Flushed clang complete sessions for " << entry.filename;
|
||||
if (params.args.size())
|
||||
project->SetFlagsForFile(params.args, path);
|
||||
clang_complete->FlushSession(entry.filename);
|
||||
LOG_S(INFO) << "Flushed clang complete sessions for " << entry.filename;
|
||||
}
|
||||
}
|
||||
};
|
||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen);
|
||||
|
@ -21,14 +21,13 @@ std::optional<lsMarkedString> GetComments(QueryDatabase* db, SymbolRef sym) {
|
||||
|
||||
// Returns the hover or detailed name for `sym`, if any.
|
||||
std::optional<lsMarkedString> GetHoverOrName(QueryDatabase* db,
|
||||
const std::string& language,
|
||||
SymbolRef sym) {
|
||||
|
||||
LanguageId lang,
|
||||
SymbolRef sym) {
|
||||
std::optional<lsMarkedString> ret;
|
||||
WithEntity(db, sym, [&](const auto& entity) {
|
||||
if (const auto* def = entity.AnyDef()) {
|
||||
lsMarkedString m;
|
||||
m.language = language;
|
||||
m.language = LanguageIdentifier(lang);
|
||||
if (!def->hover.empty()) {
|
||||
m.value = def->hover;
|
||||
ret = m;
|
||||
|
@ -48,6 +48,21 @@ struct Range {
|
||||
}
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<Range> {
|
||||
std::size_t operator()(Range x) const {
|
||||
union U {
|
||||
Range range = {};
|
||||
uint64_t u64;
|
||||
} u;
|
||||
static_assert(sizeof(Range) == 8);
|
||||
u.range = x;
|
||||
return hash<uint64_t>()(u.u64);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Reflection
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
@ -91,18 +91,6 @@ bool ShouldAddToAngleIncludes(const std::string& arg) {
|
||||
return StartsWithAny(arg, kAngleIncludeArgs);
|
||||
}
|
||||
|
||||
LanguageId SourceFileLanguage(const std::string& path) {
|
||||
if (EndsWith(path, ".c"))
|
||||
return LanguageId::C;
|
||||
else if (EndsWith(path, ".cpp") || EndsWith(path, ".cc"))
|
||||
return LanguageId::Cpp;
|
||||
else if (EndsWith(path, ".mm"))
|
||||
return LanguageId::ObjCpp;
|
||||
else if (EndsWith(path, ".m"))
|
||||
return LanguageId::ObjC;
|
||||
return LanguageId::Unknown;
|
||||
}
|
||||
|
||||
Project::Entry GetCompilationEntryFromCompileCommandEntry(
|
||||
ProjectConfig* config,
|
||||
const CompileCommandsEntry& entry) {
|
||||
|
94
src/query.cc
94
src/query.cc
@ -17,8 +17,7 @@
|
||||
#include <unordered_set>
|
||||
|
||||
// Used by |HANDLE_MERGEABLE| so only |range| is needed.
|
||||
MAKE_HASHABLE(Range, t.start, t.end);
|
||||
MAKE_HASHABLE(Use, t.range);
|
||||
MAKE_HASHABLE(Use, t.range, t.file_id);
|
||||
|
||||
namespace {
|
||||
|
||||
@ -48,7 +47,6 @@ void AssignFileId(int file_id, std::vector<T>& xs) {
|
||||
AssignFileId(file_id, x);
|
||||
}
|
||||
|
||||
|
||||
void AddRange(int file_id, std::vector<Use>& into, const std::vector<Use>& from) {
|
||||
into.reserve(into.size() + from.size());
|
||||
for (Use use : from) {
|
||||
@ -62,39 +60,24 @@ void AddRange(int _, std::vector<Usr>& into, const std::vector<Usr>& from) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RemoveRange(std::vector<T>& dest, const std::vector<T>& to_remove) {
|
||||
void RemoveRange(std::vector<T>& from, const std::vector<T>& to_remove) {
|
||||
if (to_remove.size()) {
|
||||
std::unordered_set<T> to_remove_set(to_remove.begin(), to_remove.end());
|
||||
dest.erase(
|
||||
std::remove_if(dest.begin(), dest.end(),
|
||||
from.erase(
|
||||
std::remove_if(from.begin(), from.end(),
|
||||
[&](const T& t) { return to_remove_set.count(t) > 0; }),
|
||||
dest.end());
|
||||
from.end());
|
||||
}
|
||||
}
|
||||
|
||||
QueryFile::DefUpdate BuildFileDefUpdate(const IndexFile& indexed) {
|
||||
QueryFile::Def def;
|
||||
def.path = indexed.path;
|
||||
def.args = indexed.args;
|
||||
def.includes = indexed.includes;
|
||||
def.inactive_regions = indexed.skipped_by_preprocessor;
|
||||
def.dependencies = indexed.dependencies;
|
||||
|
||||
// Convert enum to markdown compatible strings
|
||||
def.language = [&indexed]() {
|
||||
switch (indexed.language) {
|
||||
case LanguageId::C:
|
||||
return "c";
|
||||
case LanguageId::Cpp:
|
||||
return "cpp";
|
||||
case LanguageId::ObjC:
|
||||
return "objective-c";
|
||||
case LanguageId::ObjCpp:
|
||||
return "objective-cpp";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
def.path = std::move(indexed.path);
|
||||
def.args = std::move(indexed.args);
|
||||
def.includes = std::move(indexed.includes);
|
||||
def.inactive_regions = std::move(indexed.skipped_by_preprocessor);
|
||||
def.dependencies = std::move(indexed.dependencies);
|
||||
def.language = indexed.language;
|
||||
|
||||
auto add_all_symbols = [&](Use use, Usr usr, SymbolKind kind) {
|
||||
def.all_symbols.push_back(SymbolRef(use.range, usr, kind, use.role));
|
||||
@ -172,10 +155,8 @@ QueryFile::DefUpdate BuildFileDefUpdate(const IndexFile& indexed) {
|
||||
template <typename Q>
|
||||
bool TryReplaceDef(std::forward_list<Q>& def_list, Q&& def) {
|
||||
for (auto& def1 : def_list)
|
||||
if (def1.file_id == def.file_id) {
|
||||
if (!def1.spell || def.spell) {
|
||||
def1 = std::move(def);
|
||||
}
|
||||
if (def1.spell->file_id == def.spell->file_id) {
|
||||
def1 = std::move(def);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -195,57 +176,57 @@ IndexUpdate IndexUpdate::CreateDelta(IndexFile* previous,
|
||||
if (!previous)
|
||||
previous = ∅
|
||||
|
||||
r.files_def_update = BuildFileDefUpdate(*current);
|
||||
r.files_def_update = BuildFileDefUpdate(std::move(*current));
|
||||
|
||||
for (auto& it : previous->usr2func) {
|
||||
auto& func = it.second;
|
||||
if (func.def.spell)
|
||||
r.funcs_removed.push_back(func.usr);
|
||||
r.funcs_declarations[func.usr].first = func.declarations;
|
||||
r.funcs_uses[func.usr].first = func.uses;
|
||||
r.funcs_derived[func.usr].first = func.derived;
|
||||
r.funcs_declarations[func.usr].first = std::move(func.declarations);
|
||||
r.funcs_uses[func.usr].first = std::move(func.uses);
|
||||
r.funcs_derived[func.usr].first = std::move(func.derived);
|
||||
}
|
||||
for (auto& it : current->usr2func) {
|
||||
auto& func = it.second;
|
||||
if (func.def.detailed_name.size())
|
||||
if (func.def.spell && func.def.detailed_name.size())
|
||||
r.funcs_def_update.emplace_back(it.first, func.def);
|
||||
r.funcs_declarations[func.usr].second = func.declarations;
|
||||
r.funcs_uses[func.usr].second = func.uses;
|
||||
r.funcs_derived[func.usr].second = func.derived;
|
||||
r.funcs_declarations[func.usr].second = std::move(func.declarations);
|
||||
r.funcs_uses[func.usr].second = std::move(func.uses);
|
||||
r.funcs_derived[func.usr].second = std::move(func.derived);
|
||||
}
|
||||
|
||||
for (auto& it : previous->usr2type) {
|
||||
auto& type = it.second;
|
||||
if (type.def.spell)
|
||||
r.types_removed.push_back(type.usr);
|
||||
r.types_declarations[type.usr].first = type.declarations;
|
||||
r.types_uses[type.usr].first = type.uses;
|
||||
r.types_derived[type.usr].first = type.derived;
|
||||
r.types_instances[type.usr].first = type.instances;
|
||||
r.types_declarations[type.usr].first = std::move(type.declarations);
|
||||
r.types_uses[type.usr].first = std::move(type.uses);
|
||||
r.types_derived[type.usr].first = std::move(type.derived);
|
||||
r.types_instances[type.usr].first = std::move(type.instances);
|
||||
};
|
||||
for (auto& it : current->usr2type) {
|
||||
auto& type = it.second;
|
||||
if (type.def.detailed_name.size())
|
||||
if (type.def.spell && type.def.detailed_name.size())
|
||||
r.types_def_update.emplace_back(it.first, type.def);
|
||||
r.types_declarations[type.usr].second = type.declarations;
|
||||
r.types_uses[type.usr].second = type.uses;
|
||||
r.types_derived[type.usr].second = type.derived;
|
||||
r.types_instances[type.usr].second = type.instances;
|
||||
r.types_declarations[type.usr].second = std::move(type.declarations);
|
||||
r.types_uses[type.usr].second = std::move(type.uses);
|
||||
r.types_derived[type.usr].second = std::move(type.derived);
|
||||
r.types_instances[type.usr].second = std::move(type.instances);
|
||||
};
|
||||
|
||||
for (auto& it : previous->usr2var) {
|
||||
auto& var = it.second;
|
||||
if (var.def.spell)
|
||||
r.vars_removed.push_back(var.usr);
|
||||
r.vars_declarations[var.usr].first = var.declarations;
|
||||
r.vars_uses[var.usr].first = var.uses;
|
||||
r.vars_declarations[var.usr].first = std::move(var.declarations);
|
||||
r.vars_uses[var.usr].first = std::move(var.uses);
|
||||
}
|
||||
for (auto& it : current->usr2var) {
|
||||
auto& var = it.second;
|
||||
if (var.def.detailed_name.size())
|
||||
if (var.def.spell && var.def.detailed_name.size())
|
||||
r.vars_def_update.emplace_back(it.first, var.def);
|
||||
r.vars_declarations[var.usr].second = var.declarations;
|
||||
r.vars_uses[var.usr].second = var.uses;
|
||||
r.vars_declarations[var.usr].second = std::move(var.declarations);
|
||||
r.vars_uses[var.usr].second = std::move(var.uses);
|
||||
}
|
||||
|
||||
return r;
|
||||
@ -281,7 +262,7 @@ void QueryDatabase::RemoveUsrs(
|
||||
for (auto usr : to_remove) {
|
||||
QueryFunc& func = Func(usr);
|
||||
func.def.remove_if([=](const QueryFunc::Def& def) {
|
||||
return def.file_id == file_id;
|
||||
return def.spell->file_id == file_id;
|
||||
});
|
||||
}
|
||||
break;
|
||||
@ -290,7 +271,7 @@ void QueryDatabase::RemoveUsrs(
|
||||
for (auto usr : to_remove) {
|
||||
QueryVar& var = Var(usr);
|
||||
var.def.remove_if([=](const QueryVar::Def& def) {
|
||||
return def.file_id == file_id;
|
||||
return def.spell->file_id == file_id;
|
||||
});
|
||||
}
|
||||
break;
|
||||
@ -312,6 +293,7 @@ void QueryDatabase::ApplyIndexUpdate(IndexUpdate* u) {
|
||||
#define HANDLE_MERGEABLE(update_var_name, def_var_name, storage_name) \
|
||||
for (auto& it : u->update_var_name) { \
|
||||
auto& entity = storage_name[it.first]; \
|
||||
AssignFileId(u->file_id, it.second.first); \
|
||||
RemoveRange(entity.def_var_name, it.second.first); \
|
||||
AssignFileId(u->file_id, it.second.second); \
|
||||
AddRange(u->file_id, entity.def_var_name, it.second.second); \
|
||||
|
@ -26,8 +26,7 @@ struct QueryFile {
|
||||
struct Def {
|
||||
std::string path;
|
||||
std::vector<std::string> args;
|
||||
// Language identifier
|
||||
std::string language;
|
||||
LanguageId language;
|
||||
// Includes in the file.
|
||||
std::vector<IndexInclude> includes;
|
||||
// Outline of the file (ie, for code lens).
|
||||
|
Loading…
Reference in New Issue
Block a user