mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-21 23:25:07 +00:00
Simplify Position & Range; prettify Maybe; remove file_contents.{h,cc}
This commit is contained in:
parent
a632f97a2d
commit
5fb88749a9
@ -210,7 +210,6 @@ target_sources(ccls PRIVATE
|
||||
src/config.cc
|
||||
src/diagnostics_engine.cc
|
||||
src/file_consumer.cc
|
||||
src/file_contents.cc
|
||||
src/filesystem.cc
|
||||
src/fuzzy_match.cc
|
||||
src/iindexer.cc
|
||||
|
@ -17,8 +17,8 @@ Range ResolveCXSourceRange(const CXSourceRange& range, CXFile* cx_file) {
|
||||
unsigned int end_line, end_column;
|
||||
clang_getSpellingLocation(end, nullptr, &end_line, &end_column, nullptr);
|
||||
|
||||
return Range(Position((int16_t)start_line - 1, (int16_t)start_column - 1),
|
||||
Position((int16_t)end_line - 1, (int16_t)end_column - 1));
|
||||
return Range{{int16_t(start_line - 1), (int16_t)(start_column - 1)},
|
||||
{int16_t(end_line - 1), int16_t(end_column - 1)}};
|
||||
}
|
||||
|
||||
ClangCursor ClangType::get_declaration() const {
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@ -280,7 +281,7 @@ struct ConstructorCache {
|
||||
struct IndexParam {
|
||||
std::unordered_set<CXFile> seen_cx_files;
|
||||
std::vector<std::string> seen_files;
|
||||
FileContentsMap file_contents;
|
||||
std::unordered_map<std::string, FileContents> file_contents;
|
||||
std::unordered_map<std::string, int64_t> file_modification_times;
|
||||
|
||||
// Only use this when strictly needed (ie, primary translation unit is
|
||||
@ -729,7 +730,10 @@ IndexTypeId IndexFile::ToTypeId(Usr usr) {
|
||||
return it->second;
|
||||
|
||||
IndexTypeId id(types.size());
|
||||
types.push_back(IndexType(id, usr));
|
||||
IndexType type;
|
||||
type.usr = usr;
|
||||
type.id = id;
|
||||
types.push_back(type);
|
||||
id_cache.usr_to_type_id[usr] = id;
|
||||
id_cache.type_id_to_usr[id] = usr;
|
||||
return id;
|
||||
@ -740,7 +744,10 @@ IndexFuncId IndexFile::ToFuncId(Usr usr) {
|
||||
return it->second;
|
||||
|
||||
IndexFuncId id(funcs.size());
|
||||
funcs.push_back(IndexFunc(id, usr));
|
||||
IndexFunc func;
|
||||
func.usr = usr;
|
||||
func.id = id;
|
||||
funcs.push_back(std::move(func));
|
||||
id_cache.usr_to_func_id[usr] = id;
|
||||
id_cache.func_id_to_usr[id] = usr;
|
||||
return id;
|
||||
@ -751,7 +758,10 @@ IndexVarId IndexFile::ToVarId(Usr usr) {
|
||||
return it->second;
|
||||
|
||||
IndexVarId id(vars.size());
|
||||
vars.push_back(IndexVar(id, usr));
|
||||
IndexVar var;
|
||||
var.usr = usr;
|
||||
var.id = id;
|
||||
vars.push_back(std::move(var));
|
||||
id_cache.usr_to_var_id[usr] = id;
|
||||
id_cache.var_id_to_usr[id] = usr;
|
||||
return id;
|
||||
@ -783,8 +793,6 @@ std::string IndexFile::ToString() {
|
||||
return Serialize(SerializeFormat::Json, *this);
|
||||
}
|
||||
|
||||
IndexType::IndexType(IndexTypeId id, Usr usr) : usr(usr), id(id) {}
|
||||
|
||||
template <typename T>
|
||||
void Uniquify(std::vector<Id<T>>& ids) {
|
||||
std::unordered_set<Id<T>> seen;
|
||||
@ -796,11 +804,19 @@ void Uniquify(std::vector<Id<T>>& ids) {
|
||||
}
|
||||
|
||||
void Uniquify(std::vector<Use>& uses) {
|
||||
std::unordered_set<Range> seen;
|
||||
union U {
|
||||
Range range = {};
|
||||
uint64_t u64;
|
||||
};
|
||||
static_assert(sizeof(Range) == 8);
|
||||
std::unordered_set<uint64_t> seen;
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; i < uses.size(); i++)
|
||||
if (seen.insert(uses[i].range).second)
|
||||
for (size_t i = 0; i < uses.size(); i++) {
|
||||
U u;
|
||||
u.range = uses[i].range;
|
||||
if (seen.insert(u.u64).second)
|
||||
uses[n++] = uses[i];
|
||||
}
|
||||
uses.resize(n);
|
||||
}
|
||||
|
||||
@ -2343,10 +2359,6 @@ void IndexInit() {
|
||||
clang_toggleCrashRecovery(1);
|
||||
}
|
||||
|
||||
std::string GetClangVersion() {
|
||||
return ToString(clang_getClangVersion());
|
||||
}
|
||||
|
||||
// |SymbolRef| is serialized this way.
|
||||
// |Use| also uses this though it has an extra field |file|,
|
||||
// which is not used by Index* so it does not need to be serialized.
|
||||
@ -2354,7 +2366,7 @@ void Reflect(Reader& visitor, Reference& value) {
|
||||
if (visitor.Format() == SerializeFormat::Json) {
|
||||
std::string t = visitor.GetString();
|
||||
char* s = const_cast<char*>(t.c_str());
|
||||
value.range = Range(s);
|
||||
value.range = Range::FromString(s);
|
||||
s = strchr(s, '|');
|
||||
value.id.id = RawId(strtol(s + 1, &s, 10));
|
||||
value.kind = static_cast<SymbolKind>(strtol(s + 1, &s, 10));
|
||||
@ -2368,12 +2380,13 @@ void Reflect(Reader& visitor, Reference& value) {
|
||||
}
|
||||
void Reflect(Writer& visitor, Reference& value) {
|
||||
if (visitor.Format() == SerializeFormat::Json) {
|
||||
std::string s = value.range.ToString();
|
||||
// RawId(-1) -> "-1"
|
||||
s += '|' + std::to_string(
|
||||
static_cast<std::make_signed<RawId>::type>(value.id.id));
|
||||
s += '|' + std::to_string(int(value.kind));
|
||||
s += '|' + std::to_string(int(value.role));
|
||||
char buf[99];
|
||||
snprintf(buf, sizeof buf, "%s|%" PRId32 "|%d|%d",
|
||||
value.range.ToString().c_str(),
|
||||
static_cast<std::make_signed<RawId>::type>(value.id.id),
|
||||
int(value.kind), int(value.role));
|
||||
std::string s(buf);
|
||||
Reflect(visitor, s);
|
||||
} else {
|
||||
Reflect(visitor, value.range);
|
||||
|
@ -9,8 +9,9 @@
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<std::string> GetFileContents(const std::string& path,
|
||||
FileContentsMap* file_contents) {
|
||||
std::optional<std::string> GetFileContents(
|
||||
const std::string& path,
|
||||
std::unordered_map<std::string, FileContents>* file_contents) {
|
||||
auto it = file_contents->find(path);
|
||||
if (it == file_contents->end()) {
|
||||
std::optional<std::string> content = ReadContent(path);
|
||||
@ -28,6 +29,34 @@ bool operator==(const CXFileUniqueID& a, const CXFileUniqueID& b) {
|
||||
a.data[2] == b.data[2];
|
||||
}
|
||||
|
||||
FileContents::FileContents() : line_offsets_{0} {}
|
||||
|
||||
FileContents::FileContents(const std::string& path, const std::string& content)
|
||||
: path(path), content(content) {
|
||||
line_offsets_.push_back(0);
|
||||
for (size_t i = 0; i < content.size(); i++) {
|
||||
if (content[i] == '\n')
|
||||
line_offsets_.push_back(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> FileContents::ToOffset(Position p) const {
|
||||
if (0 <= p.line && size_t(p.line) < line_offsets_.size()) {
|
||||
int ret = line_offsets_[p.line] + p.column;
|
||||
if (size_t(ret) < content.size())
|
||||
return ret;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> FileContents::ContentsInRange(Range range) const {
|
||||
std::optional<int> start_offset = ToOffset(range.start),
|
||||
end_offset = ToOffset(range.end);
|
||||
if (start_offset && end_offset && *start_offset < *end_offset)
|
||||
return content.substr(*start_offset, *end_offset - *start_offset);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool FileConsumerSharedState::Mark(const std::string& file) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return used_files.insert(file).second;
|
||||
@ -44,9 +73,10 @@ FileConsumer::FileConsumer(FileConsumerSharedState* shared_state,
|
||||
const std::string& parse_file)
|
||||
: shared_(shared_state), parse_file_(parse_file) {}
|
||||
|
||||
IndexFile* FileConsumer::TryConsumeFile(CXFile file,
|
||||
bool* is_first_ownership,
|
||||
FileContentsMap* file_contents_map) {
|
||||
IndexFile* FileConsumer::TryConsumeFile(
|
||||
CXFile file,
|
||||
bool* is_first_ownership,
|
||||
std::unordered_map<std::string, FileContents>* file_contents_map) {
|
||||
assert(is_first_ownership);
|
||||
|
||||
CXFileUniqueID file_id;
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "file_contents.h"
|
||||
#include "position.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
@ -9,6 +9,7 @@
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
struct IndexFile;
|
||||
|
||||
@ -16,6 +17,19 @@ struct IndexFile;
|
||||
MAKE_HASHABLE(CXFileUniqueID, t.data[0], t.data[1], t.data[2]);
|
||||
bool operator==(const CXFileUniqueID& a, const CXFileUniqueID& b);
|
||||
|
||||
struct FileContents {
|
||||
FileContents();
|
||||
FileContents(const std::string& path, const std::string& content);
|
||||
|
||||
std::optional<int> ToOffset(Position p) const;
|
||||
std::optional<std::string> ContentsInRange(Range range) const;
|
||||
|
||||
std::string path;
|
||||
std::string content;
|
||||
// {0, 1 + position of first newline, 1 + position of second newline, ...}
|
||||
std::vector<int> line_offsets_;
|
||||
};
|
||||
|
||||
struct FileConsumerSharedState {
|
||||
mutable std::unordered_set<std::string> used_files;
|
||||
mutable std::mutex mutex;
|
||||
@ -48,7 +62,7 @@ struct FileConsumer {
|
||||
// variable since it is large and we do not want to copy it.
|
||||
IndexFile* TryConsumeFile(CXFile file,
|
||||
bool* is_first_ownership,
|
||||
FileContentsMap* file_contents);
|
||||
std::unordered_map<std::string, FileContents>* file_contents);
|
||||
|
||||
// Returns and passes ownership of all local state.
|
||||
std::vector<std::unique_ptr<IndexFile>> TakeLocalState();
|
||||
@ -59,4 +73,4 @@ struct FileConsumer {
|
||||
std::unordered_map<CXFileUniqueID, std::unique_ptr<IndexFile>> local_;
|
||||
FileConsumerSharedState* shared_;
|
||||
std::string parse_file_;
|
||||
};
|
||||
};
|
||||
|
@ -1,29 +0,0 @@
|
||||
#include "file_contents.h"
|
||||
|
||||
FileContents::FileContents() : line_offsets_{0} {}
|
||||
|
||||
FileContents::FileContents(const std::string& path, const std::string& content)
|
||||
: path(path), content(content) {
|
||||
line_offsets_.push_back(0);
|
||||
for (size_t i = 0; i < content.size(); i++) {
|
||||
if (content[i] == '\n')
|
||||
line_offsets_.push_back(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int> FileContents::ToOffset(Position p) const {
|
||||
if (0 <= p.line && size_t(p.line) < line_offsets_.size()) {
|
||||
int ret = line_offsets_[p.line] + p.column;
|
||||
if (size_t(ret) < content.size())
|
||||
return ret;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> FileContents::ContentsInRange(Range range) const {
|
||||
std::optional<int> start_offset = ToOffset(range.start),
|
||||
end_offset = ToOffset(range.end);
|
||||
if (start_offset && end_offset && *start_offset < *end_offset)
|
||||
return content.substr(*start_offset, *end_offset - *start_offset);
|
||||
return std::nullopt;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "position.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct FileContents {
|
||||
FileContents();
|
||||
FileContents(const std::string& path, const std::string& content);
|
||||
|
||||
std::optional<int> ToOffset(Position p) const;
|
||||
std::optional<std::string> ContentsInRange(Range range) const;
|
||||
|
||||
std::string path;
|
||||
std::string content;
|
||||
// {0, 1 + position of first newline, 1 + position of second newline, ...}
|
||||
std::vector<int> line_offsets_;
|
||||
};
|
||||
|
||||
using FileContentsMap = std::unordered_map<std::string, FileContents>;
|
@ -4,7 +4,6 @@
|
||||
#include "clang_translation_unit.h"
|
||||
#include "clang_utils.h"
|
||||
#include "file_consumer.h"
|
||||
#include "file_contents.h"
|
||||
#include "language.h"
|
||||
#include "lsp.h"
|
||||
#include "maybe.h"
|
||||
@ -54,7 +53,7 @@ struct Id {
|
||||
// Needed for google::dense_hash_map.
|
||||
explicit operator RawId() const { return id; }
|
||||
|
||||
bool HasValueForMaybe_() const { return id != RawId(-1); }
|
||||
bool Valid() const { return id != RawId(-1); }
|
||||
|
||||
bool operator==(const Id& o) const { return id == o.id; }
|
||||
bool operator!=(const Id& o) const { return id != o.id; }
|
||||
@ -101,7 +100,7 @@ struct Reference {
|
||||
SymbolKind kind;
|
||||
Role role;
|
||||
|
||||
bool HasValueForMaybe_() const { return range.HasValueForMaybe_(); }
|
||||
bool Valid() const { return range.Valid(); }
|
||||
operator SymbolIdx() const { return {id, kind}; }
|
||||
std::tuple<Range, Id<void>, SymbolKind, Role> ToTuple() const {
|
||||
return std::make_tuple(range, id, kind, role);
|
||||
@ -126,8 +125,6 @@ struct Use : Reference {
|
||||
Use(Range range, Id<void> id, SymbolKind kind, Role role, Id<QueryFile> file)
|
||||
: Reference{range, id, kind, role}, file(file) {}
|
||||
};
|
||||
// Used by |HANDLE_MERGEABLE| so only |range| is needed.
|
||||
MAKE_HASHABLE(Use, t.range);
|
||||
|
||||
void Reflect(Reader& visitor, Reference& value);
|
||||
void Reflect(Writer& visitor, Reference& value);
|
||||
@ -241,9 +238,6 @@ struct IndexType {
|
||||
// NOTE: Do not insert directly! Use AddUsage instead.
|
||||
std::vector<Use> uses;
|
||||
|
||||
IndexType() {} // For serialization.
|
||||
IndexType(IndexTypeId id, Usr usr);
|
||||
|
||||
bool operator<(const IndexType& other) const { return id < other.id; }
|
||||
};
|
||||
MAKE_HASHABLE(IndexType, t.id);
|
||||
@ -336,9 +330,6 @@ struct IndexFunc : NameMixin<IndexFunc> {
|
||||
// def.spell.
|
||||
std::vector<Use> uses;
|
||||
|
||||
IndexFunc() {} // For serialization.
|
||||
IndexFunc(IndexFuncId id, Usr usr) : usr(usr), id(id) {}
|
||||
|
||||
bool operator<(const IndexFunc& other) const { return id < other.id; }
|
||||
};
|
||||
MAKE_HASHABLE(IndexFunc, t.id);
|
||||
@ -408,9 +399,6 @@ struct IndexVar {
|
||||
std::vector<Use> declarations;
|
||||
std::vector<Use> uses;
|
||||
|
||||
IndexVar() {} // For serialization.
|
||||
IndexVar(IndexVarId id, Usr usr) : usr(usr), id(id) {}
|
||||
|
||||
bool operator<(const IndexVar& other) const { return id < other.id; }
|
||||
};
|
||||
MAKE_HASHABLE(IndexVar, t.id);
|
||||
@ -519,7 +507,3 @@ std::vector<std::unique_ptr<IndexFile>> ParseWithTu(
|
||||
bool ConcatTypeAndName(std::string& type, const std::string& name);
|
||||
|
||||
void IndexInit();
|
||||
|
||||
void ClangSanityCheck();
|
||||
|
||||
std::string GetClangVersion();
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <utility>
|
||||
|
||||
// Like std::optional, but the stored data is responsible for containing the empty
|
||||
// state. T should define a function `bool T::HasValueForMaybe_()`.
|
||||
// state. T should define a function `bool T::Valid()`.
|
||||
template <typename T>
|
||||
class Maybe {
|
||||
T storage;
|
||||
@ -28,10 +28,10 @@ class Maybe {
|
||||
const T& operator*() const { return storage; }
|
||||
T& operator*() { return storage; }
|
||||
|
||||
bool HasValue() const { return storage.HasValueForMaybe_(); }
|
||||
explicit operator bool() const { return HasValue(); }
|
||||
bool Valid() const { return storage.Valid(); }
|
||||
explicit operator bool() const { return Valid(); }
|
||||
operator std::optional<T>() const {
|
||||
if (HasValue())
|
||||
if (Valid())
|
||||
return storage;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -105,7 +105,8 @@ struct Handler_TextDocumentDefinition
|
||||
if (uses.empty() && on_def)
|
||||
uses.push_back(*on_def);
|
||||
}
|
||||
AddRange(&out.result, GetLsLocationExs(db, working_files, uses));
|
||||
auto locs = GetLsLocationExs(db, working_files, uses);
|
||||
out.result.insert(out.result.end(), locs.begin(), locs.end());
|
||||
if (!out.result.empty())
|
||||
break;
|
||||
}
|
||||
|
@ -5,13 +5,3 @@ MethodType kMethodType_Exit = "exit";
|
||||
MethodType kMethodType_TextDocumentPublishDiagnostics = "textDocument/publishDiagnostics";
|
||||
MethodType kMethodType_CclsPublishInactiveRegions = "$ccls/publishInactiveRegions";
|
||||
MethodType kMethodType_CclsPublishSemanticHighlighting = "$ccls/publishSemanticHighlighting";
|
||||
|
||||
InMessage::~InMessage() = default;
|
||||
|
||||
lsRequestId RequestInMessage::GetRequestId() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
lsRequestId NotificationInMessage::GetRequestId() const {
|
||||
return std::monostate();
|
||||
}
|
10
src/method.h
10
src/method.h
@ -15,7 +15,7 @@ extern MethodType kMethodType_CclsPublishSemanticHighlighting;
|
||||
using lsRequestId = std::variant<std::monostate, int64_t, std::string>;
|
||||
|
||||
struct InMessage {
|
||||
virtual ~InMessage();
|
||||
virtual ~InMessage() = default;
|
||||
|
||||
virtual MethodType GetMethodType() const = 0;
|
||||
virtual lsRequestId GetRequestId() const = 0;
|
||||
@ -24,10 +24,14 @@ struct InMessage {
|
||||
struct RequestInMessage : public InMessage {
|
||||
// number or string, actually no null
|
||||
lsRequestId id;
|
||||
lsRequestId GetRequestId() const override;
|
||||
lsRequestId GetRequestId() const override {
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
// NotificationInMessage does not have |id|.
|
||||
struct NotificationInMessage : public InMessage {
|
||||
lsRequestId GetRequestId() const override;
|
||||
lsRequestId GetRequestId() const override {
|
||||
return std::monostate();
|
||||
}
|
||||
};
|
||||
|
133
src/position.cc
133
src/position.cc
@ -1,74 +1,29 @@
|
||||
#include "position.h"
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Position::Position() : line(-1), column(-1) {}
|
||||
|
||||
Position::Position(int16_t line, int16_t column) : line(line), column(column) {}
|
||||
|
||||
Position::Position(const char* encoded) {
|
||||
char* p = const_cast<char*>(encoded);
|
||||
line = int16_t(strtol(p, &p, 10)) - 1;
|
||||
Position Position::FromString(const std::string& encoded) {
|
||||
char* p = const_cast<char*>(encoded.c_str());
|
||||
int16_t line = int16_t(strtol(p, &p, 10)) - 1;
|
||||
assert(*p == ':');
|
||||
p++;
|
||||
column = int16_t(strtol(p, &p, 10)) - 1;
|
||||
int16_t column = int16_t(strtol(p, &p, 10)) - 1;
|
||||
return {line, column};
|
||||
}
|
||||
|
||||
std::string Position::ToString() {
|
||||
// Output looks like this:
|
||||
//
|
||||
// 1:2
|
||||
//
|
||||
// 1 => line
|
||||
// 2 => column
|
||||
|
||||
std::string result;
|
||||
result += std::to_string(line + 1);
|
||||
result += ':';
|
||||
result += std::to_string(column + 1);
|
||||
return result;
|
||||
char buf[99];
|
||||
snprintf(buf, sizeof buf, "%d:%d", line + 1, column + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string Position::ToPrettyString(const std::string& filename) {
|
||||
// Output looks like this:
|
||||
//
|
||||
// 1:2:3
|
||||
//
|
||||
// 1 => filename
|
||||
// 2 => line
|
||||
// 3 => column
|
||||
|
||||
std::string result;
|
||||
result += filename;
|
||||
result += ':';
|
||||
result += std::to_string(line + 1);
|
||||
result += ':';
|
||||
result += std::to_string(column + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Position::operator==(const Position& that) const {
|
||||
return line == that.line && column == that.column;
|
||||
}
|
||||
|
||||
bool Position::operator!=(const Position& that) const {
|
||||
return !(*this == that);
|
||||
}
|
||||
|
||||
bool Position::operator<(const Position& that) const {
|
||||
if (line != that.line)
|
||||
return line < that.line;
|
||||
return column < that.column;
|
||||
}
|
||||
|
||||
Range::Range() {}
|
||||
|
||||
Range::Range(Position position) : Range(position, position) {}
|
||||
|
||||
Range::Range(Position start, Position end) : start(start), end(end) {}
|
||||
|
||||
Range::Range(const char* encoded) {
|
||||
char* p = const_cast<char*>(encoded);
|
||||
Range Range::FromString(const std::string& encoded) {
|
||||
Position start, end;
|
||||
char* p = const_cast<char*>(encoded.c_str());
|
||||
start.line = int16_t(strtol(p, &p, 10)) - 1;
|
||||
assert(*p == ':');
|
||||
p++;
|
||||
@ -80,18 +35,14 @@ Range::Range(const char* encoded) {
|
||||
assert(*p == ':');
|
||||
p++;
|
||||
end.column = int16_t(strtol(p, nullptr, 10)) - 1;
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
bool Range::Contains(int line, int column) const {
|
||||
if (line == start.line && line == end.line)
|
||||
return column >= start.column && column < end.column;
|
||||
if (line == start.line)
|
||||
return column >= start.column;
|
||||
if (line == end.line)
|
||||
return column < end.column;
|
||||
if (line > start.line && line < end.line)
|
||||
return true;
|
||||
return false;
|
||||
if (line > INT16_MAX)
|
||||
return false;
|
||||
Position p{int16_t(line), int16_t(std::min(column, INT16_MAX))};
|
||||
return !(p < start) && p < end;
|
||||
}
|
||||
|
||||
Range Range::RemovePrefix(Position position) const {
|
||||
@ -99,47 +50,16 @@ Range Range::RemovePrefix(Position position) const {
|
||||
}
|
||||
|
||||
std::string Range::ToString() {
|
||||
// Output looks like this:
|
||||
//
|
||||
// 1:2-3:4
|
||||
//
|
||||
// 1 => start line
|
||||
// 2 => start column
|
||||
// 3 => end line
|
||||
// 4 => end column
|
||||
|
||||
std::string output;
|
||||
|
||||
output += std::to_string(start.line + 1);
|
||||
output += ':';
|
||||
output += std::to_string(start.column + 1);
|
||||
output += '-';
|
||||
output += std::to_string(end.line + 1);
|
||||
output += ':';
|
||||
output += std::to_string(end.column + 1);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool Range::operator==(const Range& that) const {
|
||||
return start == that.start && end == that.end;
|
||||
}
|
||||
|
||||
bool Range::operator!=(const Range& that) const {
|
||||
return !(*this == that);
|
||||
}
|
||||
|
||||
bool Range::operator<(const Range& that) const {
|
||||
if (start != that.start)
|
||||
return start < that.start;
|
||||
return end < that.end;
|
||||
char buf[99];
|
||||
snprintf(buf, sizeof buf, "%d:%d-%d:%d", start.line + 1, start.column + 1,
|
||||
end.line + 1, end.column + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Position
|
||||
void Reflect(Reader& visitor, Position& value) {
|
||||
if (visitor.Format() == SerializeFormat::Json) {
|
||||
std::string s = visitor.GetString();
|
||||
value = Position(s.c_str());
|
||||
value = Position::FromString(visitor.GetString());
|
||||
} else {
|
||||
Reflect(visitor, value.line);
|
||||
Reflect(visitor, value.column);
|
||||
@ -158,8 +78,7 @@ void Reflect(Writer& visitor, Position& value) {
|
||||
// Range
|
||||
void Reflect(Reader& visitor, Range& value) {
|
||||
if (visitor.Format() == SerializeFormat::Json) {
|
||||
std::string s = visitor.GetString();
|
||||
value = Range(s.c_str());
|
||||
value = Range::FromString(visitor.GetString());
|
||||
} else {
|
||||
Reflect(visitor, value.start.line);
|
||||
Reflect(visitor, value.start.column);
|
||||
|
@ -1,57 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "maybe.h"
|
||||
#include "serializer.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
struct Position {
|
||||
int16_t line;
|
||||
int16_t column;
|
||||
int16_t line = -1;
|
||||
int16_t column = -1;
|
||||
|
||||
Position();
|
||||
Position(int16_t line, int16_t column);
|
||||
explicit Position(const char* encoded);
|
||||
static Position FromString(const std::string& encoded);
|
||||
|
||||
bool HasValueForMaybe_() const { return line >= 0; }
|
||||
bool Valid() const { return line >= 0; }
|
||||
std::string ToString();
|
||||
std::string ToPrettyString(const std::string& filename);
|
||||
|
||||
// Compare two Positions and check if they are equal. Ignores the value of
|
||||
// |interesting|.
|
||||
bool operator==(const Position& that) const;
|
||||
bool operator!=(const Position& that) const;
|
||||
bool operator<(const Position& that) const;
|
||||
bool operator==(const Position& o) const {
|
||||
return line == o.line && column == o.column;
|
||||
}
|
||||
bool operator<(const Position& o) const {
|
||||
if (line != o.line)
|
||||
return line < o.line;
|
||||
return column < o.column;
|
||||
}
|
||||
};
|
||||
static_assert(
|
||||
sizeof(Position) == 4,
|
||||
"Investigate, Position should be 32-bits for indexer size reasons");
|
||||
MAKE_HASHABLE(Position, t.line, t.column);
|
||||
|
||||
struct Range {
|
||||
Position start;
|
||||
Position end;
|
||||
|
||||
Range();
|
||||
explicit Range(Position position);
|
||||
Range(Position start, Position end);
|
||||
explicit Range(const char* encoded);
|
||||
static Range FromString(const std::string& encoded);
|
||||
|
||||
bool HasValueForMaybe_() const { return start.HasValueForMaybe_(); }
|
||||
bool Valid() const { return start.Valid(); }
|
||||
bool Contains(int line, int column) const;
|
||||
Range RemovePrefix(Position position) const;
|
||||
|
||||
std::string ToString();
|
||||
|
||||
bool operator==(const Range& that) const;
|
||||
bool operator!=(const Range& that) const;
|
||||
bool operator<(const Range& that) const;
|
||||
bool operator==(const Range& o) const {
|
||||
return start == o.start && end == o.end;
|
||||
}
|
||||
bool operator<(const Range& o) const {
|
||||
return !(start == o.start) ? start < o.start : end < o.end;
|
||||
}
|
||||
};
|
||||
MAKE_HASHABLE(Range, t.start, t.end);
|
||||
|
||||
// Reflection
|
||||
class Reader;
|
||||
class Writer;
|
||||
void Reflect(Reader& visitor, Position& value);
|
||||
void Reflect(Writer& visitor, Position& value);
|
||||
void Reflect(Reader& visitor, Range& value);
|
||||
|
270
src/query.cc
270
src/query.cc
@ -11,14 +11,30 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
// TODO: Make all copy constructors explicit.
|
||||
|
||||
// Used by |HANDLE_MERGEABLE| so only |range| is needed.
|
||||
MAKE_HASHABLE(Range, t.start, t.end);
|
||||
MAKE_HASHABLE(Use, t.range);
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void AddRange(std::vector<T>* dest, const std::vector<T>& to_add) {
|
||||
dest->insert(dest->end(), to_add.begin(), to_add.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AddRange(std::vector<T>* dest, std::vector<T>&& to_add) {
|
||||
dest->insert(dest->end(), std::make_move_iterator(to_add.begin()),
|
||||
std::make_move_iterator(to_add.end()));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RemoveRange(std::vector<T>* dest, const std::vector<T>& to_remove) {
|
||||
std::unordered_set<T> to_remove_set(to_remove.begin(), to_remove.end());
|
||||
@ -897,7 +913,7 @@ void QueryDatabase::ImportOrUpdate(std::vector<QueryVar::DefUpdate>&& updates) {
|
||||
void QueryDatabase::UpdateSymbols(Maybe<Id<void>>* symbol_idx,
|
||||
SymbolKind kind,
|
||||
Id<void> idx) {
|
||||
if (!symbol_idx->HasValue()) {
|
||||
if (!symbol_idx->Valid()) {
|
||||
*symbol_idx = Id<void>(symbols.size());
|
||||
symbols.push_back(SymbolIdx{idx, kind});
|
||||
}
|
||||
@ -928,255 +944,3 @@ std::string_view QueryDatabase::GetSymbolName(RawId symbol_idx,
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
TEST_SUITE("query") {
|
||||
IndexUpdate GetDelta(IndexFile previous, IndexFile current) {
|
||||
QueryDatabase db;
|
||||
IdMap previous_map(&db, previous.id_cache);
|
||||
IdMap current_map(&db, current.id_cache);
|
||||
return IndexUpdate::CreateDelta(&previous_map, ¤t_map, &previous,
|
||||
¤t);
|
||||
}
|
||||
|
||||
TEST_CASE("remove defs") {
|
||||
IndexFile previous("foo.cc", "<empty>");
|
||||
IndexFile current("foo.cc", "<empty>");
|
||||
|
||||
previous.Resolve(previous.ToTypeId(HashUsr("usr1")))->def.spell =
|
||||
Use(Range(Position(1, 0)), {}, {}, {}, {});
|
||||
previous.Resolve(previous.ToFuncId(HashUsr("usr2")))->def.spell =
|
||||
Use(Range(Position(2, 0)), {}, {}, {}, {});
|
||||
previous.Resolve(previous.ToVarId(HashUsr("usr3")))->def.spell =
|
||||
Use(Range(Position(3, 0)), {}, {}, {}, {});
|
||||
|
||||
IndexUpdate update = GetDelta(previous, current);
|
||||
|
||||
REQUIRE(update.types_removed == std::vector<Usr>{HashUsr("usr1")});
|
||||
REQUIRE(update.funcs_removed.size() == 1);
|
||||
REQUIRE(update.funcs_removed[0].usr == HashUsr("usr2"));
|
||||
REQUIRE(update.vars_removed.size() == 1);
|
||||
REQUIRE(update.vars_removed[0].usr == HashUsr("usr3"));
|
||||
}
|
||||
|
||||
TEST_CASE("do not remove ref-only defs") {
|
||||
IndexFile previous("foo.cc", "<empty>");
|
||||
IndexFile current("foo.cc", "<empty>");
|
||||
|
||||
previous.Resolve(previous.ToTypeId(HashUsr("usr1")))
|
||||
->uses.push_back(Use{Range(Position(1, 0)), {}, {}, {}, {}});
|
||||
previous.Resolve(previous.ToFuncId(HashUsr("usr2")))
|
||||
->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
|
||||
previous.Resolve(previous.ToVarId(HashUsr("usr3")))
|
||||
->uses.push_back(Use(Range(Position(3, 0)), {}, {}, {}, {}));
|
||||
|
||||
IndexUpdate update = GetDelta(previous, current);
|
||||
|
||||
REQUIRE(update.types_removed == std::vector<Usr>{});
|
||||
REQUIRE(update.funcs_removed.empty());
|
||||
REQUIRE(update.vars_removed.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("func callers") {
|
||||
IndexFile previous("foo.cc", "<empty>");
|
||||
IndexFile current("foo.cc", "<empty>");
|
||||
|
||||
IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr")));
|
||||
IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr")));
|
||||
|
||||
pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {}));
|
||||
cf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
|
||||
|
||||
IndexUpdate update = GetDelta(previous, current);
|
||||
|
||||
REQUIRE(update.funcs_removed.empty());
|
||||
REQUIRE(update.funcs_uses.size() == 1);
|
||||
REQUIRE(update.funcs_uses[0].id == QueryFuncId(0));
|
||||
REQUIRE(update.funcs_uses[0].to_remove.size() == 1);
|
||||
REQUIRE(update.funcs_uses[0].to_remove[0].range == Range(Position(1, 0)));
|
||||
REQUIRE(update.funcs_uses[0].to_add.size() == 1);
|
||||
REQUIRE(update.funcs_uses[0].to_add[0].range == Range(Position(2, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("type usages") {
|
||||
IndexFile previous("foo.cc", "<empty>");
|
||||
IndexFile current("foo.cc", "<empty>");
|
||||
|
||||
IndexType* pt = previous.Resolve(previous.ToTypeId(HashUsr("usr")));
|
||||
IndexType* ct = current.Resolve(current.ToTypeId(HashUsr("usr")));
|
||||
|
||||
pt->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {}));
|
||||
ct->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
|
||||
|
||||
IndexUpdate update = GetDelta(previous, current);
|
||||
|
||||
REQUIRE(update.types_removed == std::vector<Usr>{});
|
||||
REQUIRE(update.types_def_update.empty());
|
||||
REQUIRE(update.types_uses.size() == 1);
|
||||
REQUIRE(update.types_uses[0].to_remove.size() == 1);
|
||||
REQUIRE(update.types_uses[0].to_remove[0].range == Range(Position(1, 0)));
|
||||
REQUIRE(update.types_uses[0].to_add.size() == 1);
|
||||
REQUIRE(update.types_uses[0].to_add[0].range == Range(Position(2, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("apply delta") {
|
||||
IndexFile previous("foo.cc", "<empty>");
|
||||
IndexFile current("foo.cc", "<empty>");
|
||||
|
||||
IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr")));
|
||||
IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr")));
|
||||
pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {}));
|
||||
pf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {}));
|
||||
cf->uses.push_back(Use(Range(Position(4, 0)), {}, {}, {}, {}));
|
||||
cf->uses.push_back(Use(Range(Position(5, 0)), {}, {}, {}, {}));
|
||||
|
||||
QueryDatabase db;
|
||||
IdMap previous_map(&db, previous.id_cache);
|
||||
IdMap current_map(&db, current.id_cache);
|
||||
REQUIRE(db.funcs.size() == 1);
|
||||
|
||||
IndexUpdate import_update =
|
||||
IndexUpdate::CreateDelta(nullptr, &previous_map, nullptr, &previous);
|
||||
IndexUpdate delta_update = IndexUpdate::CreateDelta(
|
||||
&previous_map, ¤t_map, &previous, ¤t);
|
||||
|
||||
db.ApplyIndexUpdate(&import_update);
|
||||
REQUIRE(db.funcs[0].uses.size() == 2);
|
||||
REQUIRE(db.funcs[0].uses[0].range == Range(Position(1, 0)));
|
||||
REQUIRE(db.funcs[0].uses[1].range == Range(Position(2, 0)));
|
||||
|
||||
db.ApplyIndexUpdate(&delta_update);
|
||||
REQUIRE(db.funcs[0].uses.size() == 2);
|
||||
REQUIRE(db.funcs[0].uses[0].range == Range(Position(4, 0)));
|
||||
REQUIRE(db.funcs[0].uses[1].range == Range(Position(5, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("Remove variable with usage") {
|
||||
auto load_index_from_json = [](const char* json) {
|
||||
return Deserialize(SerializeFormat::Json, "foo.cc", json, "<empty>",
|
||||
std::nullopt);
|
||||
};
|
||||
|
||||
auto previous = load_index_from_json(R"RAW(
|
||||
{
|
||||
"types": [
|
||||
{
|
||||
"id": 0,
|
||||
"usr": 17,
|
||||
"detailed_name": "",
|
||||
"short_name_offset": 0,
|
||||
"short_name_size": 0,
|
||||
"kind": 0,
|
||||
"hover": "",
|
||||
"comments": "",
|
||||
"parents": [],
|
||||
"derived": [],
|
||||
"types": [],
|
||||
"funcs": [],
|
||||
"vars": [],
|
||||
"instances": [
|
||||
0
|
||||
],
|
||||
"uses": []
|
||||
}
|
||||
],
|
||||
"funcs": [
|
||||
{
|
||||
"id": 0,
|
||||
"usr": 4259594751088586730,
|
||||
"detailed_name": "void foo()",
|
||||
"short_name_offset": 5,
|
||||
"short_name_size": 3,
|
||||
"kind": 12,
|
||||
"storage": 1,
|
||||
"hover": "",
|
||||
"comments": "",
|
||||
"declarations": [],
|
||||
"spell": "1:6-1:9|-1|1|2",
|
||||
"extent": "1:1-4:2|-1|1|0",
|
||||
"base": [],
|
||||
"derived": [],
|
||||
"locals": [],
|
||||
"uses": [],
|
||||
"callees": []
|
||||
}
|
||||
],
|
||||
"vars": [
|
||||
{
|
||||
"id": 0,
|
||||
"usr": 16837348799350457167,
|
||||
"detailed_name": "int a",
|
||||
"short_name_offset": 4,
|
||||
"short_name_size": 1,
|
||||
"hover": "",
|
||||
"comments": "",
|
||||
"declarations": [],
|
||||
"spell": "2:7-2:8|0|3|2",
|
||||
"extent": "2:3-2:8|0|3|2",
|
||||
"type": 0,
|
||||
"uses": [
|
||||
"3:3-3:4|0|3|4"
|
||||
],
|
||||
"kind": 13,
|
||||
"storage": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
)RAW");
|
||||
|
||||
auto current = load_index_from_json(R"RAW(
|
||||
{
|
||||
"types": [],
|
||||
"funcs": [
|
||||
{
|
||||
"id": 0,
|
||||
"usr": 4259594751088586730,
|
||||
"detailed_name": "void foo()",
|
||||
"short_name_offset": 5,
|
||||
"short_name_size": 3,
|
||||
"kind": 12,
|
||||
"storage": 1,
|
||||
"hover": "",
|
||||
"comments": "",
|
||||
"declarations": [],
|
||||
"spell": "1:6-1:9|-1|1|2",
|
||||
"extent": "1:1-5:2|-1|1|0",
|
||||
"base": [],
|
||||
"derived": [],
|
||||
"locals": [],
|
||||
"uses": [],
|
||||
"callees": []
|
||||
}
|
||||
],
|
||||
"vars": []
|
||||
}
|
||||
)RAW");
|
||||
|
||||
// Validate previous/current were parsed.
|
||||
REQUIRE(previous->vars.size() == 1);
|
||||
REQUIRE(current->vars.size() == 0);
|
||||
|
||||
QueryDatabase db;
|
||||
|
||||
// Apply initial file.
|
||||
{
|
||||
IdMap previous_map(&db, previous->id_cache);
|
||||
IndexUpdate import_update = IndexUpdate::CreateDelta(
|
||||
nullptr, &previous_map, nullptr, previous.get());
|
||||
db.ApplyIndexUpdate(&import_update);
|
||||
}
|
||||
|
||||
REQUIRE(db.vars.size() == 1);
|
||||
REQUIRE(db.vars[0].uses.size() == 1);
|
||||
|
||||
// Apply change.
|
||||
{
|
||||
IdMap previous_map(&db, previous->id_cache);
|
||||
IdMap current_map(&db, current->id_cache);
|
||||
IndexUpdate delta_update = IndexUpdate::CreateDelta(
|
||||
&previous_map, ¤t_map, previous.get(), current.get());
|
||||
db.ApplyIndexUpdate(&delta_update);
|
||||
}
|
||||
REQUIRE(db.vars.size() == 1);
|
||||
REQUIRE(db.vars[0].uses.size() == 0);
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ Maybe<Use> GetDefinitionSpell(QueryDatabase* db, SymbolIdx sym) {
|
||||
Maybe<Use> GetDefinitionExtent(QueryDatabase* db, SymbolIdx sym) {
|
||||
// Used to jump to file.
|
||||
if (sym.kind == SymbolKind::File)
|
||||
return Use(Range(Position(0, 0), Position(0, 0)), sym.id, sym.kind,
|
||||
Role::None, QueryFileId(sym.id));
|
||||
return Use(Range{{0, 0}, {0, 0}}, sym.id, sym.kind, Role::None,
|
||||
QueryFileId(sym.id));
|
||||
Maybe<Use> ret;
|
||||
EachEntityDef(db, sym, [&](const auto& def) { return !(ret = def.extent); });
|
||||
return ret;
|
||||
@ -126,7 +126,7 @@ std::vector<Use> GetUsesForAllBases(QueryDatabase* db, QueryFunc& root) {
|
||||
if (!seen.count(func1.usr)) {
|
||||
seen.insert(func1.usr);
|
||||
stack.push_back(&func1);
|
||||
AddRange(&ret, func1.uses);
|
||||
ret.insert(ret.end(), func1.uses.begin(), func1.uses.end());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -147,7 +147,7 @@ std::vector<Use> GetUsesForAllDerived(QueryDatabase* db, QueryFunc& root) {
|
||||
if (!seen.count(func1.usr)) {
|
||||
seen.insert(func1.usr);
|
||||
stack.push_back(&func1);
|
||||
AddRange(&ret, func1.uses);
|
||||
ret.insert(ret.end(), func1.uses.begin(), func1.uses.end());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ void ReflectMember(Writer& visitor, const char* name, std::optional<T>& value) {
|
||||
// The same as std::optional
|
||||
template <typename T>
|
||||
void ReflectMember(Writer& visitor, const char* name, Maybe<T>& value) {
|
||||
if (value.HasValue() || visitor.Format() != SerializeFormat::Json) {
|
||||
if (value.Valid() || visitor.Format() != SerializeFormat::Json) {
|
||||
visitor.Key(name);
|
||||
Reflect(visitor, value);
|
||||
}
|
||||
|
@ -229,16 +229,17 @@ IndexFile* FindDbForPathEnding(
|
||||
|
||||
bool RunIndexTests(const std::string& filter_path, bool enable_update) {
|
||||
gTestOutputMode = true;
|
||||
std::string version = ToString(clang_getClangVersion());
|
||||
|
||||
// Index tests change based on the version of clang used.
|
||||
static const char kRequiredClangVersion[] =
|
||||
"clang version 6.0.0 (tags/RELEASE_600/final)";
|
||||
if (GetClangVersion() != kRequiredClangVersion &&
|
||||
GetClangVersion().find("trunk") == std::string::npos) {
|
||||
if (version != kRequiredClangVersion &&
|
||||
version.find("trunk") == std::string::npos) {
|
||||
fprintf(stderr,
|
||||
"Index tests must be run using clang version %s, ccls is running "
|
||||
"with %s\n",
|
||||
kRequiredClangVersion, GetClangVersion().c_str());
|
||||
kRequiredClangVersion, version.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
11
src/utils.h
11
src/utils.h
@ -65,17 +65,6 @@ std::optional<std::string> ReadContent(const std::string& filename);
|
||||
|
||||
void WriteToFile(const std::string& filename, const std::string& content);
|
||||
|
||||
template <typename T>
|
||||
void AddRange(std::vector<T>* dest, const std::vector<T>& to_add) {
|
||||
dest->insert(dest->end(), to_add.begin(), to_add.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AddRange(std::vector<T>* dest, std::vector<T>&& to_add) {
|
||||
dest->insert(dest->end(), std::make_move_iterator(to_add.begin()),
|
||||
std::make_move_iterator(to_add.end()));
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/38140932
|
||||
//
|
||||
// struct SomeHashKey {
|
||||
|
Loading…
Reference in New Issue
Block a user