diff --git a/.travis.yml b/.travis.yml index b3069c87..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; @@ -16,10 +19,12 @@ 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; + sudo apt-get install -y clang-3.5; + export CXX=clang++-3.5; 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; diff --git a/README.asciidoc b/README.asciidoc index 4677ba8e..cdde2fb1 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) 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) 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/buffer.cc b/src/buffer.cc index c0167193..8b5f6ee5 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,64 @@ 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(); + + if (not record_undo) { - 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}); + 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->hash == rhs->hash and lhs->strview() == rhs->strview(); }); + + auto it = m_lines.begin(); + for (auto& d : diff) + { + if (d.mode == Diff::Keep) + it += d.len; + else if (d.mode == Diff::Add) + { + const LineCount cur_line = (int)(it - m_lines.begin()); + + 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; + } + else if (d.mode == Diff::Remove) + { + const LineCount cur_line = (int)(it - m_lines.begin()); + + 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 }); + } + } } - 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() 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") }); } diff --git a/src/diff.hh b/src/diff.hh new file mode 100644 index 00000000..c339f435 --- /dev/null +++ b/src/diff.hh @@ -0,0 +1,195 @@ +#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" + +#include +#include + +namespace Kakoune +{ + +template +struct MirroredArray : public ArrayView +{ + MirroredArray(ArrayView data, int size) + : ArrayView(data), size(size) + { + kak_assert(2 * size + 1 <= data.size()); + (*this)[1] = 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; +}; + +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, Equal eq) +{ + 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 eq(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, + Equal eq) +{ + 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, eq); + 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, eq); + 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, p.add } , 2 * D };// return last snake on reverse path + } + } + } + + kak_assert(false); + return { {}, 0 }; +} + +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, + ArrayView data1, ArrayView data2, + Equal eq, Vector& diffs) +{ + 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, + b, offB, middle_snake.y, + data1, data2, eq, diffs); + + if (int len = middle_snake.u - middle_snake.x) + 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) + { + 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) + 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 (lenB > 0) + append_diff(diffs, {Diff::Add, lenB, offB}); + else if (lenA > 0) + append_diff(diffs, {Diff::Remove, lenA, 0}); +} + +template::value_type>> +Vector find_diff(Iterator a, int N, Iterator b, int M, + Equal eq = Equal{}) +{ + 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); + + return diffs; +} + +} + +#endif // diff_hh_INCLUDED 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) diff --git a/src/selection.cc b/src/selection.cc index 44850d80..602edd96 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -401,10 +401,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]; @@ -412,20 +410,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) 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: diff --git a/src/unit_tests.cc b/src/unit_tests.cc index 58f34a98..b583c737 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,33 @@ void test_line_modifications() } } +void test_diff() +{ + auto eq = [](const Diff& lhs, const Diff& rhs) { + return lhs.mode == rhs.mode and lhs.len == rhs.len and lhs.posB == rhs.posB; + }; + + { + 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() == 11); + } + + { + 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() { test_utf8(); @@ -248,4 +276,5 @@ void run_unit_tests() test_undo_group_optimizer(); test_word_db(); test_line_modifications(); + test_diff(); } 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 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