From 04a855ebc7e4191bcd4e1cac34104929405452ba Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sun, 16 Apr 2017 01:09:12 -0700 Subject: [PATCH] Much better algorithm to sync code lens index locations to the right buffer location --- foo/b.cc | 28 +- src/command_line.cc | 22 +- src/utils.cc | 41 +++ src/utils.h | 3 + src/working_files.cc | 610 ++++++------------------------------------- src/working_files.h | 74 ++---- 6 files changed, 177 insertions(+), 601 deletions(-) diff --git a/foo/b.cc b/foo/b.cc index 01cc4403..cd5140fa 100644 --- a/foo/b.cc +++ b/foo/b.cc @@ -1,12 +1,24 @@ -namespace { +//1 +//2 -struct Foo2 { -virtual void foobar() = 0; -}; -void x() { - int bar = 3; - bar++; -} + +//3 +//4 +void b(); //7 + + + +void b(); //8 +//8 +//9 +//10 +//11 +//12 + + +//13 +void foo() { + b(); } \ No newline at end of file diff --git a/src/command_line.cc b/src/command_line.cc index 5932a0af..2e32d154 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -290,13 +290,14 @@ optional GetLsRange(WorkingFile* working_file, const Range& location) { lsPosition(location.end.line - 1, location.end.column - 1)); } - // TODO: Should we also consider location.end.line? - if (working_file->IsDeletedDiskLine(location.start.line)) + optional start = working_file->GetBufferLineFromDiskLine(location.start.line); + optional end = working_file->GetBufferLineFromDiskLine(location.end.line); + if (!start || !end) return nullopt; return lsRange( - lsPosition(working_file->GetBufferLineFromDiskLine(location.start.line) - 1, location.start.column - 1), - lsPosition(working_file->GetBufferLineFromDiskLine(location.end.line) - 1, location.end.column - 1)); + lsPosition(*start - 1, location.start.column - 1), + lsPosition(*end - 1, location.end.column - 1)); } lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id, std::string* path) { @@ -337,7 +338,7 @@ lsSymbolInformation GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files } case SymbolKind::Func: { QueryFunc* func = symbol.ResolveFunc(db); - + info.name = func->def.detailed_name; if (func->def.declaring_type.has_value()) { info.kind = lsSymbolKind::Method; @@ -422,13 +423,13 @@ lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, WorkingFiles* working_file optional ls_location = GetLsLocation(db, working_files, location); if (!ls_location) continue; - + if (path_to_edit.find(location.path) == path_to_edit.end()) { path_to_edit[location.path] = lsTextDocumentEdit(); const std::string& path = db->files[location.path.id].def.path; path_to_edit[location.path].textDocument.uri = lsDocumentUri::FromPath(path); - + WorkingFile* working_file = working_files->GetFileByFilename(path); if (working_file) path_to_edit[location.path].textDocument.version = working_file->version; @@ -871,8 +872,11 @@ void QueryDbMainLoop( auto msg = static_cast(message.get()); WorkingFile* working_file = working_files->GetFileByFilename(msg->params.textDocument.uri.GetPath()); - if (working_file) - working_file->changes.clear(); + if (working_file) { + // TODO: Update working file indexed content when we actually get the + // index update. + working_file->SetIndexContent(working_file->buffer_content); + } // Send an index update request. Index_DoIndex request(Index_DoIndex::Type::Update); diff --git a/src/utils.cc b/src/utils.cc index 738fcd2a..e617d2c1 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,12 +1,38 @@ #include "utils.h" +#include #include +#include +#include #include #include +#include +#include #include #include +namespace { + +// See http://stackoverflow.com/a/217605 +// Trim from start (in place) +void TrimStart(std::string& s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + std::not1(std::ptr_fun(std::isspace)))); +} +// Trim from end (in place) +void TrimEnd(std::string& s) { + s.erase(std::find_if(s.rbegin(), s.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), s.end()); +} +// Trim from both ends (in place) +void Trim(std::string& s) { + TrimStart(s); + TrimEnd(s); +} + +} // namespace + // See http://stackoverflow.com/a/2072890 bool EndsWith(const std::string& value, const std::string& ending) { if (ending.size() > value.size()) @@ -133,6 +159,21 @@ std::vector ReadLines(std::string filename) { return result; } +std::vector ToLines(const std::string& content, bool trim_whitespace) { + std::vector result; + + std::istringstream lines(content); + + std::string line; + while (getline(lines, line)) { + if (trim_whitespace) + Trim(line); + result.push_back(line); + } + + return result; +} + std::unordered_map ParseTestExpectation(std::string filename) { bool in_output = false; diff --git a/src/utils.h b/src/utils.h index e29490fb..1bc597b7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -16,6 +16,9 @@ std::string ReplaceAll(const std::string& source, const std::string& from, const // Finds all files in the given folder. This is recursive. std::vector GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path); std::vector ReadLines(std::string filename); +std::vector ToLines(const std::string& content, bool trim_whitespace); + + std::unordered_map ParseTestExpectation(std::string filename); void UpdateTestExpectation(const std::string& filename, const std::string& expectation, const std::string& actual); diff --git a/src/working_files.cc b/src/working_files.cc index 9b6d6bf5..72b30eb1 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -1,6 +1,6 @@ #include "working_files.h" -#include +#include "position.h" namespace { @@ -17,142 +17,83 @@ int GetOffsetForPosition(lsPosition position, const std::string& content) { 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 IsPreviousTokenNewline(const std::string& str, int start) { - return start == 0 || str[start - 1] == '\n'; -} - -int DetermineDiskLineForChange(WorkingFile* f, int start_offset, int buffer_line, int line_delta) { - // If we're adding a newline that means we will introduce a new virtual newline - // below. That's the case *except* if we are moving the current line down. - if (!IsPreviousTokenNewline(f->content, start_offset)) { - //std::cerr << " Applying newline workaround" << std::endl; - ++buffer_line; - } - - return f->GetDiskLineFromBufferLine(buffer_line); -} - } // namespace -WorkingFile::Change::Change(int disk_line, int delta) : disk_line(disk_line), delta(delta) {} +WorkingFile::WorkingFile(const std::string& filename, const std::string& buffer_content) + : filename(filename), buffer_content(buffer_content) { + OnBufferContentUpdated(); + // TODO: use cached index file contents + SetIndexContent(buffer_content); +} -WorkingFile::WorkingFile(const std::string& filename, const std::string& content) - : filename(filename), content(content) { +void WorkingFile::SetIndexContent(const std::string& index_content) { + index_lines = ToLines(index_content, true /*trim_whitespace*/); +} + +void WorkingFile::OnBufferContentUpdated() { + all_buffer_lines = ToLines(buffer_content, true /*trim_whitespace*/); + + // Build lookup buffer. + buffer_line_lookup.clear(); + buffer_line_lookup.reserve(all_buffer_lines.size()); + for (int i = 0; i < all_buffer_lines.size(); ++i) { + const std::string& buffer_line = all_buffer_lines[i]; + + auto it = buffer_line_lookup.find(buffer_line); + if (it == buffer_line_lookup.end()) + buffer_line_lookup[buffer_line] = { i + 1 }; + else + it->second.push_back(i + 1); + } +} + +optional WorkingFile::GetBufferLineFromDiskLine(int index_line) const { + // The implementation is simple but works pretty well for most cases. We + // lookup the line contents in the indexed file contents, and try to find the + // most similar line in the current buffer file. + // + // Previously, this was implemented by tracking edits and by running myers + // diff algorithm. They were complex implementations that did not work as + // well. + + // Note: |index_line| and |buffer_line| are 1-based. + + assert(index_line >= 1 && index_line <= index_lines.size()); + + // Find the line in the cached index file. We'll try to find the most similar line + // in the buffer and return the index for that. + std::string index = index_lines[index_line - 1]; + auto buffer_it = buffer_line_lookup.find(index); + if (buffer_it == buffer_line_lookup.end()) { + // TODO: Use levenshtein distance to find the best match (but only to an + // extent) + return nullopt; + } + + // From all the identical lines, return the one which is closest to + // |index_line|. There will usually only be one identical line. + assert(!buffer_it->second.empty()); + int closest_dist = INT_MAX; + int closest_buffer_line = INT_MIN; + for (int buffer_line : buffer_it->second) { + int dist = std::abs(buffer_line - index_line); + if (dist <= closest_dist) { + closest_dist = dist; + closest_buffer_line = buffer_line; + } + } + + return closest_buffer_line; } CXUnsavedFile WorkingFile::AsUnsavedFile() const { CXUnsavedFile result; result.Filename = filename.c_str(); - result.Contents = content.c_str(); - result.Length = content.size(); + result.Contents = buffer_content.c_str(); + result.Length = (unsigned long)buffer_content.size(); 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) @@ -168,7 +109,10 @@ void WorkingFiles::OnOpen(const Ipc_TextDocumentDidOpen::Params& open) { // The file may already be open. if (WorkingFile* file = GetFileByFilename(filename)) { file->version = open.textDocument.version; - file->content = content; + // TODO: Load saved indexed content and not the initial buffer content. + file->SetIndexContent(content); + file->buffer_content = content; + file->OnBufferContentUpdated(); return; } @@ -191,59 +135,15 @@ void WorkingFiles::OnChange(const Ipc_TextDocumentDidChange::Params& change) { // If range or rangeLength are emitted we replace everything, per the spec. if (diff.rangeLength == -1) { - file->content = diff.text; + file->buffer_content = diff.text; + file->OnBufferContentUpdated(); } else { - int start_offset = GetOffsetForPosition(diff.range.start, file->content); - int old_line_count = diff.range.end.line - diff.range.start.line; - int new_line_count = LineCount(diff.text.begin(), diff.text.end()); - - // TODO: this assert goes off. Figure out why. - //assert(old_line_count == LineCount(file->content.begin() + start_offset, file->content.begin() + start_offset + diff.rangeLength + 1)); - - int line_delta = new_line_count - old_line_count; - if (line_delta != 0) { - //std::cerr << " diff.range.start.line=" << diff.range.start.line+1 << ", diff.range.start.character=" << diff.range.start.character << std::endl; - //std::cerr << " diff.range.end.line=" << diff.range.end.line+1 << ", diff.range.end.character=" << diff.range.end.character << std::endl; - //std::cerr << " old_line_count=" << old_line_count << ", new_line_count=" << new_line_count << std::endl; - //std::cerr << " disk_line(diff.range.start.line)=" << file->GetDiskLineFromBufferLine(diff.range.start.line+1) << std::endl; - //std::cerr << " disk_line(diff.range.end.line)=" << file->GetDiskLineFromBufferLine(diff.range.end.line+1) << std::endl; - - // language server stores line counts starting at 0, we start at 1 - int buffer_line = diff.range.start.line + 1; - int disk_line = DetermineDiskLineForChange(file, start_offset, buffer_line, line_delta); - - bool found = false; - for (int i = 0; i < file->changes.size(); ++i) { - auto& change = file->changes[i]; - if (disk_line == change.disk_line || - (line_delta >= 1 && disk_line - 1 == change.disk_line && change.delta < 0) /* handles joining two lines and then resplitting them */ ) { - 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::sort(file->changes.begin(), file->changes.end(), - [](const WorkingFile::Change& a, const WorkingFile::Change& b) { - return a.disk_line < b.disk_line; - }); - - //std::cerr << " APPLIED" << std::endl; - //std::cerr << " Inserted delta=" << line_delta << " at disk_line=" << disk_line << " with buffer_line=" << buffer_line << std::endl; - //std::cerr << " changes.size()=" << file->changes.size() << std::endl; - //for (auto& change : file->changes) - // std::cerr << " disk_line=" << change.disk_line << " delta=" << change.delta << std::endl; - } - - - file->content.replace(file->content.begin() + start_offset, - file->content.begin() + start_offset + diff.rangeLength, + int start_offset = GetOffsetForPosition(diff.range.start, file->buffer_content); + file->buffer_content.replace(file->buffer_content.begin() + start_offset, + file->buffer_content.begin() + start_offset + diff.rangeLength, diff.text); + file->OnBufferContentUpdated(); } } //std::cerr << std::endl << std::endl << "--------" << file->content << "--------" << std::endl << std::endl; @@ -268,358 +168,4 @@ std::vector WorkingFiles::AsUnsavedFiles() const { for (auto& file : files) result.push_back(file->AsUnsavedFile()); 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_CASE("wip") { - /* - VERSION 4 - diff.range.start.line=2, diff.range.start.character=0 - diff.range.end.line= 3, diff.range.end.character= 0 - diff.text="" - Applying newline workaround - APPLIED - Inserted delta=-1 at disk_line=4 with buffer_line=4 - changes.size()=1 - disk_line=4 delta=-1 - - - VERSION 7 - diff.range.start.line=2, diff.range.start.character=0 - diff.range.end.line= 2, diff.range.end.character= 0 - diff.text="daaa\n" - Applying newline workaround - APPLIED - Inserted delta=1 at disk_line=5 with buffer_line=4 - changes.size()=2 - disk_line=4 delta=-1 - disk_line=5 delta=1 - */ - - WorkingFile f("", ""); - - /* - delete line 4 - buffer line 4 -> disk line 5 - */ - - f.changes.push_back(WorkingFile::Change(4, -1)); - - CHECK(f.GetDiskLineFromBufferLine(4) == 5); -} - -TEST_CASE("DetermineDiskLineForChange") { - // aaa - // bbb - WorkingFile f("", "aaa\nbbb"); - - // Adding a line so we have - // aaa - // - // bbb - CHECK(DetermineDiskLineForChange(&f, 3 /*start_offset*/, 1 /*buffer_line*/, 1 /*line_delta*/) == 2); - - // Adding a line so we have - // - // aaa - // bbb - CHECK(DetermineDiskLineForChange(&f, 0 /*start_offset*/, 1 /*buffer_line*/, 1 /*line_delta*/) == 1); - - // Adding a line so we have - // a - // aa - // bbb - CHECK(DetermineDiskLineForChange(&f, 1 /*start_offset*/, 1 /*buffer_line*/, 1 /*line_delta*/) == 2); - -} - -TEST_SUITE_END(); \ No newline at end of file +} \ No newline at end of file diff --git a/src/working_files.h b/src/working_files.h index 595e8ce5..773ac6ca 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -4,67 +4,37 @@ #include "utils.h" #include +#include #include +using std::experimental::optional; +using std::experimental::nullopt; + 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); - }; - int version = 0; std::string filename; - std::string content; - std::vector changes; - WorkingFile(const std::string& filename, const std::string& content); + std::string buffer_content; + // Note: This assumes 0-based lines (1-based lines are normally assumed). + std::vector index_lines; + // Note: This assumes 0-based lines (1-based lines are normally assumed). + std::vector all_buffer_lines; + // Note: The items in the value entry are 1-based lines.s + std::unordered_map> buffer_line_lookup; + + WorkingFile(const std::string& filename, const std::string& buffer_content); + + // This should be called when the indexed content has changed. + void SetIndexContent(const std::string& index_content); + // This should be called whenever |buffer_content| has changed. + void OnBufferContentUpdated(); + + // Find the buffer-line which should be shown for |indexed_line|. This + // accepts and returns 1-based lines. + optional GetBufferLineFromDiskLine(int indexed_line) const; 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 {