This commit is contained in:
Fangrui Song 2018-05-04 20:40:52 -07:00
parent ccb5cba720
commit 86efddf032
14 changed files with 140 additions and 333 deletions

View File

@ -216,6 +216,7 @@ target_sources(ccls PRIVATE
src/import_pipeline.cc src/import_pipeline.cc
src/include_complete.cc src/include_complete.cc
src/method.cc src/method.cc
src/language.cc
src/lex_utils.cc src/lex_utils.cc
src/lsp.cc src/lsp.cc
src/match.cc src/match.cc

View File

@ -4,7 +4,7 @@ ccls is a fork of cquery (originally written by Jacob Dufault),
a C/C++/Objective-C language server. a C/C++/Objective-C language server.
* code completion (with both signature help and snippets) * 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) * [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) * [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) * [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 * diagnostics
* code actions (clang FixIts) * code actions (clang FixIts)
* preprocessor skipped regions * 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) * 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)

View File

@ -718,27 +718,20 @@ std::string IndexFile::ToString() {
return Serialize(SerializeFormat::Json, *this); return Serialize(SerializeFormat::Json, *this);
} }
void Uniquify(std::vector<Usr>& ids) { void Uniquify(std::vector<Usr>& usrs) {
std::unordered_set<Usr> seen; std::unordered_set<Usr> seen;
size_t n = 0; size_t n = 0;
for (size_t i = 0; i < ids.size(); i++) for (size_t i = 0; i < usrs.size(); i++)
if (seen.insert(ids[i]).second) if (seen.insert(usrs[i]).second)
ids[n++] = ids[i]; usrs[n++] = usrs[i];
ids.resize(n); usrs.resize(n);
} }
void Uniquify(std::vector<Use>& uses) { void Uniquify(std::vector<Use>& uses) {
union U { std::unordered_set<Range> seen;
Range range = {};
uint64_t u64;
};
static_assert(sizeof(Range) == 8);
std::unordered_set<uint64_t> seen;
size_t n = 0; size_t n = 0;
for (size_t i = 0; i < uses.size(); i++) { for (size_t i = 0; i < uses.size(); i++) {
U u; if (seen.insert(uses[i].range).second)
u.range = uses[i].range;
if (seen.insert(u.u64).second)
uses[n++] = uses[i]; uses[n++] = uses[i];
} }
uses.resize(n); uses.resize(n);

View File

@ -37,43 +37,6 @@ MAKE_REFLECT_STRUCT(Out_Progress::Params,
activeThreads); activeThreads);
MAKE_REFLECT_STRUCT(Out_Progress, jsonrpc, method, params); 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() { long long GetCurrentTimeInMilliseconds() {
auto time_since_epoch = Timer::Clock::now().time_since_epoch(); auto time_since_epoch = Timer::Clock::now().time_since_epoch();
long long elapsed_milliseconds = long long elapsed_milliseconds =
@ -138,7 +101,6 @@ enum class ShouldParse { Yes, No, NoSuchFile };
ShouldParse FileNeedsParse( ShouldParse FileNeedsParse(
bool is_interactive, bool is_interactive,
TimestampManager* timestamp_manager, TimestampManager* timestamp_manager,
IModificationTimestampFetcher* modification_timestamp_fetcher,
const std::shared_ptr<ICacheManager>& cache_manager, const std::shared_ptr<ICacheManager>& cache_manager,
IndexFile* opt_previous_index, IndexFile* opt_previous_index,
const std::string& path, const std::string& path,
@ -150,8 +112,7 @@ ShouldParse FileNeedsParse(
return ""; return "";
}; };
std::optional<int64_t> modification_timestamp = std::optional<int64_t> modification_timestamp = LastWriteTime(path);
modification_timestamp_fetcher->LastWriteTime(path);
// Cannot find file. // Cannot find file.
if (!modification_timestamp) if (!modification_timestamp)
@ -193,7 +154,6 @@ enum CacheLoadResult { Parse, DoNotParse };
CacheLoadResult TryLoadFromCache( CacheLoadResult TryLoadFromCache(
FileConsumerSharedState* file_consumer_shared, FileConsumerSharedState* file_consumer_shared,
TimestampManager* timestamp_manager, TimestampManager* timestamp_manager,
IModificationTimestampFetcher* modification_timestamp_fetcher,
const std::shared_ptr<ICacheManager>& cache_manager, const std::shared_ptr<ICacheManager>& cache_manager,
bool is_interactive, bool is_interactive,
const Project::Entry& entry, const Project::Entry& entry,
@ -209,10 +169,9 @@ CacheLoadResult TryLoadFromCache(
// from cache. // from cache.
// Check timestamps and update |file_consumer_shared|. // Check timestamps and update |file_consumer_shared|.
ShouldParse path_state = FileNeedsParse( ShouldParse path_state =
is_interactive, timestamp_manager, modification_timestamp_fetcher, FileNeedsParse(is_interactive, timestamp_manager, cache_manager,
cache_manager, previous_index, path_to_index, entry.args, previous_index, path_to_index, entry.args, std::nullopt);
std::nullopt);
if (path_state == ShouldParse::Yes) if (path_state == ShouldParse::Yes)
file_consumer_shared->Reset(path_to_index); file_consumer_shared->Reset(path_to_index);
@ -228,8 +187,7 @@ CacheLoadResult TryLoadFromCache(
for (const std::string& dependency : previous_index->dependencies) { for (const std::string& dependency : previous_index->dependencies) {
assert(!dependency.empty()); assert(!dependency.empty());
if (FileNeedsParse(is_interactive, timestamp_manager, if (FileNeedsParse(is_interactive, timestamp_manager, cache_manager,
modification_timestamp_fetcher, cache_manager,
previous_index, dependency, entry.args, previous_index, dependency, entry.args,
previous_index->path) == ShouldParse::Yes) { previous_index->path) == ShouldParse::Yes) {
needs_reparse = true; needs_reparse = true;
@ -245,7 +203,7 @@ CacheLoadResult TryLoadFromCache(
return CacheLoadResult::Parse; return CacheLoadResult::Parse;
// No timestamps changed - load directly from cache. // 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 // TODO/FIXME: real perf
PerformanceImportFile perf; PerformanceImportFile perf;
@ -333,7 +291,6 @@ void ParseFile(DiagnosticsEngine* diag_engine,
WorkingFiles* working_files, WorkingFiles* working_files,
FileConsumerSharedState* file_consumer_shared, FileConsumerSharedState* file_consumer_shared,
TimestampManager* timestamp_manager, TimestampManager* timestamp_manager,
IModificationTimestampFetcher* modification_timestamp_fetcher,
IIndexer* indexer, IIndexer* indexer,
const Index_Request& request, const Index_Request& request,
const Project::Entry& entry) { const Project::Entry& entry) {
@ -349,11 +306,9 @@ void ParseFile(DiagnosticsEngine* diag_engine,
// Try to load the file from cache. // Try to load the file from cache.
if (TryLoadFromCache(file_consumer_shared, timestamp_manager, if (TryLoadFromCache(file_consumer_shared, timestamp_manager,
modification_timestamp_fetcher, request.cache_manager, request.cache_manager, request.is_interactive, entry,
request.is_interactive, entry, path_to_index) == CacheLoadResult::DoNotParse)
path_to_index) == CacheLoadResult::DoNotParse) {
return; return;
}
LOG_S(INFO) << "parse " << path_to_index; LOG_S(INFO) << "parse " << path_to_index;
std::vector<FileContents> file_contents = PreloadFileContents( std::vector<FileContents> file_contents = PreloadFileContents(
@ -404,7 +359,6 @@ bool IndexMain_DoParse(
WorkingFiles* working_files, WorkingFiles* working_files,
FileConsumerSharedState* file_consumer_shared, FileConsumerSharedState* file_consumer_shared,
TimestampManager* timestamp_manager, TimestampManager* timestamp_manager,
IModificationTimestampFetcher* modification_timestamp_fetcher,
IIndexer* indexer) { IIndexer* indexer) {
auto* queue = QueueManager::instance(); auto* queue = QueueManager::instance();
std::optional<Index_Request> request = queue->index_request.TryPopFront(); std::optional<Index_Request> request = queue->index_request.TryPopFront();
@ -414,8 +368,7 @@ bool IndexMain_DoParse(
Project::Entry entry; Project::Entry entry;
entry.filename = request->path; entry.filename = request->path;
entry.args = request->args; entry.args = request->args;
ParseFile(diag_engine, working_files, file_consumer_shared, ParseFile(diag_engine, working_files, file_consumer_shared, timestamp_manager,
timestamp_manager, modification_timestamp_fetcher,
indexer, request.value(), entry); indexer, request.value(), entry);
return true; return true;
} }
@ -424,8 +377,7 @@ bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) {
auto* queue = QueueManager::instance(); auto* queue = QueueManager::instance();
bool did_work = false; bool did_work = false;
IterationLoop loop; for (int i = 100; i--; ) {
while (loop.Next()) {
std::optional<Index_OnIdMapped> response = queue->on_id_mapped.TryPopFront(); std::optional<Index_OnIdMapped> response = queue->on_id_mapped.TryPopFront();
if (!response) if (!response)
return did_work; return did_work;
@ -434,24 +386,24 @@ bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) {
Timer time; 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. // Write current index to disk if requested.
std::string path = response->current->path;
if (response->write_to_disk) { if (response->write_to_disk) {
LOG_S(INFO) << "store index for " << response->current->path; LOG_S(INFO) << "store index for " << path;
time.Reset(); time.Reset();
response->cache_manager->WriteToCache(*response->current); response->cache_manager->WriteToCache(*response->current);
response->perf.index_save_to_disk = time.ElapsedMicrosecondsAndReset(); response->perf.index_save_to_disk = time.ElapsedMicrosecondsAndReset();
timestamp_manager->UpdateCachedModificationTime( timestamp_manager->UpdateCachedModificationTime(
response->current->path, path, response->current->last_modification_time);
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); Index_OnIndexed reply(std::move(update), response->perf);
queue->on_indexed.PushBack(std::move(reply), response->is_interactive); queue->on_indexed.PushBack(std::move(reply), response->is_interactive);
} }
@ -533,7 +485,6 @@ void Indexer_Main(DiagnosticsEngine* diag_engine,
Project* project, Project* project,
WorkingFiles* working_files, WorkingFiles* working_files,
MultiQueueWaiter* waiter) { MultiQueueWaiter* waiter) {
RealModificationTimestampFetcher modification_timestamp_fetcher;
auto* queue = QueueManager::instance(); auto* queue = QueueManager::instance();
// Build one index per-indexer, as building the index acquires a global lock. // Build one index per-indexer, as building the index acquires a global lock.
auto indexer = std::make_unique<ClangIndexer>(); auto indexer = std::make_unique<ClangIndexer>();
@ -555,7 +506,6 @@ void Indexer_Main(DiagnosticsEngine* diag_engine,
// index. // index.
did_work = IndexMain_DoParse(diag_engine, working_files, did_work = IndexMain_DoParse(diag_engine, working_files,
file_consumer_shared, timestamp_manager, file_consumer_shared, timestamp_manager,
&modification_timestamp_fetcher,
indexer.get()) || indexer.get()) ||
did_work; did_work;
@ -614,8 +564,7 @@ bool QueryDb_ImportMain(QueryDatabase* db,
bool did_work = false; bool did_work = false;
IterationLoop loop; for (int i = 80; i--; ) {
while (loop.Next()) {
std::optional<Index_OnIndexed> response = queue->on_indexed.TryPopFront(); std::optional<Index_OnIndexed> response = queue->on_indexed.TryPopFront();
if (!response) if (!response)
break; break;
@ -626,153 +575,3 @@ bool QueryDb_ImportMain(QueryDatabase* db,
return did_work; 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, &timestamp_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*/,
&timestamp_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());
}
}

View File

@ -105,7 +105,6 @@ struct FuncDef : NameMixin<FuncDef> {
// Functions that this function calls. // Functions that this function calls.
std::vector<SymbolRef> callees; std::vector<SymbolRef> callees;
int file_id;
// Type which declares this one (ie, it is a method) // Type which declares this one (ie, it is a method)
Usr declaring_type = 0; Usr declaring_type = 0;
int16_t qual_name_offset = 0; int16_t qual_name_offset = 0;
@ -133,7 +132,6 @@ MAKE_REFLECT_STRUCT(FuncDef,
comments, comments,
spell, spell,
extent, extent,
file_id,
declaring_type, declaring_type,
bases, bases,
vars, vars,
@ -177,7 +175,6 @@ struct TypeDef : NameMixin<TypeDef> {
// If set, then this is the same underlying type as the given value (ie, this // If set, then this is the same underlying type as the given value (ie, this
// type comes from a using or typedef statement). // type comes from a using or typedef statement).
Usr alias_of = 0; Usr alias_of = 0;
int file_id;
int16_t qual_name_offset = 0; int16_t qual_name_offset = 0;
int16_t short_name_offset = 0; int16_t short_name_offset = 0;
@ -201,7 +198,6 @@ MAKE_REFLECT_STRUCT(TypeDef,
comments, comments,
spell, spell,
extent, extent,
file_id,
alias_of, alias_of,
bases, bases,
types, types,
@ -228,7 +224,6 @@ struct VarDef : NameMixin<VarDef> {
Maybe<Use> spell; Maybe<Use> spell;
Maybe<Use> extent; Maybe<Use> extent;
int file_id;
// Type of the variable. // Type of the variable.
Usr type = 0; Usr type = 0;
@ -259,7 +254,6 @@ MAKE_REFLECT_STRUCT(VarDef,
comments, comments,
spell, spell,
extent, extent,
file_id,
type, type,
kind, kind,
storage); storage);

30
src/language.cc Normal file
View 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 "";
}
}

View File

@ -2,8 +2,13 @@
#include "serializer.h" #include "serializer.h"
#include <string_view>
// Used to identify the language at a file level. The ordering is important, as // 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 // a file previously identified as `C`, will be changed to `Cpp` if it
// encounters a c++ declaration. // encounters a c++ declaration.
enum class LanguageId { Unknown = 0, C = 1, Cpp = 2, ObjC = 3, ObjCpp = 4 }; enum class LanguageId { Unknown = 0, C = 1, Cpp = 2, ObjC = 3, ObjCpp = 4 };
MAKE_REFLECT_TYPE_PROXY(LanguageId); MAKE_REFLECT_TYPE_PROXY(LanguageId);
LanguageId SourceFileLanguage(std::string_view path);
const char* LanguageIdentifier(LanguageId lang);

View File

@ -108,7 +108,8 @@ bool Expand(MessageHandler* m,
if (const auto* def = func.AnyDef()) if (const auto* def = func.AnyDef())
for (SymbolRef ref : def->callees) for (SymbolRef ref : def->callees)
if (ref.kind == SymbolKind::Func) 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); call_type);
} else { } else {
for (Use use : func.uses) for (Use use : func.uses)

View File

@ -61,19 +61,21 @@ struct Handler_TextDocumentDidOpen
include_complete->AddFile(working_file->filename); include_complete->AddFile(working_file->filename);
clang_complete->NotifyView(path); clang_complete->NotifyView(path);
if (params.args.size())
project->SetFlagsForFile(params.args, path);
// Submit new index request. // Submit new index request if it is not a header file.
Project::Entry entry = project->FindCompilationEntryForFile(path); if (SourceFileLanguage(path) != LanguageId::Unknown) {
QueueManager::instance()->index_request.PushBack( Project::Entry entry = project->FindCompilationEntryForFile(path);
QueueManager::instance()->index_request.PushBack(
Index_Request( Index_Request(
entry.filename, params.args.size() ? params.args : entry.args, entry.filename, params.args.size() ? params.args : entry.args,
true /*is_interactive*/, params.textDocument.text, cache_manager), true /*is_interactive*/, params.textDocument.text, cache_manager),
true /* priority */); true /* priority */);
clang_complete->FlushSession(entry.filename); clang_complete->FlushSession(entry.filename);
LOG_S(INFO) << "Flushed clang complete sessions for " << entry.filename; LOG_S(INFO) << "Flushed clang complete sessions for " << entry.filename;
if (params.args.size()) }
project->SetFlagsForFile(params.args, path);
} }
}; };
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen); REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen);

View File

@ -21,14 +21,13 @@ std::optional<lsMarkedString> GetComments(QueryDatabase* db, SymbolRef sym) {
// Returns the hover or detailed name for `sym`, if any. // Returns the hover or detailed name for `sym`, if any.
std::optional<lsMarkedString> GetHoverOrName(QueryDatabase* db, std::optional<lsMarkedString> GetHoverOrName(QueryDatabase* db,
const std::string& language, LanguageId lang,
SymbolRef sym) { SymbolRef sym) {
std::optional<lsMarkedString> ret; std::optional<lsMarkedString> ret;
WithEntity(db, sym, [&](const auto& entity) { WithEntity(db, sym, [&](const auto& entity) {
if (const auto* def = entity.AnyDef()) { if (const auto* def = entity.AnyDef()) {
lsMarkedString m; lsMarkedString m;
m.language = language; m.language = LanguageIdentifier(lang);
if (!def->hover.empty()) { if (!def->hover.empty()) {
m.value = def->hover; m.value = def->hover;
ret = m; ret = m;

View File

@ -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 // Reflection
class Reader; class Reader;
class Writer; class Writer;

View File

@ -91,18 +91,6 @@ bool ShouldAddToAngleIncludes(const std::string& arg) {
return StartsWithAny(arg, kAngleIncludeArgs); 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( Project::Entry GetCompilationEntryFromCompileCommandEntry(
ProjectConfig* config, ProjectConfig* config,
const CompileCommandsEntry& entry) { const CompileCommandsEntry& entry) {

View File

@ -17,8 +17,7 @@
#include <unordered_set> #include <unordered_set>
// Used by |HANDLE_MERGEABLE| so only |range| is needed. // Used by |HANDLE_MERGEABLE| so only |range| is needed.
MAKE_HASHABLE(Range, t.start, t.end); MAKE_HASHABLE(Use, t.range, t.file_id);
MAKE_HASHABLE(Use, t.range);
namespace { namespace {
@ -48,7 +47,6 @@ void AssignFileId(int file_id, std::vector<T>& xs) {
AssignFileId(file_id, x); AssignFileId(file_id, x);
} }
void AddRange(int file_id, std::vector<Use>& into, const std::vector<Use>& from) { void AddRange(int file_id, std::vector<Use>& into, const std::vector<Use>& from) {
into.reserve(into.size() + from.size()); into.reserve(into.size() + from.size());
for (Use use : from) { for (Use use : from) {
@ -62,39 +60,24 @@ void AddRange(int _, std::vector<Usr>& into, const std::vector<Usr>& from) {
} }
template <typename T> 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()) { if (to_remove.size()) {
std::unordered_set<T> to_remove_set(to_remove.begin(), to_remove.end()); std::unordered_set<T> to_remove_set(to_remove.begin(), to_remove.end());
dest.erase( from.erase(
std::remove_if(dest.begin(), dest.end(), std::remove_if(from.begin(), from.end(),
[&](const T& t) { return to_remove_set.count(t) > 0; }), [&](const T& t) { return to_remove_set.count(t) > 0; }),
dest.end()); from.end());
} }
} }
QueryFile::DefUpdate BuildFileDefUpdate(const IndexFile& indexed) { QueryFile::DefUpdate BuildFileDefUpdate(const IndexFile& indexed) {
QueryFile::Def def; QueryFile::Def def;
def.path = indexed.path; def.path = std::move(indexed.path);
def.args = indexed.args; def.args = std::move(indexed.args);
def.includes = indexed.includes; def.includes = std::move(indexed.includes);
def.inactive_regions = indexed.skipped_by_preprocessor; def.inactive_regions = std::move(indexed.skipped_by_preprocessor);
def.dependencies = indexed.dependencies; def.dependencies = std::move(indexed.dependencies);
def.language = indexed.language;
// 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 "";
}
}();
auto add_all_symbols = [&](Use use, Usr usr, SymbolKind kind) { auto add_all_symbols = [&](Use use, Usr usr, SymbolKind kind) {
def.all_symbols.push_back(SymbolRef(use.range, usr, kind, use.role)); 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> template <typename Q>
bool TryReplaceDef(std::forward_list<Q>& def_list, Q&& def) { bool TryReplaceDef(std::forward_list<Q>& def_list, Q&& def) {
for (auto& def1 : def_list) for (auto& def1 : def_list)
if (def1.file_id == def.file_id) { if (def1.spell->file_id == def.spell->file_id) {
if (!def1.spell || def.spell) { def1 = std::move(def);
def1 = std::move(def);
}
return true; return true;
} }
return false; return false;
@ -195,57 +176,57 @@ IndexUpdate IndexUpdate::CreateDelta(IndexFile* previous,
if (!previous) if (!previous)
previous = &empty; previous = &empty;
r.files_def_update = BuildFileDefUpdate(*current); r.files_def_update = BuildFileDefUpdate(std::move(*current));
for (auto& it : previous->usr2func) { for (auto& it : previous->usr2func) {
auto& func = it.second; auto& func = it.second;
if (func.def.spell) if (func.def.spell)
r.funcs_removed.push_back(func.usr); r.funcs_removed.push_back(func.usr);
r.funcs_declarations[func.usr].first = func.declarations; r.funcs_declarations[func.usr].first = std::move(func.declarations);
r.funcs_uses[func.usr].first = func.uses; r.funcs_uses[func.usr].first = std::move(func.uses);
r.funcs_derived[func.usr].first = func.derived; r.funcs_derived[func.usr].first = std::move(func.derived);
} }
for (auto& it : current->usr2func) { for (auto& it : current->usr2func) {
auto& func = it.second; 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_def_update.emplace_back(it.first, func.def);
r.funcs_declarations[func.usr].second = func.declarations; r.funcs_declarations[func.usr].second = std::move(func.declarations);
r.funcs_uses[func.usr].second = func.uses; r.funcs_uses[func.usr].second = std::move(func.uses);
r.funcs_derived[func.usr].second = func.derived; r.funcs_derived[func.usr].second = std::move(func.derived);
} }
for (auto& it : previous->usr2type) { for (auto& it : previous->usr2type) {
auto& type = it.second; auto& type = it.second;
if (type.def.spell) if (type.def.spell)
r.types_removed.push_back(type.usr); r.types_removed.push_back(type.usr);
r.types_declarations[type.usr].first = type.declarations; r.types_declarations[type.usr].first = std::move(type.declarations);
r.types_uses[type.usr].first = type.uses; r.types_uses[type.usr].first = std::move(type.uses);
r.types_derived[type.usr].first = type.derived; r.types_derived[type.usr].first = std::move(type.derived);
r.types_instances[type.usr].first = type.instances; r.types_instances[type.usr].first = std::move(type.instances);
}; };
for (auto& it : current->usr2type) { for (auto& it : current->usr2type) {
auto& type = it.second; 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_def_update.emplace_back(it.first, type.def);
r.types_declarations[type.usr].second = type.declarations; r.types_declarations[type.usr].second = std::move(type.declarations);
r.types_uses[type.usr].second = type.uses; r.types_uses[type.usr].second = std::move(type.uses);
r.types_derived[type.usr].second = type.derived; r.types_derived[type.usr].second = std::move(type.derived);
r.types_instances[type.usr].second = type.instances; r.types_instances[type.usr].second = std::move(type.instances);
}; };
for (auto& it : previous->usr2var) { for (auto& it : previous->usr2var) {
auto& var = it.second; auto& var = it.second;
if (var.def.spell) if (var.def.spell)
r.vars_removed.push_back(var.usr); r.vars_removed.push_back(var.usr);
r.vars_declarations[var.usr].first = var.declarations; r.vars_declarations[var.usr].first = std::move(var.declarations);
r.vars_uses[var.usr].first = var.uses; r.vars_uses[var.usr].first = std::move(var.uses);
} }
for (auto& it : current->usr2var) { for (auto& it : current->usr2var) {
auto& var = it.second; 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_def_update.emplace_back(it.first, var.def);
r.vars_declarations[var.usr].second = var.declarations; r.vars_declarations[var.usr].second = std::move(var.declarations);
r.vars_uses[var.usr].second = var.uses; r.vars_uses[var.usr].second = std::move(var.uses);
} }
return r; return r;
@ -281,7 +262,7 @@ void QueryDatabase::RemoveUsrs(
for (auto usr : to_remove) { for (auto usr : to_remove) {
QueryFunc& func = Func(usr); QueryFunc& func = Func(usr);
func.def.remove_if([=](const QueryFunc::Def& def) { func.def.remove_if([=](const QueryFunc::Def& def) {
return def.file_id == file_id; return def.spell->file_id == file_id;
}); });
} }
break; break;
@ -290,7 +271,7 @@ void QueryDatabase::RemoveUsrs(
for (auto usr : to_remove) { for (auto usr : to_remove) {
QueryVar& var = Var(usr); QueryVar& var = Var(usr);
var.def.remove_if([=](const QueryVar::Def& def) { var.def.remove_if([=](const QueryVar::Def& def) {
return def.file_id == file_id; return def.spell->file_id == file_id;
}); });
} }
break; break;
@ -312,6 +293,7 @@ void QueryDatabase::ApplyIndexUpdate(IndexUpdate* u) {
#define HANDLE_MERGEABLE(update_var_name, def_var_name, storage_name) \ #define HANDLE_MERGEABLE(update_var_name, def_var_name, storage_name) \
for (auto& it : u->update_var_name) { \ for (auto& it : u->update_var_name) { \
auto& entity = storage_name[it.first]; \ auto& entity = storage_name[it.first]; \
AssignFileId(u->file_id, it.second.first); \
RemoveRange(entity.def_var_name, it.second.first); \ RemoveRange(entity.def_var_name, it.second.first); \
AssignFileId(u->file_id, it.second.second); \ AssignFileId(u->file_id, it.second.second); \
AddRange(u->file_id, entity.def_var_name, it.second.second); \ AddRange(u->file_id, entity.def_var_name, it.second.second); \

View File

@ -26,8 +26,7 @@ struct QueryFile {
struct Def { struct Def {
std::string path; std::string path;
std::vector<std::string> args; std::vector<std::string> args;
// Language identifier LanguageId language;
std::string language;
// Includes in the file. // Includes in the file.
std::vector<IndexInclude> includes; std::vector<IndexInclude> includes;
// Outline of the file (ie, for code lens). // Outline of the file (ie, for code lens).