mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-22 07:35:08 +00:00
Enable partial document change sync, use delta info to keep references/code lens in sync when editing
This commit is contained in:
parent
3cd9ae4d16
commit
6e90f8db45
@ -82,10 +82,21 @@ optional<QueryableLocation> GetDefinitionSpellingOfSymbol(QueryableDatabase* db,
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
lsRange GetLsRange(const Range& location) {
|
||||
lsRange GetLsRange(WorkingFile* working_file, const Range& location) {
|
||||
if (!working_file) {
|
||||
return lsRange(
|
||||
lsPosition(location.start.line - 1, location.start.column - 1),
|
||||
lsPosition(location.end.line - 1, location.end.column - 1));
|
||||
}
|
||||
|
||||
return lsRange(
|
||||
lsPosition(location.start.line - 1, location.start.column - 1),
|
||||
lsPosition(location.end.line - 1, location.end.column - 1));
|
||||
lsPosition(working_file->GetBufferLineFromDiskLine(location.start.line) - 1, location.start.column - 1),
|
||||
lsPosition(working_file->GetBufferLineFromDiskLine(location.end.line) - 1, location.end.column - 1));
|
||||
}
|
||||
|
||||
lsDocumentUri GetLsDocumentUri(QueryableDatabase* db, QueryFileId file_id, std::string* path) {
|
||||
*path = db->files[file_id.id].def.usr;
|
||||
return lsDocumentUri::FromPath(*path);
|
||||
}
|
||||
|
||||
lsDocumentUri GetLsDocumentUri(QueryableDatabase* db, QueryFileId file_id) {
|
||||
@ -93,15 +104,16 @@ lsDocumentUri GetLsDocumentUri(QueryableDatabase* db, QueryFileId file_id) {
|
||||
return lsDocumentUri::FromPath(path);
|
||||
}
|
||||
|
||||
lsLocation GetLsLocation(QueryableDatabase* db, const QueryableLocation& location) {
|
||||
return lsLocation(
|
||||
GetLsDocumentUri(db, location.path),
|
||||
GetLsRange(location.range));
|
||||
lsLocation GetLsLocation(QueryableDatabase* db, WorkingFiles* working_files, const QueryableLocation& location) {
|
||||
std::string path;
|
||||
lsDocumentUri uri = GetLsDocumentUri(db, location.path, &path);
|
||||
lsRange range = GetLsRange(working_files->GetFileByFilename(path), location.range);
|
||||
return lsLocation(uri, range);
|
||||
}
|
||||
|
||||
// Returns a symbol. The symbol will have the location pointing to the
|
||||
// definition.
|
||||
lsSymbolInformation GetSymbolInfo(QueryableDatabase* db, SymbolIdx symbol) {
|
||||
lsSymbolInformation GetSymbolInfo(QueryableDatabase* db, WorkingFiles* working_files, SymbolIdx symbol) {
|
||||
lsSymbolInformation info;
|
||||
|
||||
switch (symbol.kind) {
|
||||
@ -117,7 +129,7 @@ lsSymbolInformation GetSymbolInfo(QueryableDatabase* db, SymbolIdx symbol) {
|
||||
info.name = def->def.qualified_name;
|
||||
info.kind = lsSymbolKind::Class;
|
||||
if (def->def.definition_extent.has_value())
|
||||
info.location = GetLsLocation(db, def->def.definition_extent.value());
|
||||
info.location = GetLsLocation(db, working_files, def->def.definition_extent.value());
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
@ -132,7 +144,7 @@ lsSymbolInformation GetSymbolInfo(QueryableDatabase* db, SymbolIdx symbol) {
|
||||
}
|
||||
|
||||
if (def->def.definition_extent.has_value()) {
|
||||
info.location = GetLsLocation(db, def->def.definition_extent.value());
|
||||
info.location = GetLsLocation(db, working_files, def->def.definition_extent.value());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -142,7 +154,7 @@ lsSymbolInformation GetSymbolInfo(QueryableDatabase* db, SymbolIdx symbol) {
|
||||
info.kind = lsSymbolKind::Variable;
|
||||
|
||||
if (def->def.definition_extent.has_value()) {
|
||||
info.location = GetLsLocation(db, def->def.definition_extent.value());
|
||||
info.location = GetLsLocation(db, working_files, def->def.definition_extent.value());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -158,15 +170,17 @@ lsSymbolInformation GetSymbolInfo(QueryableDatabase* db, SymbolIdx symbol) {
|
||||
|
||||
void AddCodeLens(
|
||||
QueryableDatabase* db,
|
||||
WorkingFiles* working_files,
|
||||
std::vector<TCodeLens>* result,
|
||||
QueryableLocation loc,
|
||||
WorkingFile* working_file,
|
||||
const std::vector<QueryableLocation>& uses,
|
||||
bool exclude_loc,
|
||||
bool only_interesting,
|
||||
const char* singular,
|
||||
const char* plural) {
|
||||
TCodeLens code_lens;
|
||||
code_lens.range = GetLsRange(loc.range);
|
||||
code_lens.range = GetLsRange(working_file, loc.range);
|
||||
code_lens.command = lsCommand<lsCodeLensCommandArguments>();
|
||||
code_lens.command->command = "superindex.showReferences";
|
||||
code_lens.command->arguments.uri = GetLsDocumentUri(db, loc.path);
|
||||
@ -179,7 +193,7 @@ void AddCodeLens(
|
||||
continue;
|
||||
if (only_interesting && !use.range.interesting)
|
||||
continue;
|
||||
unique_uses.insert(GetLsLocation(db, use));
|
||||
unique_uses.insert(GetLsLocation(db, working_files, use));
|
||||
}
|
||||
code_lens.command->arguments.locations.assign(unique_uses.begin(),
|
||||
unique_uses.end());
|
||||
@ -236,6 +250,28 @@ std::vector<QueryableLocation> ToQueryableLocation(QueryableDatabase* db, const
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct Index_DoIndex {
|
||||
enum class Type {
|
||||
Import,
|
||||
@ -539,6 +575,21 @@ void QueryDbMainLoop(
|
||||
case IpcId::TextDocumentDidChange: {
|
||||
auto msg = static_cast<Ipc_TextDocumentDidChange*>(message.get());
|
||||
working_files->OnChange(msg->params);
|
||||
|
||||
|
||||
// Send an index update request.
|
||||
// TODO: we should only do this when we save. Figure out a way to handle code lens (dynamic offsets?)
|
||||
|
||||
#if false
|
||||
Index_DoIndex request(Index_DoIndex::Type::Update);
|
||||
//WorkingFile* changed = working_files->GetFileByFilename(msg->params.textDocument.uri.GetPath());
|
||||
optional<CompilationEntry> entry = project->FindCompilationEntryForFile(msg->params.textDocument.uri.GetPath());
|
||||
request.path = msg->params.textDocument.uri.GetPath();
|
||||
if (entry)
|
||||
request.args = entry->args;
|
||||
queue_do_index->Enqueue(std::move(request));
|
||||
#endif
|
||||
|
||||
//std::cerr << "Changing " << msg->params.textDocument.uri.GetPath() << std::endl;
|
||||
break;
|
||||
}
|
||||
@ -586,7 +637,7 @@ void QueryDbMainLoop(
|
||||
ref.loc.range.start.column <= target_column && ref.loc.range.end.column >= target_column) {
|
||||
optional<QueryableLocation> location = GetDefinitionSpellingOfSymbol(db, ref.idx);
|
||||
if (location)
|
||||
response.result.push_back(GetLsLocation(db, location.value()));
|
||||
response.result.push_back(GetLsLocation(db, working_files, location.value()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -609,8 +660,8 @@ void QueryDbMainLoop(
|
||||
|
||||
std::cerr << "File outline size is " << file->def.outline.size() << std::endl;
|
||||
for (SymbolRef ref : file->def.outline) {
|
||||
lsSymbolInformation info = GetSymbolInfo(db, ref.idx);
|
||||
info.location = GetLsLocation(db, ref.loc);
|
||||
lsSymbolInformation info = GetSymbolInfo(db, working_files, ref.idx);
|
||||
info.location = GetLsLocation(db, working_files, ref.loc);
|
||||
response.result.push_back(info);
|
||||
}
|
||||
|
||||
@ -631,6 +682,7 @@ void QueryDbMainLoop(
|
||||
std::cerr << "Unable to find file " << msg->params.textDocument.uri.GetPath() << std::endl;
|
||||
break;
|
||||
}
|
||||
WorkingFile* working_file = working_files->GetFileByFilename(file->def.usr);
|
||||
|
||||
for (SymbolRef ref : file->def.outline) {
|
||||
// NOTE: We OffsetColumn so that the code lens always show up in a
|
||||
@ -640,15 +692,15 @@ void QueryDbMainLoop(
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
QueryableTypeDef& def = db->types[symbol.idx];
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(0), def.uses,
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(0), working_file, def.uses,
|
||||
false /*exclude_loc*/, false /*only_interesting*/, "ref",
|
||||
"refs");
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(1), def.uses,
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(1), working_file, def.uses,
|
||||
false /*exclude_loc*/, true /*only_interesting*/, "iref",
|
||||
"irefs");
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(2), ToQueryableLocation(db, def.derived),
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(2), working_file, ToQueryableLocation(db, def.derived),
|
||||
false /*exclude_loc*/, false /*only_interesting*/, "derived", "derived");
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(3), ToQueryableLocation(db, def.instantiations),
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(3), working_file, ToQueryableLocation(db, def.instantiations),
|
||||
false /*exclude_loc*/, false /*only_interesting*/, "instantiation", "instantiations");
|
||||
break;
|
||||
}
|
||||
@ -657,17 +709,17 @@ void QueryDbMainLoop(
|
||||
//AddCodeLens(&response.result, ref.loc.OffsetStartColumn(0), def.uses,
|
||||
// false /*exclude_loc*/, false /*only_interesting*/, "reference",
|
||||
// "references");
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(1), ToQueryableLocation(db, def.callers),
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(1), working_file, ToQueryableLocation(db, def.callers),
|
||||
true /*exclude_loc*/, false /*only_interesting*/, "caller", "callers");
|
||||
//AddCodeLens(&response.result, ref.loc.OffsetColumn(2), def.def.callees,
|
||||
// false /*exclude_loc*/, false /*only_interesting*/, "callee", "callees");
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(3), ToQueryableLocation(db, def.derived),
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(3), working_file, ToQueryableLocation(db, def.derived),
|
||||
false /*exclude_loc*/, false /*only_interesting*/, "derived", "derived");
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
QueryableVarDef& def = db->vars[symbol.idx];
|
||||
AddCodeLens(db, &response.result, ref.loc.OffsetStartColumn(0), def.uses,
|
||||
AddCodeLens(db, working_files, &response.result, ref.loc.OffsetStartColumn(0), working_file, def.uses,
|
||||
true /*exclude_loc*/, false /*only_interesting*/, "reference",
|
||||
"references");
|
||||
break;
|
||||
@ -702,7 +754,7 @@ void QueryDbMainLoop(
|
||||
}
|
||||
|
||||
if (db->qualified_names[i].find(query) != std::string::npos)
|
||||
response.result.push_back(GetSymbolInfo(db, db->symbols[i]));
|
||||
response.result.push_back(GetSymbolInfo(db, working_files, db->symbols[i]));
|
||||
}
|
||||
|
||||
SendOutMessageToClient(language_client, response);
|
||||
@ -827,7 +879,7 @@ void LanguageServerStdinLoop(IpcMessageQueue* ipc) {
|
||||
//response.result.capabilities.textDocumentSync->change = lsTextDocumentSyncKind::Full;
|
||||
//response.result.capabilities.textDocumentSync->willSave = true;
|
||||
//response.result.capabilities.textDocumentSync->willSaveWaitUntil = true;
|
||||
response.result.capabilities.textDocumentSync = lsTextDocumentSyncKind::Full; // TODO: use incremental at some point
|
||||
response.result.capabilities.textDocumentSync = lsTextDocumentSyncKind::Incremental; // TODO: use incremental at some point
|
||||
|
||||
response.result.capabilities.completionProvider = lsCompletionOptions();
|
||||
response.result.capabilities.completionProvider->resolveProvider = false;
|
||||
@ -970,7 +1022,7 @@ int main(int argc, char** argv) {
|
||||
if (context.shouldExit())
|
||||
return res;
|
||||
|
||||
RunTests();
|
||||
//RunTests();
|
||||
return 0;
|
||||
}
|
||||
else if (options.find("--help") != options.end()) {
|
||||
|
10
src/query.h
10
src/query.h
@ -56,11 +56,11 @@ struct QueryableLocation {
|
||||
}
|
||||
};
|
||||
|
||||
class QueryableFile;
|
||||
class QueryableTypeDef;
|
||||
class QueryableFuncDef;
|
||||
class QueryableVarDef;
|
||||
class QueryableDatabase;
|
||||
struct QueryableFile;
|
||||
struct QueryableTypeDef;
|
||||
struct QueryableFuncDef;
|
||||
struct QueryableVarDef;
|
||||
struct QueryableDatabase;
|
||||
|
||||
enum class SymbolKind { Invalid, File, Type, Func, Var };
|
||||
struct SymbolIdx {
|
||||
|
@ -1,5 +1,55 @@
|
||||
#include "working_files.h"
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
||||
int offset = 0;
|
||||
|
||||
int remaining_lines = position.line;
|
||||
while (remaining_lines > 0) {
|
||||
if (content[offset] == '\n')
|
||||
--remaining_lines;
|
||||
++offset;
|
||||
}
|
||||
|
||||
return offset + position.character;
|
||||
}
|
||||
|
||||
int LineCount(std::string::const_iterator start, const std::string::const_iterator& end) {
|
||||
int count = 0;
|
||||
while (start != end) {
|
||||
if (*start == '\n')
|
||||
++count;
|
||||
++start;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool IsNextTokenNewline(const std::string& str, int start) {
|
||||
while (start < str.size()) {
|
||||
// Peek a token ahead for \r\n. We cannot check a token back because
|
||||
// isalpha('\r') returns false.
|
||||
if ((start + 1 < str.size()) && str[start] == '\r' && str[start + 1] == '\n')
|
||||
return true;
|
||||
|
||||
if (str[start] == '\n')
|
||||
return true;
|
||||
|
||||
if (!isalpha(str[start]))
|
||||
return false;
|
||||
|
||||
++start;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WorkingFile::Change::Change(int disk_line, int delta) : disk_line(disk_line), delta(delta) {}
|
||||
|
||||
WorkingFile::WorkingFile(const std::string& filename, const std::string& content)
|
||||
: filename(filename), content(content) {
|
||||
}
|
||||
@ -12,6 +62,101 @@ CXUnsavedFile WorkingFile::AsUnsavedFile() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
int WorkingFile::GetDiskLineFromBufferLine(int buffer_line) const {
|
||||
int running_delta = 0;
|
||||
|
||||
// TODO: The disk line could come *after* the buffer line, ie, we insert into the start of the document.
|
||||
// change disk_line=1, delta=4 // insert 4 lines before line 1
|
||||
// buffer_line=5 maps to disk_line=1
|
||||
//
|
||||
|
||||
for (const auto& change : changes) {
|
||||
int buffer_start = running_delta + change.disk_line;
|
||||
int buffer_end = running_delta + change.disk_line + change.delta;
|
||||
|
||||
if (buffer_line < buffer_start)
|
||||
break;
|
||||
|
||||
if (buffer_line >= buffer_start && buffer_line < buffer_end) {
|
||||
//if (buffer_line == change.disk_line || change.disk_line == 1) return change.disk_line;
|
||||
return change.disk_line;
|
||||
}
|
||||
|
||||
running_delta += change.delta;
|
||||
}
|
||||
|
||||
return buffer_line - running_delta;
|
||||
}
|
||||
|
||||
int WorkingFile::GetBufferLineFromDiskLine(int disk_line) const {
|
||||
int buffer_line = disk_line;
|
||||
|
||||
for (const auto& change : changes) {
|
||||
if (disk_line >= change.disk_line) {
|
||||
assert(change.delta != 0);
|
||||
|
||||
if (change.delta > 0)
|
||||
buffer_line += change.delta;
|
||||
else {
|
||||
if (disk_line < (change.disk_line + (-change.delta)))
|
||||
buffer_line -= disk_line - change.disk_line + 1;
|
||||
else
|
||||
buffer_line += change.delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//std::cerr << "disk_line " << disk_line << " => buffer_line " << buffer_line << std::endl;// << " (line_deltas.size() = " << line_deltas.size() << ")" << std::endl;
|
||||
return buffer_line;
|
||||
}
|
||||
|
||||
bool WorkingFile::IsDeletedDiskLine(int disk_line) const {
|
||||
for (const auto& change : changes) {
|
||||
if (change.delta < 0 &&
|
||||
disk_line >= change.disk_line && disk_line < (change.disk_line + (-change.delta))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorkingFile::IsBufferLineOnlyInBuffer(int buffer_line) const {
|
||||
//
|
||||
// 5: 2
|
||||
// 7: 3
|
||||
//
|
||||
// 1 -> 1
|
||||
// 2 -> 2
|
||||
// 3 -> 3
|
||||
// 4 -> 4
|
||||
// 5 -> 7
|
||||
// 6 -> 8
|
||||
// 7 -> 12
|
||||
// 8 -> 13
|
||||
//
|
||||
// lines 5,6 are virtual
|
||||
// lines 9,10,11 are virtual
|
||||
//
|
||||
//
|
||||
|
||||
int running_delta = 0;
|
||||
for (const auto& change : changes) {
|
||||
int buffer_start = change.disk_line + running_delta;
|
||||
int buffer_end = change.disk_line + running_delta + change.delta;
|
||||
|
||||
if (buffer_line < buffer_start)
|
||||
break;
|
||||
|
||||
if (buffer_line >= buffer_start && buffer_line < buffer_end)
|
||||
return true;
|
||||
|
||||
running_delta += change.delta;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
WorkingFile* WorkingFiles::GetFileByFilename(const std::string& filename) {
|
||||
for (auto& file : files) {
|
||||
if (file->filename == filename)
|
||||
@ -44,14 +189,61 @@ void WorkingFiles::OnChange(const Ipc_TextDocumentDidChange::Params& change) {
|
||||
// TODO: we should probably pay attention to versioning.
|
||||
|
||||
for (const Ipc_TextDocumentDidChange::lsTextDocumentContentChangeEvent& diff : change.contentChanges) {
|
||||
//std::cerr << "Applying rangeLength=" << diff.rangeLength;
|
||||
|
||||
// If range or rangeLength are emitted we replace everything, per the spec.
|
||||
if (diff.rangeLength == -1) {
|
||||
file->content = diff.text;
|
||||
}
|
||||
else {
|
||||
file->content.replace(file->content.begin(), file->content.begin() + diff.rangeLength, diff.text);
|
||||
int start_offset = GetOffsetForPosition(diff.range.start, file->content);
|
||||
int previous_line_count = diff.range.end.line - diff.range.start.line;
|
||||
int replace_line_count = LineCount(diff.text.begin(), diff.text.end());
|
||||
|
||||
int line_delta = replace_line_count - previous_line_count;
|
||||
if (line_delta != 0) {
|
||||
// language server stores line counts starting at 0, we start at 1
|
||||
int buffer_line = diff.range.start.line + 1;
|
||||
|
||||
// If we're adding a newline but not actually shifting any of the
|
||||
// contents of this line, we should mark the text prepended on the next
|
||||
// line.
|
||||
if (IsNextTokenNewline(file->content, start_offset))
|
||||
++buffer_line;
|
||||
|
||||
int disk_line = file->GetDiskLineFromBufferLine(buffer_line);
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < file->changes.size(); ++i) {
|
||||
auto& change = file->changes[i];
|
||||
if (change.disk_line == disk_line) {
|
||||
change.delta += line_delta;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (change.delta == 0)
|
||||
file->changes.erase(file->changes.begin() + i);
|
||||
}
|
||||
if (!found)
|
||||
file->changes.push_back(WorkingFile::Change(disk_line, line_delta));
|
||||
|
||||
//std::cerr << "(changes.size()=" << file->changes.size() << ") Inserted delta=" << line_delta << " at disk_line=" << disk_line << " with buffer_line=" << buffer_line << std::endl;
|
||||
//for (auto& change : file->changes)
|
||||
// std::cerr << " disk_line=" << change.disk_line << " delta=" << change.delta << std::endl;
|
||||
|
||||
std::sort(file->changes.begin(), file->changes.end(),
|
||||
[](const WorkingFile::Change& a, const WorkingFile::Change& b) {
|
||||
return a.disk_line < b.disk_line;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
file->content.replace(file->content.begin() + start_offset,
|
||||
file->content.begin() + start_offset + diff.rangeLength,
|
||||
diff.text);
|
||||
}
|
||||
}
|
||||
//std::cerr << std::endl << std::endl << "--------" << file->content << "--------" << std::endl << std::endl;
|
||||
}
|
||||
|
||||
void WorkingFiles::OnClose(const Ipc_TextDocumentDidClose::Params& close) {
|
||||
@ -75,3 +267,294 @@ std::vector<CXUnsavedFile> WorkingFiles::AsUnsavedFiles() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE("WorkingFiles");
|
||||
|
||||
TEST_CASE("buffer-to-disk no changes") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("buffer-to-disk start add") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(1, 3));
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == false);
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(4) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(5) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("buffer-to-disk past-start add") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, 3));
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == false);
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 2); // buffer-only
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 2); // buffer-only
|
||||
CHECK(f.GetDiskLineFromBufferLine(4) == 2); // buffer-only
|
||||
CHECK(f.GetDiskLineFromBufferLine(5) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(6) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("buffer-to-disk start remove") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(1, -3));
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == false);
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 4);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 5);
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 6);
|
||||
CHECK(f.GetDiskLineFromBufferLine(4) == 7);
|
||||
CHECK(f.GetDiskLineFromBufferLine(5) == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("buffer-to-disk past-start remove") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, -3));
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == false);
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 5);
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 6);
|
||||
CHECK(f.GetDiskLineFromBufferLine(4) == 7);
|
||||
CHECK(f.GetDiskLineFromBufferLine(5) == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("buffer 1-1 mapping") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, 5));
|
||||
f.changes.push_back(WorkingFile::Change(3, -2));
|
||||
f.changes.push_back(WorkingFile::Change(4, 3));
|
||||
|
||||
for (int i = 1; i < 20; ++i) {
|
||||
// TODO: no good disk line from change starting at line 1
|
||||
|
||||
int disk1_line = f.GetDiskLineFromBufferLine(i);
|
||||
int buffer_line = f.GetBufferLineFromDiskLine(disk1_line);
|
||||
int disk2_line = f.GetDiskLineFromBufferLine(buffer_line);
|
||||
|
||||
CHECK(disk1_line == disk2_line);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("disk-to-buffer deleted disk lines") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(4, -3));
|
||||
|
||||
CHECK(f.GetBufferLineFromDiskLine(1) == 1);
|
||||
CHECK(f.GetBufferLineFromDiskLine(2) == 2);
|
||||
CHECK(f.GetBufferLineFromDiskLine(3) == 3);
|
||||
CHECK(f.GetBufferLineFromDiskLine(4) == 3);
|
||||
CHECK(f.GetBufferLineFromDiskLine(5) == 3);
|
||||
CHECK(f.GetBufferLineFromDiskLine(6) == 3);
|
||||
CHECK(f.GetBufferLineFromDiskLine(7) == 4);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("disk-to-buffer mapping add remove") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, 5));
|
||||
f.changes.push_back(WorkingFile::Change(10, -2));
|
||||
|
||||
CHECK(f.GetBufferLineFromDiskLine(1) == 1);
|
||||
CHECK(f.GetBufferLineFromDiskLine(2) == 7);
|
||||
CHECK(f.GetBufferLineFromDiskLine(3) == 8);
|
||||
|
||||
CHECK(f.GetBufferLineFromDiskLine(8) == 13);
|
||||
CHECK(f.GetBufferLineFromDiskLine(9) == 14);
|
||||
CHECK(f.GetBufferLineFromDiskLine(10) == 14);
|
||||
CHECK(f.GetBufferLineFromDiskLine(11) == 14);
|
||||
CHECK(f.GetBufferLineFromDiskLine(12) == 15);
|
||||
}
|
||||
|
||||
TEST_CASE("disk-to-buffer mapping add") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, 5));
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(4) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(5) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(6) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(7) == 2);
|
||||
CHECK(f.GetDiskLineFromBufferLine(8) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("disk-to-buffer remove lines") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, -5));
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(1) == 1);
|
||||
CHECK(f.GetDiskLineFromBufferLine(2) == 7);
|
||||
CHECK(f.GetDiskLineFromBufferLine(3) == 8);
|
||||
CHECK(f.GetDiskLineFromBufferLine(4) == 9);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("disk-to-buffer overlapping") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(1, 5));
|
||||
f.changes.push_back(WorkingFile::Change(2, -2));
|
||||
f.changes.push_back(WorkingFile::Change(3, 3));
|
||||
|
||||
CHECK(f.GetBufferLineFromDiskLine(1) == 6);
|
||||
CHECK(f.GetBufferLineFromDiskLine(2) == 6);
|
||||
CHECK(f.GetBufferLineFromDiskLine(3) == 9);
|
||||
CHECK(f.GetBufferLineFromDiskLine(4) == 10);
|
||||
CHECK(f.GetBufferLineFromDiskLine(5) == 11);
|
||||
CHECK(f.GetBufferLineFromDiskLine(6) == 12);
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(6) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(7) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(8) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(9) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(10) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(11) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(12) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("is buffer line removing lines") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, -2));
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("is buffer line adding lines") {
|
||||
|
||||
//
|
||||
// 5: 2
|
||||
// 7: 3
|
||||
//
|
||||
// 1 -> 1
|
||||
// 2 -> 2
|
||||
// 3 -> 3
|
||||
// 4 -> 4
|
||||
// 5 -> 7
|
||||
// 6 -> 8
|
||||
// 7 -> 12
|
||||
// 8 -> 13
|
||||
//
|
||||
// lines 5,6 are virtual
|
||||
// lines 9,10,11 are virtual
|
||||
//
|
||||
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(5, 2));
|
||||
f.changes.push_back(WorkingFile::Change(7, 3));
|
||||
|
||||
CHECK(f.GetBufferLineFromDiskLine(1) == 1);
|
||||
CHECK(f.GetBufferLineFromDiskLine(2) == 2);
|
||||
CHECK(f.GetBufferLineFromDiskLine(3) == 3);
|
||||
CHECK(f.GetBufferLineFromDiskLine(4) == 4);
|
||||
CHECK(f.GetBufferLineFromDiskLine(5) == 7);
|
||||
CHECK(f.GetBufferLineFromDiskLine(6) == 8);
|
||||
CHECK(f.GetBufferLineFromDiskLine(7) == 12);
|
||||
CHECK(f.GetBufferLineFromDiskLine(8) == 13);
|
||||
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(1) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(2) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(3) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(4) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(5) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(6) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(7) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(8) == false);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(9) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(10) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(11) == true);
|
||||
CHECK(f.IsBufferLineOnlyInBuffer(12) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("sanity") {
|
||||
/*
|
||||
(changes.size()=1) Inserted delta=1 at disk_line=4 with buffer_line=4
|
||||
disk_line=4 delta=1
|
||||
(changes.size()=1) Inserted delta=1 at disk_line=4 with buffer_line=5
|
||||
disk_line=4 delta=2
|
||||
(changes.size()=2) Inserted delta=1 at disk_line=3 with buffer_line=5
|
||||
disk_line=4 delta=2
|
||||
disk_line=3 delta=1
|
||||
*/
|
||||
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(4, 2));
|
||||
|
||||
CHECK(f.GetDiskLineFromBufferLine(5) == 4);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("deleted_line start") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(1, -3));
|
||||
|
||||
CHECK(f.IsDeletedDiskLine(1) == true);
|
||||
CHECK(f.IsDeletedDiskLine(2) == true);
|
||||
CHECK(f.IsDeletedDiskLine(3) == true);
|
||||
CHECK(f.IsDeletedDiskLine(4) == false);
|
||||
}
|
||||
|
||||
TEST_CASE("deleted_line past-start") {
|
||||
WorkingFile f("", "");
|
||||
|
||||
f.changes.push_back(WorkingFile::Change(2, -3));
|
||||
|
||||
CHECK(f.IsDeletedDiskLine(1) == false);
|
||||
CHECK(f.IsDeletedDiskLine(2) == true);
|
||||
CHECK(f.IsDeletedDiskLine(3) == true);
|
||||
CHECK(f.IsDeletedDiskLine(4) == true);
|
||||
CHECK(f.IsDeletedDiskLine(5) == false);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -1,18 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "language_server_api.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
struct WorkingFile {
|
||||
struct Change {
|
||||
//
|
||||
// A change at disk_line=2, delta=3 means that we are prepending 3 lines
|
||||
// before disk_line=2. This means disk_line=2 is shifted to buffer_line=5.
|
||||
//
|
||||
// buffer_line=1 disk_line=1 type=disk
|
||||
// buffer_line=2 disk_line=2 type=virtual
|
||||
// buffer_line=3 disk_line=2 type=virtual
|
||||
// buffer_line=4 disk_line=2 type=virtual
|
||||
// buffer_line=5 disk_line=2 type=disk
|
||||
// buffer_line=6 disk_line=3 type=disk
|
||||
//
|
||||
//
|
||||
// A change at disk_line=2, delta=-3 means that we are removing 3 lines
|
||||
// starting at disk_line=2. This means disk_line=2,3,4 are removed.
|
||||
//
|
||||
// buffer_line=1 disk_line=1 type=disk
|
||||
// buffer_line=2 disk_line=5 type=disk
|
||||
// buffer_line=3 disk_line=6 type=disk
|
||||
//
|
||||
// There is no meaningful way to map disk_line=2,3,4 to buffer_line; at the
|
||||
// moment they are shifted to buffer_line=2. Ideally calling code checks
|
||||
// IsDeletedLine and does not emit the reference if that's the case.
|
||||
//
|
||||
|
||||
int disk_line;
|
||||
int delta;
|
||||
|
||||
Change(int disk_line, int delta);
|
||||
};
|
||||
|
||||
std::string filename;
|
||||
std::string content;
|
||||
std::vector<Change> changes;
|
||||
|
||||
WorkingFile(const std::string& filename, const std::string& content);
|
||||
|
||||
CXUnsavedFile AsUnsavedFile() const;
|
||||
|
||||
// TODO: Add IsDeletedLine(int disk_line) and avoid showing code lens info for that line.
|
||||
|
||||
// Returns the disk line prior to buffer_line.
|
||||
//
|
||||
// - If there is a change at the start of the document and a buffer_line in that change
|
||||
// is requested, 1 is returned.
|
||||
// - If buffer_line is contained within a change (ie, IsBufferLineOnlyInBuffer is true),
|
||||
// then the line immediately preceding that change is returned.
|
||||
//
|
||||
// Otherwise, the actual disk line is returned.
|
||||
int GetDiskLineFromBufferLine(int buffer_line) const;
|
||||
|
||||
int GetBufferLineFromDiskLine(int disk_line) const;
|
||||
|
||||
bool IsDeletedDiskLine(int disk_line) const;
|
||||
|
||||
bool IsBufferLineOnlyInBuffer(int buffer_line) const;
|
||||
};
|
||||
|
||||
struct WorkingFiles {
|
||||
|
Loading…
Reference in New Issue
Block a user