From 49def73e4e6717e325275f936681c55e93cba200 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 12 May 2015 23:41:35 +0100 Subject: [PATCH 01/20] Add initial diff implementation based Eugene W. Myers' algorithm --- src/diff.hh | 175 ++++++++++++++++++++++++++++++++++++++++++++++ src/unit_tests.cc | 10 +++ 2 files changed, 185 insertions(+) create mode 100644 src/diff.hh diff --git a/src/diff.hh b/src/diff.hh new file mode 100644 index 00000000..aa482acf --- /dev/null +++ b/src/diff.hh @@ -0,0 +1,175 @@ +#include "array_view.hh" +#include "vector.hh" + +namespace Kakoune +{ + +template +struct MirroredArray : public ArrayView +{ + MirroredArray(ArrayView data, int size) + : ArrayView(data), size(size) + { + kak_assert(2 * size + 1 <= data.size()); + } + + T& operator[](int n) { return ArrayView::operator[](n + size); } + const T& operator[](int n) const { return ArrayView::operator[](n + size); } +private: + int size; +}; + +struct Snake{ int x, y, u, v; bool add; }; + +template +Snake find_end_snake_of_further_reaching_dpath(Iterator a, int N, Iterator b, int M, + const MirroredArray& V, + const int D, const int k) +{ + int x; // our position along a + + const bool add = k == -D or (k != D and V[k-1] < V[k+1]); + + // if diagonal on the right goes further along x than diagonal on the left, + // then we take a vertical edge from it to this diagonal, hence x = V[k+1] + if (add) + x = V[k+1]; + // else, we take an horizontal edge from our left diagonal,x = V[k-1]+1 + else + x = V[k-1]+1; + + int y = x - k; // we are by construction on diagonal k, so our position along + // b (y) is x - k. + + int u = x, v = y; + // follow end snake along diagonal k + while (u < N and v < M and a[u] == b[v]) + ++u, ++v; + + return { x, y, u, v, add }; +} + +struct SnakeLen : Snake +{ + SnakeLen(Snake s, int d) : Snake(s), d(d) {} + int d; +}; + +template +SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M, + ArrayView data1, ArrayView data2) +{ + const int delta = N - M; + MirroredArray V1{data1, N + M}; + MirroredArray V2{data2, N + M}; + + std::reverse_iterator ra{a + N}, rb{b + M}; + + for (int D = 0; D <= (M + N + 1) / 2; ++D) + { + for (int k1 = -D; k1 <= D; k1 += 2) + { + auto p = find_end_snake_of_further_reaching_dpath(a, N, b, M, V1, D, k1); + V1[k1] = p.u; + + const int k2 = -(k1 - delta); + if ((delta % 2 != 0) and -(D-1) <= k2 and k2 <= (D-1)) + { + if (V1[k1] + V2[k2] >= N) + return { p, 2 * D - 1 };// return last snake on forward path + } + } + + for (int k2 = -D; k2 <= D; k2 += 2) + { + auto p = find_end_snake_of_further_reaching_dpath(ra, N, rb, M, V2, D, k2); + V2[k2] = p.u; + + const int k1 = -(k2 - delta); + if ((delta % 2 == 0) and -D <= k1 and k1 <= D) + { + if (V1[k1] + V2[k2] >= N) + return { { N - p.u, M - p.v, N - p.x , M - p.y } , 2 * D };// return last snake on reverse path + } + } + } + + kak_assert(false); +} + +template +struct Diff +{ + bool add; + Iterator begin; + Iterator end; +}; + +template +void find_diff_rec(Iterator a, size_t N, Iterator b, size_t M, + ArrayView data1, ArrayView data2, + Vector>& diffs) +{ + if (N > 0 and M > 0) + { + auto middle_snake = find_middle_snake(a, N, b, M, data1, data2); + if (middle_snake.d > 1) + { + find_diff_rec(a, middle_snake.x, b, middle_snake.y, + data1, data2, diffs); + + find_diff_rec(a + middle_snake.u, N - middle_snake.u, + b + middle_snake.v, M - middle_snake.v, + data1, data2, diffs); + } + else if (middle_snake.d == 1) + { + int diag = 0; + while (a[diag] == b[diag]) + ++diag; + + if (middle_snake.add) + diffs.push_back({true, b + middle_snake.y, b + middle_snake.y + 1}); + else + diffs.push_back({false, a + middle_snake.x-1, a + middle_snake.x}); + } + } + else if (M > 0) + diffs.push_back({true, b, b + M}); + else if (N > 0) + diffs.push_back({false, a, a + N}); +} + +template +void compact_diffs(Vector>& diffs) +{ + if (diffs.size() < 2) + return; + + auto out_it = diffs.begin(); + for (auto it = out_it + 1; it != diffs.end(); ++it) + { + if (it->add == out_it->add and it->begin == out_it->end) + out_it->end = it->end; + else if (++out_it != it) + *out_it = *it; + } +} + +template +Vector> find_diff(Iterator a, size_t N, Iterator b, size_t M) +{ + Vector data(4 * (N+M)); + Vector> diffs; + const size_t max_D_size = 2 * (N + M) + 1; + find_diff_rec(a, N, b, M, + {data.data(), max_D_size}, + {data.data() + max_D_size, max_D_size}, + diffs); + + // compact_diffs(diffs); + + return diffs; +} + +} diff --git a/src/unit_tests.cc b/src/unit_tests.cc index 58f34a98..e054ee41 100644 --- a/src/unit_tests.cc +++ b/src/unit_tests.cc @@ -1,5 +1,6 @@ #include "assert.hh" #include "buffer.hh" +#include "diff.hh" #include "keys.hh" #include "selectors.hh" #include "word_db.hh" @@ -239,6 +240,14 @@ void test_line_modifications() } } +void test_diff() +{ + StringView s1 = "mais que fais la police"; + StringView s2 = "mais ou va la police"; + + auto diff = find_diff(s1.begin(), (int)s1.length(), s2.begin(), (int)s2.length()); +} + void run_unit_tests() { test_utf8(); @@ -248,4 +257,5 @@ void run_unit_tests() test_undo_group_optimizer(); test_word_db(); test_line_modifications(); + test_diff(); } From 51f6c52013740d3a160712d2c8eeb3f48b99b903 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 13 May 2015 13:28:23 +0100 Subject: [PATCH 02/20] Fix StringDataPtr gdb pretty printer --- gdb/kakoune.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/gdb/kakoune.py b/gdb/kakoune.py index 97c0e307..f47be8fe 100644 --- a/gdb/kakoune.py +++ b/gdb/kakoune.py @@ -73,8 +73,8 @@ class StringView: def to_string(self): return "\"%s\"" % (self.val['m_data'].string("utf-8", "ignore", self.val['m_length']['m_value'])) -class StringStoragePtr: - """ Print a ref_ptr""" +class StringDataPtr: + """ Print a RefPtr""" def __init__(self, val): self.val = val @@ -85,6 +85,16 @@ class StringStoragePtr: content = (ptr + 1).cast(str_type).string("utf-8", "ignore", ptr.dereference()['length']) return "\"%s\" (ref:%d)" % (content.replace("\n", "\\n"), ptr.dereference()['refcount']) +class RefPtr: + """ Print a RefPtr""" + + def __init__(self, val): + self.val = val + + def to_string(self): + ptr = self.val['m_ptr'] + return "\"refptr %s\"" % (ptr) + class Option: """ Print a Option""" @@ -131,7 +141,8 @@ def build_pretty_printer(): pp.add_printer('String', '^Kakoune::String$', String) pp.add_printer('StringView', '^Kakoune::StringView$', StringView) pp.add_printer('SharedString', '^Kakoune::SharedString$', StringView) - pp.add_printer('StringStoragePtr', '^Kakoune::ref_ptr$', StringStoragePtr) + pp.add_printer('StringDataPtr', '^Kakoune::RefPtr$', StringDataPtr) + pp.add_printer('RefPtr', '^Kakoune::RefPtr<.*>$', RefPtr) pp.add_printer('Option', '^Kakoune::Option$', Option) pp.add_printer('LineCount', '^Kakoune::LineCount$', LineCount) pp.add_printer('CharCount', '^Kakoune::CharCount$', CharCount) From 7a8c2d7f564f7c38faa40fd88ce3d92b848cfaa0 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 13 May 2015 20:51:10 +0100 Subject: [PATCH 03/20] Fix diff implementation and change the Diff struct format --- src/diff.hh | 98 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/src/diff.hh b/src/diff.hh index aa482acf..bc971708 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -1,6 +1,9 @@ #include "array_view.hh" #include "vector.hh" +#include +#include + namespace Kakoune { @@ -21,10 +24,10 @@ private: struct Snake{ int x, y, u, v; bool add; }; -template +template Snake find_end_snake_of_further_reaching_dpath(Iterator a, int N, Iterator b, int M, const MirroredArray& V, - const int D, const int k) + const int D, const int k, Equal eq) { int x; // our position along a @@ -43,7 +46,7 @@ Snake find_end_snake_of_further_reaching_dpath(Iterator a, int N, Iterator b, in int u = x, v = y; // follow end snake along diagonal k - while (u < N and v < M and a[u] == b[v]) + while (u < N and v < M and eq(a[u], b[v])) ++u, ++v; return { x, y, u, v, add }; @@ -55,9 +58,10 @@ struct SnakeLen : Snake int d; }; -template +template SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M, - ArrayView data1, ArrayView data2) + ArrayView data1, ArrayView data2, + Equal eq) { const int delta = N - M; MirroredArray V1{data1, N + M}; @@ -69,7 +73,7 @@ SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M, { for (int k1 = -D; k1 <= D; k1 += 2) { - auto p = find_end_snake_of_further_reaching_dpath(a, N, b, M, V1, D, k1); + auto p = find_end_snake_of_further_reaching_dpath(a, N, b, M, V1, D, k1, eq); V1[k1] = p.u; const int k2 = -(k1 - delta); @@ -82,7 +86,7 @@ SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M, for (int k2 = -D; k2 <= D; k2 += 2) { - auto p = find_end_snake_of_further_reaching_dpath(ra, N, rb, M, V2, D, k2); + auto p = find_end_snake_of_further_reaching_dpath(ra, N, rb, M, V2, D, k2, eq); V2[k2] = p.u; const int k1 = -(k2 - delta); @@ -95,53 +99,60 @@ SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M, } kak_assert(false); + return { {}, 0 }; } -template struct Diff { - bool add; - Iterator begin; - Iterator end; + enum { Keep, Add, Remove } mode; + int len; + int posB; }; -template -void find_diff_rec(Iterator a, size_t N, Iterator b, size_t M, +template +void find_diff_rec(Iterator a, int offA, int lenA, + Iterator b, int offB, int lenB, ArrayView data1, ArrayView data2, - Vector>& diffs) + Equal eq, Vector& diffs) { - if (N > 0 and M > 0) + if (lenA > 0 and lenB > 0) { - auto middle_snake = find_middle_snake(a, N, b, M, data1, data2); + auto middle_snake = find_middle_snake(a + offA, lenA, b + offB, lenB, data1, data2, eq); if (middle_snake.d > 1) { - find_diff_rec(a, middle_snake.x, b, middle_snake.y, - data1, data2, diffs); + find_diff_rec(a, offA, middle_snake.x, + b, offB, middle_snake.y, + data1, data2, eq, diffs); - find_diff_rec(a + middle_snake.u, N - middle_snake.u, - b + middle_snake.v, M - middle_snake.v, - data1, data2, diffs); + if (int len = middle_snake.u - middle_snake.x) + diffs.push_back({Diff::Keep, len, 0}); + + find_diff_rec(a, offA + middle_snake.u, lenA - middle_snake.u, + b, offB + middle_snake.v, lenB - middle_snake.v, + data1, data2, eq, diffs); } else if (middle_snake.d == 1) { int diag = 0; - while (a[diag] == b[diag]) + while (eq(a[offA + diag], b[offB + diag])) ++diag; + if (diag != 0) + diffs.push_back({Diff::Keep, diag, 0}); + if (middle_snake.add) - diffs.push_back({true, b + middle_snake.y, b + middle_snake.y + 1}); + diffs.push_back({Diff::Add, 1, offB + middle_snake.y-1}); else - diffs.push_back({false, a + middle_snake.x-1, a + middle_snake.x}); + diffs.push_back({Diff::Remove, 1, 0}); } } - else if (M > 0) - diffs.push_back({true, b, b + M}); - else if (N > 0) - diffs.push_back({false, a, a + N}); + else if (lenB > 0) + diffs.push_back({Diff::Add, lenB, offB}); + else if (lenA > 0) + diffs.push_back({Diff::Remove, lenA, 0}); } -template -void compact_diffs(Vector>& diffs) +inline void compact_diffs(Vector& diffs) { if (diffs.size() < 2) return; @@ -149,25 +160,28 @@ void compact_diffs(Vector>& diffs) auto out_it = diffs.begin(); for (auto it = out_it + 1; it != diffs.end(); ++it) { - if (it->add == out_it->add and it->begin == out_it->end) - out_it->end = it->end; + if (it->mode == out_it->mode and + (it->mode != Diff::Add or + it->posB == out_it->posB + out_it->len)) + out_it->len += it->len; else if (++out_it != it) *out_it = *it; } + diffs.erase(out_it+1, diffs.end()); } -template -Vector> find_diff(Iterator a, size_t N, Iterator b, size_t M) +template::value_type>> +Vector find_diff(Iterator a, int N, Iterator b, int M, + Equal eq = Equal{}) { - Vector data(4 * (N+M)); - Vector> diffs; - const size_t max_D_size = 2 * (N + M) + 1; - find_diff_rec(a, N, b, M, - {data.data(), max_D_size}, - {data.data() + max_D_size, max_D_size}, - diffs); + const int max = 2 * (N + M) + 1; + Vector data(2*max); + Vector diffs; + find_diff_rec(a, 0, N, b, 0, M, + {data.data(), (size_t)max}, {data.data() + max, (size_t)max}, + eq, diffs); - // compact_diffs(diffs); + compact_diffs(diffs); return diffs; } From 15b26fd06ca38de2b7752ed3a6d68f149cdd4f21 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 13 May 2015 20:54:44 +0100 Subject: [PATCH 04/20] Use diff when reloading buffer --- src/buffer.cc | 64 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index c0167193..cb0facee 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -9,6 +9,7 @@ #include "shared_string.hh" #include "utils.hh" #include "window.hh" +#include "diff.hh" #include @@ -159,36 +160,59 @@ struct Buffer::Modification void Buffer::reload(BufferLines lines, time_t fs_timestamp) { - m_changes.push_back({ Change::Erase, true, {0,0}, line_count() }); - - commit_undo_group(); - if (not (m_flags & Flags::NoUndo)) - { - for (auto line = line_count()-1; line >= 0; --line) - m_current_undo_group.emplace_back( - Modification::Erase, line, - SharedString{m_lines.get_storage(line)}); - } - if (lines.empty()) lines.emplace_back(StringData::create("\n")); - for (size_t l = 0; l < lines.size(); ++l) + const bool record_undo = not (m_flags & Flags::NoUndo); + + commit_undo_group(); + + auto diff = find_diff(m_lines.begin(), m_lines.size(), + lines.begin(), (int)lines.size(), + [](const StringDataPtr& lhs, const StringDataPtr& rhs) + { return lhs->strview() == rhs->strview(); }); + + auto it = m_lines.begin(); + for (auto& d : diff) { - auto& line = lines[l]; - kak_assert(not (line->length == 0) and line->data()[line->length-1] == '\n'); - if (not (m_flags & Flags::NoUndo)) - m_current_undo_group.emplace_back( - Modification::Insert, LineCount{(int)l}, SharedString{line}); + switch (d.mode) + { + case Diff::Keep: it += d.len; break; + case Diff::Add: + { + const LineCount cur_line = (int)(it - m_lines.begin()); + if (record_undo) + { + for (LineCount line = 0; line < d.len; ++line) + m_current_undo_group.emplace_back( + Modification::Insert, cur_line + line, + SharedString{lines[(int)(d.posB + line)]}); + } + m_changes.push_back({ Change::Insert, it == m_lines.end(), cur_line, cur_line + d.len }); + it = m_lines.insert(it, &lines[d.posB], &lines[d.posB + d.len]) + d.len; + break; + } + case Diff::Remove: + { + const LineCount cur_line = (int)(it - m_lines.begin()); + if (record_undo) + { + for (LineCount line = d.len-1; line >= 0; --line) + m_current_undo_group.emplace_back( + Modification::Erase, cur_line + line, + SharedString{m_lines.get_storage(cur_line + line)}); + } + it = m_lines.erase(it, it + d.len); + m_changes.push_back({ Change::Erase, it == m_lines.end(), cur_line, cur_line + d.len }); + break; + } + } } - static_cast(m_lines) = std::move(lines); commit_undo_group(); m_last_save_undo_index = m_history_cursor - m_history.begin(); m_fs_timestamp = fs_timestamp; - - m_changes.push_back({ Change::Insert, true, {0,0}, line_count() }); } void Buffer::commit_undo_group() From 11528e45e95972e5d5b0fcdeb8f3731fd97dc79d Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 13 May 2015 21:30:23 +0100 Subject: [PATCH 05/20] Use friend functions rather than methods for StronglyTypedNumber binary ops --- src/units.hh | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/units.hh b/src/units.hh index 2ef536d2..21c583b2 100644 --- a/src/units.hh +++ b/src/units.hh @@ -21,20 +21,20 @@ public: } [[gnu::always_inline]] - constexpr RealType operator+(RealType other) const - { return RealType(m_value + other.m_value); } + constexpr friend RealType operator+(RealType lhs, RealType rhs) + { return RealType(lhs.m_value + rhs.m_value); } [[gnu::always_inline]] - constexpr RealType operator-(RealType other) const - { return RealType(m_value - other.m_value); } + constexpr friend RealType operator-(RealType lhs, RealType rhs) + { return RealType(lhs.m_value - rhs.m_value); } [[gnu::always_inline]] - constexpr RealType operator*(RealType other) const - { return RealType(m_value * other.m_value); } + constexpr friend RealType operator*(RealType lhs, RealType rhs) + { return RealType(lhs.m_value * rhs.m_value); } [[gnu::always_inline]] - constexpr RealType operator/(RealType other) const - { return RealType(m_value / other.m_value); } + constexpr friend RealType operator/(RealType lhs, RealType rhs) + { return RealType(lhs.m_value / rhs.m_value); } [[gnu::always_inline]] RealType& operator+=(RealType other) @@ -72,36 +72,36 @@ public: constexpr RealType operator-() const { return RealType(-m_value); } [[gnu::always_inline]] - constexpr RealType operator%(RealType other) const - { return RealType(m_value % other.m_value); } + constexpr friend RealType operator%(RealType lhs, RealType rhs) + { return RealType(lhs.m_value % rhs.m_value); } [[gnu::always_inline]] RealType& operator%=(RealType other) { m_value %= other.m_value; return static_cast(*this); } [[gnu::always_inline]] - constexpr bool operator==(RealType other) const - { return m_value == other.m_value; } + constexpr friend bool operator==(RealType lhs, RealType rhs) + { return lhs.m_value == rhs.m_value; } [[gnu::always_inline]] - constexpr bool operator!=(RealType other) const - { return m_value != other.m_value; } + constexpr friend bool operator!=(RealType lhs, RealType rhs) + { return lhs.m_value != rhs.m_value; } [[gnu::always_inline]] - constexpr bool operator<(RealType other) const - { return m_value < other.m_value; } + constexpr friend bool operator<(RealType lhs, RealType rhs) + { return lhs.m_value < rhs.m_value; } [[gnu::always_inline]] - constexpr bool operator<=(RealType other) const - { return m_value <= other.m_value; } + constexpr friend bool operator<=(RealType lhs, RealType rhs) + { return lhs.m_value <= rhs.m_value; } [[gnu::always_inline]] - constexpr bool operator>(RealType other) const - { return m_value > other.m_value; } + constexpr friend bool operator>(RealType lhs, RealType rhs) + { return lhs.m_value > rhs.m_value; } [[gnu::always_inline]] - constexpr bool operator>=(RealType other) const - { return m_value >= other.m_value; } + constexpr friend bool operator>=(RealType lhs, RealType rhs) + { return lhs.m_value >= rhs.m_value; } [[gnu::always_inline]] constexpr bool operator!() const From f224d11ccd0a6f72497665aca92eb57b3d6d860f Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 13 May 2015 21:31:39 +0100 Subject: [PATCH 06/20] Small cleanup in selections.cc --- src/selection.cc | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/selection.cc b/src/selection.cc index f1fbf9b1..7d0afad5 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -406,10 +406,8 @@ void SelectionList::sort_and_merge_overlapping() std::stable_sort(begin(), end(), compare_selections); m_selections.erase(merge_overlapping(begin(), end(), m_main, overlaps), end()); } -namespace -{ -inline void _avoid_eol(const Buffer& buffer, ByteCoord& coord) +static inline void _avoid_eol(const Buffer& buffer, ByteCoord& coord) { auto column = coord.column; auto line = buffer[coord.line]; @@ -417,20 +415,14 @@ inline void _avoid_eol(const Buffer& buffer, ByteCoord& coord) coord.column = line.byte_count_to(line.char_length() - 2); } - -inline void _avoid_eol(const Buffer& buffer, Selection& sel) -{ - _avoid_eol(buffer, sel.anchor()); - _avoid_eol(buffer, sel.cursor()); -} - -} - void SelectionList::avoid_eol() { update(); for (auto& sel : m_selections) - _avoid_eol(buffer(), sel); + { + _avoid_eol(buffer(), sel.anchor()); + _avoid_eol(buffer(), sel.cursor()); + } } BufferIterator prepare_insert(Buffer& buffer, const Selection& sel, InsertMode mode) From cc97d4ba41169a3a365922b05a30fdab88dbe688 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 14 May 2015 13:57:03 +0100 Subject: [PATCH 07/20] Fix bugs in diff implementation --- src/diff.hh | 5 +++++ src/unit_tests.cc | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/diff.hh b/src/diff.hh index bc971708..e68ec10c 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -14,6 +14,8 @@ struct MirroredArray : public ArrayView : ArrayView(data), size(size) { kak_assert(2 * size + 1 <= data.size()); + for (int i = -size; i <= size; ++i) + (*this)[i] = 0; } T& operator[](int n) { return ArrayView::operator[](n + size); } @@ -118,6 +120,7 @@ void find_diff_rec(Iterator a, int offA, int lenA, if (lenA > 0 and lenB > 0) { auto middle_snake = find_middle_snake(a + offA, lenA, b + offB, lenB, data1, data2, eq); + kak_assert(middle_snake.u <= lenA and middle_snake.v <= lenB); if (middle_snake.d > 1) { find_diff_rec(a, offA, middle_snake.x, @@ -145,6 +148,8 @@ void find_diff_rec(Iterator a, int offA, int lenA, else diffs.push_back({Diff::Remove, 1, 0}); } + else if (int len = middle_snake.u - middle_snake.x) + diffs.push_back({Diff::Keep, len, 0}); } else if (lenB > 0) diffs.push_back({Diff::Add, lenB, offB}); diff --git a/src/unit_tests.cc b/src/unit_tests.cc index e054ee41..29fd90f6 100644 --- a/src/unit_tests.cc +++ b/src/unit_tests.cc @@ -242,10 +242,29 @@ void test_line_modifications() void test_diff() { - StringView s1 = "mais que fais la police"; - StringView s2 = "mais ou va la police"; + auto eq = [](const Diff& lhs, const Diff& rhs) { + return lhs.mode == rhs.mode and lhs.len == rhs.len and lhs.posB == rhs.posB; + }; - auto diff = find_diff(s1.begin(), (int)s1.length(), s2.begin(), (int)s2.length()); + { + StringView s1 = "mais que fais la police"; + StringView s2 = "mais ou va la police"; + + auto diff = find_diff(s1.begin(), (int)s1.length(), s2.begin(), (int)s2.length()); + kak_assert(diff.size() == 10); + } + + { + StringView s1 = "a?"; + StringView s2 = "!"; + + auto diff = find_diff(s1.begin(), (int)s1.length(), s2.begin(), (int)s2.length()); + + kak_assert(diff.size() == 3 and + eq(diff[0], {Diff::Remove, 1, 0}) and + eq(diff[1], {Diff::Add, 1, 0}) and + eq(diff[2], {Diff::Remove, 1, 0})); + } } void run_unit_tests() From 8ba68044809d6fc0bc34eae44f7ff0a855463d1a Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 14 May 2015 14:05:02 +0100 Subject: [PATCH 08/20] Do not use diff when reloading a no undo buffer --- src/buffer.cc | 59 ++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index cb0facee..be3ca7a2 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -167,44 +167,49 @@ void Buffer::reload(BufferLines lines, time_t fs_timestamp) commit_undo_group(); - auto diff = find_diff(m_lines.begin(), m_lines.size(), - lines.begin(), (int)lines.size(), - [](const StringDataPtr& lhs, const StringDataPtr& rhs) - { return lhs->strview() == rhs->strview(); }); - - auto it = m_lines.begin(); - for (auto& d : diff) + if (not record_undo) { - switch (d.mode) + m_changes.push_back({ Change::Erase, true, {0,0}, line_count() }); + + static_cast(m_lines) = std::move(lines); + + m_changes.push_back({ Change::Insert, true, {0,0}, line_count() }); + } + else + { + auto diff = find_diff(m_lines.begin(), m_lines.size(), + lines.begin(), (int)lines.size(), + [](const StringDataPtr& lhs, const StringDataPtr& rhs) + { return lhs->strview() == rhs->strview(); }); + + auto it = m_lines.begin(); + for (auto& d : diff) { - case Diff::Keep: it += d.len; break; - case Diff::Add: + if (d.mode == Diff::Keep) + it += d.len; + else if (d.mode == Diff::Add) { const LineCount cur_line = (int)(it - m_lines.begin()); - if (record_undo) - { - for (LineCount line = 0; line < d.len; ++line) - m_current_undo_group.emplace_back( - Modification::Insert, cur_line + line, - SharedString{lines[(int)(d.posB + line)]}); - } + + for (LineCount line = 0; line < d.len; ++line) + m_current_undo_group.emplace_back( + Modification::Insert, cur_line + line, + SharedString{lines[(int)(d.posB + line)]}); + m_changes.push_back({ Change::Insert, it == m_lines.end(), cur_line, cur_line + d.len }); it = m_lines.insert(it, &lines[d.posB], &lines[d.posB + d.len]) + d.len; - break; } - case Diff::Remove: + else if (d.mode == Diff::Remove) { const LineCount cur_line = (int)(it - m_lines.begin()); - if (record_undo) - { - for (LineCount line = d.len-1; line >= 0; --line) - m_current_undo_group.emplace_back( - Modification::Erase, cur_line + line, - SharedString{m_lines.get_storage(cur_line + line)}); - } + + for (LineCount line = d.len-1; line >= 0; --line) + m_current_undo_group.emplace_back( + Modification::Erase, cur_line + line, + SharedString{m_lines.get_storage(cur_line + line)}); + it = m_lines.erase(it, it + d.len); m_changes.push_back({ Change::Erase, it == m_lines.end(), cur_line, cur_line + d.len }); - break; } } } From e9af3a4217843974c1e9c89bb8ddcfee44216c9e Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 14 May 2015 19:05:41 +0100 Subject: [PATCH 09/20] always_inline a few methods --- src/array_view.hh | 2 ++ src/diff.hh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/array_view.hh b/src/array_view.hh index 922cd99a..33ae8979 100644 --- a/src/array_view.hh +++ b/src/array_view.hh @@ -44,6 +44,8 @@ public: constexpr T* pointer() const { return m_pointer; } constexpr size_t size() const { return m_size; } + + [[gnu::always_inline]] constexpr T& operator[](size_t n) const { return *(m_pointer + n); } constexpr T* begin() const { return m_pointer; } diff --git a/src/diff.hh b/src/diff.hh index e68ec10c..da1328e2 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -18,7 +18,9 @@ struct MirroredArray : public ArrayView (*this)[i] = 0; } + [[gnu::always_inline]] T& operator[](int n) { return ArrayView::operator[](n + size); } + [[gnu::always_inline]] const T& operator[](int n) const { return ArrayView::operator[](n + size); } private: int size; From 802d6e106cb6a58ea2b0d4d0c986614e1713f34c Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 14 May 2015 19:13:52 +0100 Subject: [PATCH 10/20] Keep hash stored in StringData --- src/buffer.cc | 2 +- src/shared_string.hh | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index be3ca7a2..8b5f6ee5 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -180,7 +180,7 @@ void Buffer::reload(BufferLines lines, time_t fs_timestamp) auto diff = find_diff(m_lines.begin(), m_lines.size(), lines.begin(), (int)lines.size(), [](const StringDataPtr& lhs, const StringDataPtr& rhs) - { return lhs->strview() == rhs->strview(); }); + { return lhs->hash == rhs->hash and lhs->strview() == rhs->strview(); }); auto it = m_lines.begin(); for (auto& d : diff) diff --git a/src/shared_string.hh b/src/shared_string.hh index 74232613..cc52e0d9 100644 --- a/src/shared_string.hh +++ b/src/shared_string.hh @@ -13,8 +13,9 @@ struct StringData : UseMemoryDomain { int refcount; int length; + uint32_t hash; - constexpr StringData(int ref, int len) : refcount(ref), length(len) {} + StringData(int ref, int len) : refcount(ref), length(len) {} [[gnu::always_inline]] char* data() { return reinterpret_cast(this + 1); } @@ -32,6 +33,7 @@ struct StringData : UseMemoryDomain if (back != 0) res->data()[len-1] = back; res->data()[len] = 0; + res->hash = hash_data(res->data(), res->length); return RefPtr(res); } @@ -88,6 +90,11 @@ public: explicit SharedString(StringDataPtr storage) : StringView{storage->strview()}, m_storage(std::move(storage)) {} + friend size_t hash_value(const SharedString& str) + { + return str.m_storage ? str.m_storage->hash : hash_data(str.data(), (int)str.length()); + } + private: SharedString(StringView str, StringDataPtr storage) : StringView{str}, m_storage(std::move(storage)) {} @@ -96,11 +103,6 @@ private: StringDataPtr m_storage; }; -inline size_t hash_value(const SharedString& str) -{ - return hash_data(str.data(), (int)str.length()); -} - class StringRegistry : public Singleton { public: From 73ddf18dc3144c20b0eb88accd69d1b8b851d855 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Fri, 15 May 2015 13:55:39 +0100 Subject: [PATCH 11/20] Another bug fix in diff implementation --- src/diff.hh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/diff.hh b/src/diff.hh index da1328e2..fe679bd6 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -97,7 +97,7 @@ SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M, if ((delta % 2 == 0) and -D <= k1 and k1 <= D) { if (V1[k1] + V2[k2] >= N) - return { { N - p.u, M - p.v, N - p.x , M - p.y } , 2 * D };// return last snake on reverse path + return { { N - p.u, M - p.v, N - p.x , M - p.y, p.add } , 2 * D };// return last snake on reverse path } } } @@ -138,11 +138,7 @@ void find_diff_rec(Iterator a, int offA, int lenA, } else if (middle_snake.d == 1) { - int diag = 0; - while (eq(a[offA + diag], b[offB + diag])) - ++diag; - - if (diag != 0) + if (int diag = middle_snake.x - (middle_snake.add ? 0 : 1)) diffs.push_back({Diff::Keep, diag, 0}); if (middle_snake.add) From cf7b64ba67fc933938abcf676d5f4351078db919 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sat, 16 May 2015 11:42:58 +0100 Subject: [PATCH 12/20] Preserve selections across reloads --- src/client.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/client.cc b/src/client.cc index 0d729bc3..71e8c29f 100644 --- a/src/client.cc +++ b/src/client.cc @@ -173,12 +173,8 @@ void Client::reload_buffer() { auto& buffer = context().buffer(); kak_assert(buffer.flags() & Buffer::Flags::File); - CharCoord view_pos = context().window().position(); - ByteCoord cursor_pos = context().selections().main().cursor(); Buffer* buf = create_buffer_from_file(buffer.name()); kak_assert(buf == &buffer); - context().selections_write_only() = SelectionList{buffer, buffer.clamp(cursor_pos)}; - context().window().set_position(view_pos); context().print_status({ "'" + buffer.display_name() + "' reloaded", get_face("Information") }); } From 0a6ad4dcf4e5155bd4052e9dd72005a6f85366e1 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sun, 17 May 2015 20:13:11 +0100 Subject: [PATCH 13/20] Only initialize element 1 in mirrored arrays. --- src/diff.hh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/diff.hh b/src/diff.hh index fe679bd6..adaa5cdd 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -14,8 +14,7 @@ struct MirroredArray : public ArrayView : ArrayView(data), size(size) { kak_assert(2 * size + 1 <= data.size()); - for (int i = -size; i <= size; ++i) - (*this)[i] = 0; + (*this)[1] = 0; } [[gnu::always_inline]] From 38bbecef62da8c0ca82dbbc3debc81c5e1b96a2e Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 18 May 2015 22:59:59 +0100 Subject: [PATCH 14/20] Fix bug in diff implementations (missing snake after d=1 change) and refactor --- src/diff.hh | 59 ++++++++++++++++++++++------------------------- src/unit_tests.cc | 2 +- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/diff.hh b/src/diff.hh index adaa5cdd..f16b74ef 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -110,8 +110,19 @@ struct Diff enum { Keep, Add, Remove } mode; int len; int posB; + int posA; }; +inline void append_diff(Vector& diffs, Diff diff) +{ + if (not diffs.empty() and diffs.back().mode == diff.mode + and (diff.mode != Diff::Add or + diffs.back().posB + diffs.back().len == diff.posB)) + diffs.back().len += diff.len; + else + diffs.push_back(diff); +} + template void find_diff_rec(Iterator a, int offA, int lenA, Iterator b, int offB, int lenB, @@ -129,47 +140,33 @@ void find_diff_rec(Iterator a, int offA, int lenA, data1, data2, eq, diffs); if (int len = middle_snake.u - middle_snake.x) - diffs.push_back({Diff::Keep, len, 0}); + append_diff(diffs, {Diff::Keep, len, 0}); find_diff_rec(a, offA + middle_snake.u, lenA - middle_snake.u, b, offB + middle_snake.v, lenB - middle_snake.v, data1, data2, eq, diffs); } - else if (middle_snake.d == 1) + else { - if (int diag = middle_snake.x - (middle_snake.add ? 0 : 1)) - diffs.push_back({Diff::Keep, diag, 0}); + if (middle_snake.d == 1) + { + const int diag = middle_snake.x - (middle_snake.add ? 0 : 1); + if (diag != 0) + append_diff(diffs, {Diff::Keep, diag, 0}); - if (middle_snake.add) - diffs.push_back({Diff::Add, 1, offB + middle_snake.y-1}); - else - diffs.push_back({Diff::Remove, 1, 0}); + if (middle_snake.add) + append_diff(diffs, {Diff::Add, 1, offB + diag}); + else + append_diff(diffs, {Diff::Remove, 1, 0}); + } + if (int len = middle_snake.u - middle_snake.x) + append_diff(diffs, {Diff::Keep, len, 0}); } - else if (int len = middle_snake.u - middle_snake.x) - diffs.push_back({Diff::Keep, len, 0}); } else if (lenB > 0) - diffs.push_back({Diff::Add, lenB, offB}); + append_diff(diffs, {Diff::Add, lenB, offB}); else if (lenA > 0) - diffs.push_back({Diff::Remove, lenA, 0}); -} - -inline void compact_diffs(Vector& diffs) -{ - if (diffs.size() < 2) - return; - - auto out_it = diffs.begin(); - for (auto it = out_it + 1; it != diffs.end(); ++it) - { - if (it->mode == out_it->mode and - (it->mode != Diff::Add or - it->posB == out_it->posB + out_it->len)) - out_it->len += it->len; - else if (++out_it != it) - *out_it = *it; - } - diffs.erase(out_it+1, diffs.end()); + append_diff(diffs, {Diff::Remove, lenA, 0}); } template::value_type>> @@ -183,8 +180,6 @@ Vector find_diff(Iterator a, int N, Iterator b, int M, {data.data(), (size_t)max}, {data.data() + max, (size_t)max}, eq, diffs); - compact_diffs(diffs); - return diffs; } diff --git a/src/unit_tests.cc b/src/unit_tests.cc index 29fd90f6..b583c737 100644 --- a/src/unit_tests.cc +++ b/src/unit_tests.cc @@ -251,7 +251,7 @@ void test_diff() StringView s2 = "mais ou va la police"; auto diff = find_diff(s1.begin(), (int)s1.length(), s2.begin(), (int)s2.length()); - kak_assert(diff.size() == 10); + kak_assert(diff.size() == 11); } { From 9f46d75b27612b4bc280f61d09ba9212289b5ef7 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 19 May 2015 19:46:24 +0100 Subject: [PATCH 15/20] Add headers guard to diff.hh along with a comment about the algorithm --- src/diff.hh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/diff.hh b/src/diff.hh index f16b74ef..c339f435 100644 --- a/src/diff.hh +++ b/src/diff.hh @@ -1,3 +1,10 @@ +#ifndef diff_hh_INCLUDED +#define diff_hh_INCLUDED + +// Implementation of the linear space variant of the algorithm described in +// "An O(ND) Difference Algorithm and Its Variations" +// (http://xmailserver.org/diff2.pdf) + #include "array_view.hh" #include "vector.hh" @@ -184,3 +191,5 @@ Vector find_diff(Iterator a, int N, Iterator b, int M, } } + +#endif // diff_hh_INCLUDED From 9d4f397e396ebc8d118702d1d945275dc31cf200 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 May 2015 13:36:01 +0100 Subject: [PATCH 16/20] Use gcc-4.9 in travis tests --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3069c87..268046c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,10 +16,10 @@ before_install: install: - if [ $TRAVIS_OS_NAME = linux ]; then if [ "$CXX" = "clang++" ]; then - sudo apt-get install -y libstdc++-4.8-dev; + sudo apt-get install -y libstdc++-4.9-dev; elif [ "$CXX" = "g++" ]; then - sudo apt-get install -y g++-4.8; - export CXX=g++-4.8; + sudo apt-get install -y g++-4.9; + export CXX=g++-4.9; fi; sudo apt-get install -y libncursesw5-dev; sudo apt-get install -y libboost-regex1.54-dev; From 12789938dc2881750c2a8b2e77430be5b297942d Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 May 2015 13:54:17 +0100 Subject: [PATCH 17/20] Require Gcc 4.9 in Readme --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 3af6567c..13609e73 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -93,7 +93,7 @@ Building Kakoune dependencies are: - * A C++11 compliant compiler (GCC >= 4.8.1 or clang >= 3.4) + * A C++14 compliant compiler (GCC >= 4.9 or clang >= 3.4) * boost (>= 1.50) * ncurses with wide-characters support (>= 5.3, generally refered as libncursesw) From dfd6182bbb2f683d9ae096fed3210ee5f84a3d25 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 May 2015 14:01:28 +0100 Subject: [PATCH 18/20] Test suite shows something when kakoune exit code is not 0 --- test/run | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/run b/test/run index e3178c2e..65e8808b 100755 --- a/test/run +++ b/test/run @@ -42,6 +42,10 @@ main() { quit! " ${test}/../src/kak out -n -u -e "$kak_commands" + retval=$? + if (( retval != 0 )); then + echo "Kakoune returned error $retval" + fi for expect in $test_files; do if cmp -s $test/$dir/$expect $expect; then echo "$indent$name" | colorize green normal From 325cc2d894fda740ca44dd0df980d60393f9874e Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 May 2015 22:53:14 +0100 Subject: [PATCH 19/20] Ignore SIGPIPE --- src/main.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cc b/src/main.cc index fdc05ca0..6177227f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -541,6 +541,7 @@ int main(int argc, char* argv[]) signal(SIGFPE, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGTERM, signal_handler); + signal(SIGPIPE, SIG_IGN); Vector params; for (size_t i = 1; i < argc; ++i) From 41248c5728cf807939429a9f8a0c734e24c60c13 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 21 May 2015 13:33:40 +0100 Subject: [PATCH 20/20] Use clang-3.5 for travis tests on linux --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 268046c9..97a213aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ before_install: - if [ $TRAVIS_OS_NAME = linux ]; then sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y; sudo add-apt-repository ppa:boost-latest/ppa -y; + if [ "$CXX" = "clang++" ]; then + sudo add-apt-repository ppa:h-rayflood/llvm-upper -y; + fi; sudo apt-get update -qq; elif [ $TRAVIS_OS_NAME = osx ]; then brew update; @@ -17,6 +20,8 @@ install: - if [ $TRAVIS_OS_NAME = linux ]; then if [ "$CXX" = "clang++" ]; then sudo apt-get install -y libstdc++-4.9-dev; + sudo apt-get install -y clang-3.5; + export CXX=clang++-3.5; elif [ "$CXX" = "g++" ]; then sudo apt-get install -y g++-4.9; export CXX=g++-4.9;