diff --git a/TODO b/TODO index 3f4cecd5..127ef8b0 100644 --- a/TODO +++ b/TODO @@ -60,16 +60,6 @@ the interpreters, regex for finding used kak variables (ability to add direct python/perl/ruby/whatever evaluation). -* Optimize BufferModificationListener behaviour: - - - Editor should modify in reverse order, as a modification at one point does not - impact the position of selections before it, so applying modification in reverse - order removes the needs to have selection updated on the fly, and permit deferred - update. - - - Accumulate all the modifications and update the selections with the - whole knowledge -> try to remove this O(n2) behaviour. - * User defined text objects * multiple parameters support for normal mode commands ? diff --git a/gdb/kakoune.py b/gdb/kakoune.py index 7486a7fa..2192d52f 100644 --- a/gdb/kakoune.py +++ b/gdb/kakoune.py @@ -105,8 +105,8 @@ def build_pretty_printer(): pp = gdb.printing.RegexpCollectionPrettyPrinter("kakoune") pp.add_printer('memoryview', '^Kakoune::memoryview<.*>$', MemoryView) pp.add_printer('LineAndColumn', '^Kakoune::LineAndColumn<.*>$', LineAndColumn) - pp.add_printer('BufferCoord', '^Kakoune::BufferCoord$', LineAndColumn) - pp.add_printer('DisplayCoord', '^Kakoune::DisplayCoord$', LineAndColumn) + pp.add_printer('ByteCoord', '^Kakoune::ByteCoord$', LineAndColumn) + pp.add_printer('CharCoord', '^Kakoune::CharCoord$', LineAndColumn) pp.add_printer('BufferIterator', '^Kakoune::BufferIterator$', BufferIterator) pp.add_printer('String', '^Kakoune::String$', String) pp.add_printer('Option', '^Kakoune::Option$', Option) diff --git a/src/buffer.cc b/src/buffer.cc index dbdd6247..884ae270 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -19,8 +19,6 @@ Buffer::Buffer(String name, Flags flags, std::vector lines, m_flags(flags | Flags::NoUndo), m_history(), m_history_cursor(m_history.begin()), m_last_save_undo_index(0), - // start buffer timestamp at 1 so that caches can init to 0 - m_timestamp(1), m_fs_timestamp(fs_timestamp), m_hooks(GlobalHooks::instance()), m_options(GlobalOptions::instance()), @@ -32,15 +30,15 @@ Buffer::Buffer(String name, Flags flags, std::vector lines, if (lines.empty()) lines.emplace_back("\n"); - ByteCount pos = 0; m_lines.reserve(lines.size()); for (auto& line : lines) { kak_assert(not line.empty() and line.back() == '\n'); - m_lines.emplace_back(Line{ m_timestamp, pos, std::move(line) }); - pos += m_lines.back().length(); + m_lines.emplace_back(std::move(line)); } + m_changes.push_back({ Change::Insert, {0,0}, line_count(), true }); + if (flags & Flags::File) { if (flags & Flags::New) @@ -68,38 +66,30 @@ Buffer::~Buffer() m_options.unregister_watcher(*this); BufferManager::instance().unregister_buffer(*this); m_values.clear(); - kak_assert(m_change_listeners.empty()); } void Buffer::reload(std::vector lines, time_t fs_timestamp) { - // use back coord to simulate the persistance of the last end of line - // as buffers are expected to never be empty. - for (auto listener : m_change_listeners) - listener->on_erase(*this, {0,0}, back_coord()); + m_changes.push_back({ Change::Erase, {0,0}, back_coord(), true }); m_history.clear(); m_current_undo_group.clear(); m_history_cursor = m_history.begin(); m_last_save_undo_index = 0; m_lines.clear(); - ++m_timestamp; if (lines.empty()) lines.emplace_back("\n"); - ByteCount pos = 0; m_lines.reserve(lines.size()); for (auto& line : lines) { kak_assert(not line.empty() and line.back() == '\n'); - m_lines.emplace_back(Line{ m_timestamp, pos, std::move(line) }); - pos += m_lines.back().length(); + m_lines.emplace_back(std::move(line)); } m_fs_timestamp = fs_timestamp; - for (auto listener : m_change_listeners) - listener->on_insert(*this, {0,0}, back_coord()); + m_changes.push_back({ Change::Insert, {0,0}, back_coord(), true }); } String Buffer::display_name() const @@ -141,7 +131,7 @@ ByteCoord Buffer::clamp(ByteCoord coord) const ByteCoord Buffer::offset_coord(ByteCoord coord, CharCount offset) { - auto& line = m_lines[coord.line].content; + auto& line = m_lines[coord.line]; auto character = std::max(0_char, std::min(line.char_count_to(coord.column) + offset, line.char_length() - 1)); return {coord.line, line.byte_count_to(character)}; @@ -149,9 +139,9 @@ ByteCoord Buffer::offset_coord(ByteCoord coord, CharCount offset) ByteCoord Buffer::offset_coord(ByteCoord coord, LineCount offset) { - auto character = m_lines[coord.line].content.char_count_to(coord.column); + auto character = m_lines[coord.line].char_count_to(coord.column); auto line = Kakoune::clamp(coord.line + offset, 0_line, line_count()-1); - auto& content = m_lines[line].content; + auto& content = m_lines[line]; character = std::max(0_char, std::min(character, content.char_length() - 2)); return {line, content.byte_count_to(character)}; @@ -168,7 +158,7 @@ String Buffer::string(ByteCoord begin, ByteCoord end) const ByteCount count = -1; if (line == end.line) count = end.column - start; - res += m_lines[line].content.substr(start, count); + res += m_lines[line].substr(start, count); } return res; } @@ -191,200 +181,12 @@ struct Buffer::Modification } }; -class UndoGroupOptimizer -{ - static constexpr auto Insert = Buffer::Modification::Type::Insert; - static constexpr auto Erase = Buffer::Modification::Type::Erase; - - static ByteCoord advance(ByteCoord coord, const String& str) - { - for (auto c : str) - { - if (c == '\n') - { - ++coord.line; - coord.column = 0; - } - else - ++coord.column; - } - return coord; - } - - static ByteCount count_byte_to(ByteCoord pos, ByteCoord endpos, const String& str) - { - ByteCount count = 0; - for (auto it = str.begin(); it != str.end() and pos != endpos; ++it) - { - if (*it == '\n') - { - ++pos.line; - pos.column = 0; - } - else - ++pos.column; - ++count; - } - kak_assert(pos == endpos); - return count; - } - - static const ByteCount overlaps(const String& lhs, const String& rhs) - { - if (lhs.empty() or rhs.empty()) - return -1; - - char c = rhs.front(); - ByteCount pos = 0; - while ((pos = (int)lhs.find_first_of(c, (int)pos)) != -1) - { - ByteCount i = pos, j = 0; - while (i != lhs.length() and j != rhs.length() and lhs[i] == rhs[j]) - ++i, ++j; - if (i == lhs.length()) - break; - ++pos; - } - return pos; - } - - static bool merge_contiguous(Buffer::UndoGroup& undo_group) - { - bool progress = false; - auto it = undo_group.begin(); - auto it_next = it+1; - while (it_next != undo_group.end()) - { - ByteCount pos; - auto& coord = it->coord; - auto& next_coord = it_next->coord; - - // reorders modification doing a kind of custom bubble sort - // so we have a O(n²) worst case complexity of the undo group optimization - if (next_coord < coord) - { - ByteCoord next_end = advance(next_coord, it_next->content); - if (it_next->type == Insert) - { - if (coord.line == next_coord.line) - coord.column += next_end.column - next_coord.column; - coord.line += next_end.line - next_coord.line; - } - else if (it->type == Insert and next_end > coord) - { - ByteCount start = count_byte_to(next_coord, coord, it_next->content); - ByteCount len = std::min(it->content.length(), it_next->content.length() - start); - kak_assert(it_next->content.substr(start, len) == it->content.substr(0, len)); - it->coord = it_next->coord; - it->content = it->content.substr(len); - it_next->content = it_next->content.substr(0,start) + it_next->content.substr(start + len); - } - else if (it->type == Erase and next_end >= coord) - { - ByteCount start = count_byte_to(next_coord, coord, it_next->content); - it_next->content = it_next->content.substr(0, start) + it->content + it_next->content.substr(start); - it->coord = it_next->coord; - it->content.clear(); - } - else - { - if (next_end.line == coord.line) - { - coord.line = next_coord.line; - coord.column = next_coord.column + coord.column - next_end.column; - } - else - coord.line -= next_end.line - next_coord.line; - } - std::swap(*it, *it_next); - progress = true; - } - - kak_assert(coord <= next_coord); - if (it->type == Erase and it_next->type == Erase and coord == next_coord) - { - it->content += it_next->content; - it_next = undo_group.erase(it_next); - progress = true; - } - else if (it->type == Insert and it_next->type == Insert and - is_in_range(next_coord, coord, advance(coord, it->content))) - { - ByteCount prefix_len = count_byte_to(coord, next_coord, it->content); - it->content = it->content.substr(0, prefix_len) + it_next->content - + it->content.substr(prefix_len); - it_next = undo_group.erase(it_next); - progress = true; - } - else if (it->type == Insert and it_next->type == Erase and - next_coord < advance(coord, it->content)) - { - ByteCount insert_len = it->content.length(); - ByteCount erase_len = it_next->content.length(); - ByteCount prefix_len = count_byte_to(coord, next_coord, it->content); - - ByteCount suffix_len = insert_len - prefix_len; - if (suffix_len >= erase_len) - { - it->content = it->content.substr(0, prefix_len) + it->content.substr(prefix_len + erase_len); - it_next = undo_group.erase(it_next); - } - else - { - it->content = it->content.substr(0, prefix_len); - it_next->content = it_next->content.substr(suffix_len); - ++it, ++it_next; - } - progress = true; - } - else if (it->type == Erase and it_next->type == Insert and coord == next_coord and - (pos = overlaps(it->content, it_next->content)) != -1) - { - ByteCount overlaps_len = it->content.length() - pos; - it->content = it->content.substr(0, pos); - it_next->coord = advance(it_next->coord, it_next->content.substr(0, overlaps_len)); - it_next->content = it_next->content.substr(overlaps_len); - ++it, ++it_next; - progress = true; - } - else - ++it, ++it_next; - } - return progress; - } - - static bool erase_empty(Buffer::UndoGroup& undo_group) - { - auto it = std::remove_if(begin(undo_group), end(undo_group), - [](Buffer::Modification& m) { return m.content.empty(); }); - if (it != end(undo_group)) - { - undo_group.erase(it, end(undo_group)); - return true; - } - return false; - } -public: - static void optimize(Buffer::UndoGroup& undo_group) - { - while (undo_group.size() > 1) - { - bool progress = false; - progress |= merge_contiguous(undo_group); - progress |= erase_empty(undo_group); - if (not progress) - break; - } - } -}; void Buffer::commit_undo_group() { if (m_flags & Flags::NoUndo) return; - UndoGroupOptimizer::optimize(m_current_undo_group); - if (m_current_undo_group.empty()) return; @@ -429,14 +231,11 @@ bool Buffer::redo() void Buffer::check_invariant() const { #ifdef KAK_DEBUG - ByteCount start = 0; kak_assert(not m_lines.empty()); for (auto& line : m_lines) { - kak_assert(line.start == start); kak_assert(line.length() > 0); - kak_assert(line.content.back() == '\n'); - start += line.length(); + kak_assert(line.back() == '\n'); } #endif } @@ -448,15 +247,9 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content) if (content.empty()) return pos; - ++m_timestamp; - ByteCount offset = this->offset(pos); - - // all following lines advanced by length - for (LineCount i = pos.line+1; i < line_count(); ++i) - m_lines[i].start += content.length(); - ByteCoord begin; ByteCoord end; + bool at_end = false; // if we inserted at the end of the buffer, we have created a new // line without inserting a '\n' if (is_end(pos)) @@ -466,22 +259,23 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content) { if (content[i] == '\n') { - m_lines.push_back({ m_timestamp, offset + start, content.substr(start, i + 1 - start) }); + m_lines.push_back(content.substr(start, i + 1 - start)); start = i + 1; } } if (start != content.length()) - m_lines.push_back({ m_timestamp, offset + start, content.substr(start) }); + m_lines.push_back(content.substr(start)); begin = pos.column == 0 ? pos : ByteCoord{ pos.line + 1, 0 }; - end = ByteCoord{ line_count()-1, m_lines.back().length() }; + end = ByteCoord{ line_count(), 0 }; + at_end = true; } else { - String prefix = m_lines[pos.line].content.substr(0, pos.column); - String suffix = m_lines[pos.line].content.substr(pos.column); + String prefix = m_lines[pos.line].substr(0, pos.column); + String suffix = m_lines[pos.line].substr(pos.column); - std::vector new_lines; + std::vector new_lines; ByteCount start = 0; for (ByteCount i = 0; i < content.length(); ++i) @@ -492,18 +286,17 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content) if (start == 0) { line_content = prefix + line_content; - new_lines.push_back({ m_timestamp, offset + start - prefix.length(), - std::move(line_content) }); + new_lines.push_back(std::move(line_content)); } else - new_lines.push_back({ m_timestamp, offset + start, std::move(line_content) }); + new_lines.push_back(std::move(line_content)); start = i + 1; } } if (start == 0) - new_lines.push_back({ m_timestamp, offset + start - prefix.length(), prefix + content + suffix }); + new_lines.push_back(prefix + content + suffix); else if (start != content.length() or not suffix.empty()) - new_lines.push_back({ m_timestamp, offset + start, content.substr(start) + suffix }); + new_lines.push_back(content.substr(start) + suffix); LineCount last_line = pos.line + new_lines.size() - 1; @@ -517,8 +310,7 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content) end = ByteCoord{ last_line, m_lines[last_line].length() - suffix.length() }; } - for (auto listener : m_change_listeners) - listener->on_insert(*this, begin, end); + m_changes.push_back({ Change::Insert, begin, end, at_end }); return begin; } @@ -526,11 +318,9 @@ ByteCoord Buffer::do_erase(ByteCoord begin, ByteCoord end) { kak_assert(is_valid(begin)); kak_assert(is_valid(end)); - ++m_timestamp; - const ByteCount length = distance(begin, end); - String prefix = m_lines[begin.line].content.substr(0, begin.column); - String suffix = m_lines[end.line].content.substr(end.column); - Line new_line = { m_timestamp, m_lines[begin.line].start, prefix + suffix }; + String prefix = m_lines[begin.line].substr(0, begin.column); + String suffix = m_lines[end.line].substr(end.column); + String new_line = prefix + suffix; ByteCoord next; if (new_line.length() != 0) @@ -545,11 +335,7 @@ ByteCoord Buffer::do_erase(ByteCoord begin, ByteCoord end) next = is_end(begin) ? end_coord() : ByteCoord{begin.line, 0}; } - for (LineCount i = begin.line+1; i < line_count(); ++i) - m_lines[i].start -= length; - - for (auto listener : m_change_listeners) - listener->on_erase(*this, begin, end); + m_changes.push_back({ Change::Erase, begin, end, is_end(begin) }); return next; } @@ -628,26 +414,44 @@ void Buffer::notify_saved() m_flags &= ~Flags::New; size_t history_cursor_index = m_history_cursor - m_history.begin(); if (m_last_save_undo_index != history_cursor_index) - { - ++m_timestamp; m_last_save_undo_index = history_cursor_index; - } m_fs_timestamp = get_fs_timestamp(m_name); } ByteCoord Buffer::advance(ByteCoord coord, ByteCount count) const { - ByteCount off = Kakoune::clamp(offset(coord) + count, 0_byte, byte_count()); - auto it = std::upper_bound(m_lines.begin(), m_lines.end(), off, - [](ByteCount s, const Line& l) { return s < l.start; }) - 1; - return { LineCount{ (int)(it - m_lines.begin()) }, off - it->start }; + if (count > 0) + { + auto line = coord.line; + count += coord.column; + while (count >= m_lines[line].length()) + { + count -= m_lines[line++].length(); + if (line == line_count()) + return end_coord(); + } + return { line, count }; + } + else if (count < 0) + { + auto line = coord.line; + count += coord.column; + while (count < 0) + { + count += m_lines[--line].length(); + if (line < 0) + return {0, 0}; + } + return { line, count }; + } + return coord; } ByteCoord Buffer::char_next(ByteCoord coord) const { if (coord.column < m_lines[coord.line].length() - 1) { - auto& line = m_lines[coord.line].content; + auto& line = m_lines[coord.line]; coord.column += utf8::codepoint_size(line.begin() + (int)coord.column); // Handle invalid utf-8 if (coord.column >= line.length()) @@ -678,7 +482,7 @@ ByteCoord Buffer::char_prev(ByteCoord coord) const } else { - auto& line = m_lines[coord.line].content; + auto& line = m_lines[coord.line]; coord.column = (int)(utf8::character_start(line.begin() + (int)coord.column - 1) - line.begin()); } return coord; @@ -704,7 +508,7 @@ void Buffer::on_option_changed(const Option& option) void Buffer::run_hook_in_own_context(const String& hook_name, const String& param) { - InputHandler hook_handler(*this, { Selection{} }); + InputHandler hook_handler({ *this, Selection{} }); m_hooks.run_hook(hook_name, param, hook_handler.context()); } diff --git a/src/buffer.hh b/src/buffer.hh index ff09528e..2aa38ad3 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -63,15 +63,6 @@ private: ByteCoord m_coord; }; -class BufferChangeListener -{ -public: - virtual void on_insert(const Buffer& buffer, - ByteCoord begin, ByteCoord end) = 0; - virtual void on_erase(const Buffer& buffer, - ByteCoord begin, ByteCoord end) = 0; -}; - // A Buffer is a in-memory representation of a file // // The Buffer class permits to read and mutate this file @@ -104,7 +95,6 @@ public: BufferIterator erase(BufferIterator begin, BufferIterator end); size_t timestamp() const; - size_t line_timestamp(LineCount line) const; time_t fs_timestamp() const; void set_fs_timestamp(time_t ts); @@ -115,7 +105,6 @@ public: String string(ByteCoord begin, ByteCoord end) const; char byte_at(ByteCoord c) const; - ByteCount offset(ByteCoord c) const; ByteCount distance(ByteCoord begin, ByteCoord end) const; ByteCoord advance(ByteCoord coord, ByteCount count) const; ByteCoord next(ByteCoord coord) const; @@ -134,11 +123,10 @@ public: BufferIterator begin() const; BufferIterator end() const; - ByteCount byte_count() const; LineCount line_count() const; const String& operator[](LineCount line) const - { return m_lines[line].content; } + { return m_lines[line]; } // returns an iterator at given coordinates. clamp line_and_column BufferIterator iterator_at(ByteCoord coord) const; @@ -170,30 +158,30 @@ public: void run_hook_in_own_context(const String& hook_name, const String& param); - std::unordered_set& change_listeners() const { return m_change_listeners; } - void reload(std::vector lines, time_t fs_timestamp = InvalidTime); void check_invariant() const; + + struct Change + { + enum Type { Insert, Erase }; + Type type; + ByteCoord begin; + ByteCoord end; + bool at_end; + }; + memoryview changes_since(size_t timestamp) const; private: void on_option_changed(const Option& option) override; - struct Line + struct LineList : std::vector { - size_t timestamp; - ByteCount start; - String content; + String& operator[](LineCount line) + { return std::vector::operator[]((int)line); } - ByteCount length() const { return content.length(); } - }; - struct LineList : std::vector - { - Line& operator[](LineCount line) - { return std::vector::operator[]((int)line); } - - const Line& operator[](LineCount line) const - { return std::vector::operator[]((int)line); } + const String& operator[](LineCount line) const + { return std::vector::operator[]((int)line); } }; LineList m_lines; @@ -215,14 +203,11 @@ private: void revert_modification(const Modification& modification); size_t m_last_save_undo_index; - size_t m_timestamp; + + std::vector m_changes; time_t m_fs_timestamp; - // this is mutable as adding or removing listeners is not muting the - // buffer observable state. - mutable std::unordered_set m_change_listeners; - OptionManager m_options; HookManager m_hooks; KeymapManager m_keymaps; @@ -259,30 +244,6 @@ private: } }; -struct BufferListenerRegisterFuncs -{ - static void insert(const Buffer& buffer, BufferChangeListener& listener) - { - buffer.change_listeners().insert(&listener); - } - static void remove(const Buffer& buffer, BufferChangeListener& listener) - { - buffer.change_listeners().erase(&listener); - } -}; - -class BufferChangeListener_AutoRegister - : public BufferChangeListener, - public AutoRegister -{ -public: - BufferChangeListener_AutoRegister(Buffer& buffer) - : AutoRegister(buffer) {} - - Buffer& buffer() const { return registry(); } -}; - } #include "buffer.inl.hh" diff --git a/src/buffer.inl.hh b/src/buffer.inl.hh index 1920857d..f457eb0f 100644 --- a/src/buffer.inl.hh +++ b/src/buffer.inl.hh @@ -9,7 +9,7 @@ namespace Kakoune inline char Buffer::byte_at(ByteCoord c) const { kak_assert(c.line < line_count() and c.column < m_lines[c.line].length()); - return m_lines[c.line].content[c.column]; + return m_lines[c.line][c.column]; } inline ByteCoord Buffer::next(ByteCoord coord) const @@ -40,18 +40,26 @@ inline ByteCoord Buffer::prev(ByteCoord coord) const inline ByteCount Buffer::distance(ByteCoord begin, ByteCoord end) const { - return offset(end) - offset(begin); -} - -inline ByteCount Buffer::offset(ByteCoord c) const -{ - if (c.line == line_count()) - return m_lines.back().start + m_lines.back().length(); - return m_lines[c.line].start + c.column; + if (begin > end) + return -distance(end, begin); + ByteCount res = 0; + for (LineCount l = begin.line; l <= end.line; ++l) + { + ByteCount len = m_lines[l].length(); + res += len; + if (l == begin.line) + res -= begin.column; + if (l == end.line) + res -= len - end.column; + } + return res; } inline bool Buffer::is_valid(ByteCoord c) const { + if (c.line < 0 or c.column < 0) + return false; + return (c.line < line_count() and c.column < m_lines[c.line].length()) or (c.line == line_count() - 1 and c.column == m_lines.back().length()) or (c.line == line_count() and c.column == 0); @@ -74,13 +82,6 @@ inline BufferIterator Buffer::end() const return BufferIterator(*this, { line_count() - 1, m_lines.back().length() }); } -inline ByteCount Buffer::byte_count() const -{ - if (m_lines.empty()) - return 0; - return m_lines.back().start + m_lines.back().length(); -} - inline LineCount Buffer::line_count() const { return LineCount(m_lines.size()); @@ -88,12 +89,13 @@ inline LineCount Buffer::line_count() const inline size_t Buffer::timestamp() const { - return m_timestamp; + return m_changes.size(); } -inline size_t Buffer::line_timestamp(LineCount line) const +inline memoryview Buffer::changes_since(size_t timestamp) const { - return m_lines[line].timestamp; + return { m_changes.data() + timestamp, + m_changes.data() + m_changes.size() }; } inline ByteCoord Buffer::back_coord() const diff --git a/src/buffer_utils.hh b/src/buffer_utils.hh index bd617595..f47c2c26 100644 --- a/src/buffer_utils.hh +++ b/src/buffer_utils.hh @@ -24,20 +24,6 @@ inline CharCount char_length(const Buffer& buffer, const Selection& range) utf8::next(buffer.iterator_at(range.max()))); } -inline void avoid_eol(const Buffer& buffer, ByteCoord& coord) -{ - const auto column = coord.column; - const auto& line = buffer[coord.line]; - if (column != 0 and column == line.length() - 1) - 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()); -} - CharCount get_column(const Buffer& buffer, CharCount tabstop, ByteCoord coord); diff --git a/src/client.cc b/src/client.cc index 5be59b99..dacaf607 100644 --- a/src/client.cc +++ b/src/client.cc @@ -18,7 +18,7 @@ Client::Client(std::unique_ptr&& ui, EnvVarMap env_vars, String name) : m_ui{std::move(ui)}, m_window{std::move(window)}, - m_input_handler{m_window->buffer(), std::move(selections), + m_input_handler{std::move(selections), std::move(name)}, m_env_vars(env_vars) { @@ -102,7 +102,7 @@ static void reload_buffer(Context& context, const String& filename) if (not buf) return; context.change_buffer(*buf); - context.selections() = SelectionList{buf->clamp(cursor_pos)}; + context.selections() = SelectionList{ *buf, buf->clamp(cursor_pos)}; context.window().set_position(view_pos); context.print_status({ "'" + buf->display_name() + "' reloaded", get_color("Information") }); diff --git a/src/client_manager.cc b/src/client_manager.cc index 6e3fd638..692a1a46 100644 --- a/src/client_manager.cc +++ b/src/client_manager.cc @@ -94,18 +94,18 @@ WindowAndSelections ClientManager::get_free_window(Buffer& buffer) w->forget_timestamp(); WindowAndSelections res = std::move(*it); m_free_windows.erase(it.base()-1); + res.selections.update(); return res; } } return WindowAndSelections{ std::unique_ptr{new Window{buffer}}, - DynamicSelectionList{buffer, - { Selection{ {}, {} } } } }; + SelectionList{ buffer, Selection{} } }; } void ClientManager::add_free_window(std::unique_ptr&& window, SelectionList selections) { Buffer& buffer = window->buffer(); - m_free_windows.push_back({ std::move(window), DynamicSelectionList{ buffer, std::move(selections) } }); + m_free_windows.push_back({ std::move(window), SelectionList{ std::move(selections) }, buffer.timestamp() }); } void ClientManager::ensure_no_client_uses_buffer(Buffer& buffer) diff --git a/src/client_manager.hh b/src/client_manager.hh index d3334486..410adab5 100644 --- a/src/client_manager.hh +++ b/src/client_manager.hh @@ -12,7 +12,8 @@ struct client_removed{}; struct WindowAndSelections { std::unique_ptr window; - DynamicSelectionList selections; + SelectionList selections; + size_t timestamp; }; class ClientManager : public Singleton diff --git a/src/commands.cc b/src/commands.cc index 818919a5..94b728a9 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -131,7 +131,8 @@ void edit(const ParametersParser& parser, Context& context) int column = param_count > 2 and not parser[2].empty() ? std::max(0, str_to_int(parser[2]) - 1) : 0; - context.selections() = context.buffer().clamp({ line, column }); + auto& buffer = context.buffer(); + context.selections() = { buffer, buffer.clamp({ line, column }) }; if (context.has_window()) context.window().center_line(context.selections().main().cursor().line); } @@ -968,7 +969,7 @@ void context_wrap(const ParametersParser& parser, Context& context, Func func) for (auto& name : names) { Buffer& buffer = BufferManager::instance().get_buffer(name); - InputHandler input_handler{buffer, ( Selection{} )}; + InputHandler input_handler{{ buffer, Selection{} }}; func(parser, input_handler.context()); } return; @@ -986,7 +987,7 @@ void context_wrap(const ParametersParser& parser, Context& context, Func func) if (parser.has_option("draft")) { - InputHandler input_handler(real_context->buffer(), real_context->selections(), real_context->name()); + InputHandler input_handler(real_context->selections(), real_context->name()); // We do not want this draft context to commit undo groups if the real one is // going to commit the whole thing later @@ -995,12 +996,17 @@ void context_wrap(const ParametersParser& parser, Context& context, Func func) if (parser.has_option("itersel")) { - DynamicSelectionList sels{real_context->buffer(), real_context->selections()}; + SelectionList sels{real_context->selections()}; ScopedEdition edition{input_handler.context()}; for (auto& sel : sels) { - input_handler.context().selections() = sel; + input_handler.context().selections() = SelectionList{ sels.buffer(), sel, sels.timestamp() }; + input_handler.context().selections().update(); + func(parser, input_handler.context()); + + if (&sels.buffer() != &input_handler.context().buffer()) + throw runtime_error("the buffer has changed while iterating on selections"); } } else diff --git a/src/context.cc b/src/context.cc index 00e3ff50..4f28b596 100644 --- a/src/context.cc +++ b/src/context.cc @@ -10,10 +10,10 @@ namespace Kakoune Context::Context() = default; Context::~Context() = default; -Context::Context(InputHandler& input_handler, Buffer& buffer, - SelectionList selections, String name) +Context::Context(InputHandler& input_handler, SelectionList selections, + String name) : m_input_handler{&input_handler}, - m_selections{{buffer, std::move(selections)}}, + m_selections{std::move(selections)}, m_name(std::move(name)) {} @@ -21,7 +21,7 @@ Buffer& Context::buffer() const { if (not has_buffer()) throw runtime_error("no buffer in context"); - return (*m_selections).registry(); + return const_cast((*m_selections).buffer()); } Window& Context::window() const @@ -103,32 +103,37 @@ void Context::push_jump() if (m_current_jump != m_jump_list.end()) { auto begin = m_current_jump; - if (&buffer() != &begin->buffer() or - (const SelectionList&)(*begin) != jump) + if (&buffer() != &begin->buffer() or *begin != jump) ++begin; m_jump_list.erase(begin, m_jump_list.end()); } m_jump_list.erase(std::remove(begin(m_jump_list), end(m_jump_list), jump), end(m_jump_list)); - m_jump_list.push_back({buffer(), jump}); + m_jump_list.push_back(jump); m_current_jump = m_jump_list.end(); } -const DynamicSelectionList& Context::jump_forward() +const SelectionList& Context::jump_forward() { if (m_current_jump != m_jump_list.end() and m_current_jump + 1 != m_jump_list.end()) - return *++m_current_jump; + { + SelectionList& res = *++m_current_jump; + res.update(); + return res; + } throw runtime_error("no next jump"); } -const DynamicSelectionList& Context::jump_backward() +const SelectionList& Context::jump_backward() { if (m_current_jump != m_jump_list.end() and *m_current_jump != selections()) { push_jump(); - return *--m_current_jump; + SelectionList& res = *--m_current_jump; + res.update(); + return res; } if (m_current_jump != m_jump_list.begin()) { @@ -137,7 +142,9 @@ const DynamicSelectionList& Context::jump_backward() push_jump(); --m_current_jump; } - return *--m_current_jump; + SelectionList& res = *--m_current_jump; + res.update(); + return res; } throw runtime_error("no previous jump"); } @@ -168,7 +175,7 @@ void Context::change_buffer(Buffer& buffer) if (has_client()) client().change_buffer(buffer); else - m_selections = DynamicSelectionList{ buffer }; + m_selections = SelectionList{buffer, Selection{}}; if (has_input_handler()) input_handler().reset_normal_mode(); } @@ -177,14 +184,13 @@ SelectionList& Context::selections() { if (not m_selections) throw runtime_error("no selections in context"); + (*m_selections).update(); return *m_selections; } const SelectionList& Context::selections() const { - if (not m_selections) - throw runtime_error("no selections in context"); - return *m_selections; + return const_cast(*this).selections(); } std::vector Context::selections_content() const @@ -196,6 +202,12 @@ std::vector Context::selections_content() const return contents; } +void Context::set_selections(std::vector sels) +{ + *m_selections = std::move(sels); + (*m_selections).check_invariant(); +} + void Context::begin_edition() { if (m_edition_level >= 0) diff --git a/src/context.hh b/src/context.hh index 60264aba..3aa4e0cf 100644 --- a/src/context.hh +++ b/src/context.hh @@ -1,7 +1,7 @@ #ifndef context_hh_INCLUDED #define context_hh_INCLUDED -#include "dynamic_selection_list.hh" +#include "selection.hh" #include @@ -26,7 +26,8 @@ class Context { public: Context(); - Context(InputHandler& input_handler, Buffer& buffer, SelectionList selections, String name = ""); + Context(InputHandler& input_handler, SelectionList selections, + String name = ""); ~Context(); Context(const Context&) = delete; @@ -50,6 +51,7 @@ public: SelectionList& selections(); const SelectionList& selections() const; std::vector selections_content() const; + void set_selections(std::vector sels); void change_buffer(Buffer& buffer); @@ -63,8 +65,8 @@ public: void print_status(DisplayLine status) const; void push_jump(); - const DynamicSelectionList& jump_forward(); - const DynamicSelectionList& jump_backward(); + const SelectionList& jump_forward(); + const SelectionList& jump_backward(); void forget_jumps_to_buffer(Buffer& buffer); const String& name() const { return m_name; } @@ -84,11 +86,11 @@ private: safe_ptr m_client; friend class Client; - boost::optional m_selections; + boost::optional m_selections; String m_name; - using JumpList = std::vector; + using JumpList = std::vector; JumpList m_jump_list; JumpList::iterator m_current_jump = m_jump_list.begin(); }; diff --git a/src/dynamic_selection_list.cc b/src/dynamic_selection_list.cc deleted file mode 100644 index 569d7818..00000000 --- a/src/dynamic_selection_list.cc +++ /dev/null @@ -1,49 +0,0 @@ -#include "dynamic_selection_list.hh" - -namespace Kakoune -{ - -DynamicSelectionList::DynamicSelectionList(Buffer& buffer, - SelectionList selections) - : SelectionList(std::move(selections)), - BufferChangeListener_AutoRegister(buffer) -{ - check_invariant(); -} - -DynamicSelectionList& DynamicSelectionList::operator=(SelectionList selections) -{ - SelectionList::operator=(std::move(selections)); - check_invariant(); - return *this; -} - -void DynamicSelectionList::check_invariant() const -{ -#ifdef KAK_DEBUG - SelectionList::check_invariant(); - const Buffer& buffer = registry(); - for (size_t i = 0; i < size(); ++i) - { - auto& sel = (*this)[i]; - kak_assert(buffer.is_valid(sel.anchor())); - kak_assert(buffer.is_valid(sel.cursor())); - kak_assert(not buffer.is_end(sel.anchor())); - kak_assert(not buffer.is_end(sel.cursor())); - kak_assert(utf8::is_character_start(buffer.iterator_at(sel.anchor()))); - kak_assert(utf8::is_character_start(buffer.iterator_at(sel.cursor()))); - } -#endif -} - -void DynamicSelectionList::on_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end) -{ - update_insert(buffer, begin, end); -} - -void DynamicSelectionList::on_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end) -{ - update_erase(buffer, begin, end); -} - -} diff --git a/src/dynamic_selection_list.hh b/src/dynamic_selection_list.hh deleted file mode 100644 index 4bfbd39a..00000000 --- a/src/dynamic_selection_list.hh +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef dynamic_selection_list_hh_INCLUDED -#define dynamic_selection_list_hh_INCLUDED - -#include "selection.hh" - -namespace Kakoune -{ - -class DynamicSelectionList : public SelectionList, - public BufferChangeListener_AutoRegister -{ -public: - using iterator = SelectionList::iterator; - using const_iterator = SelectionList::const_iterator; - - DynamicSelectionList(Buffer& buffer, SelectionList selections = { Selection{} }); - - DynamicSelectionList& operator=(SelectionList selections); - void check_invariant() const; - -private: - void on_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end) override; - void on_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end) override; -}; - -} - -#endif // dynamic_selection_list_hh_INCLUDED - diff --git a/src/highlighters.cc b/src/highlighters.cc index dff2f9e4..ab765bb2 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -5,7 +5,7 @@ #include "color_registry.hh" #include "context.hh" #include "display_buffer.hh" -#include "line_change_watcher.hh" +#include "line_modification.hh" #include "option_types.hh" #include "register_manager.hh" #include "string.hh" @@ -158,7 +158,7 @@ struct BufferSideCache { Value& cache_val = buffer.values()[m_id]; if (not cache_val) - cache_val = Value(T{buffer}); + cache_val = Value(T{}); return cache_val.as(); } private: @@ -195,7 +195,6 @@ public: private: struct Cache { - Cache(const Buffer&){} std::pair m_range; size_t m_timestamp = 0; std::vector>> m_matches; @@ -699,10 +698,8 @@ private: : lhs.begin < rhs.begin; } - struct Cache : public LineChangeWatcher + struct Cache { - Cache(const Buffer& buffer) : LineChangeWatcher(buffer) {} - size_t timestamp = 0; MatchList begin_matches; MatchList end_matches; @@ -724,7 +721,7 @@ private: } else { - auto modifs = cache.compute_modifications(); + auto modifs = compute_line_modifications(buffer, cache.timestamp); update_matches(buffer, modifs, cache.begin_matches, m_begin); update_matches(buffer, modifs, cache.end_matches, m_end); } @@ -746,7 +743,7 @@ private: beg_it = std::upper_bound(beg_it, cache.begin_matches.end(), *end_it, compare_matches_end); } - cache.timestamp = buffer.timestamp(); + cache.timestamp = buf_timestamp; return cache.regions; } @@ -775,15 +772,15 @@ private: auto modif_it = std::lower_bound(modifs.begin(), modifs.end(), it->line, [](const LineModification& c, const LineCount& l) { return c.old_line < l; }); - bool erase = false; - if (modif_it != modifs.begin()) + + bool erase = (modif_it != modifs.end() and modif_it->old_line == it->line); + if (not erase and modif_it != modifs.begin()) { auto& prev = *(modif_it-1); erase = it->line <= prev.old_line + prev.num_removed; it->line += prev.diff(); } - erase = erase or (it->line >= buffer.line_count() or - it->timestamp < buffer.line_timestamp(it->line)); + erase = erase or (it->line >= buffer.line_count()); if (not erase) { @@ -804,9 +801,10 @@ private: // try to find new matches in each updated lines for (auto& modif : modifs) { - for (auto line = modif.new_line; line < modif.new_line + modif.num_added+1; ++line) + for (auto line = modif.new_line; + line < modif.new_line + modif.num_added+1 and + line < buffer.line_count(); ++line) { - kak_assert(line < buffer.line_count()); auto& l = buffer[line]; for (boost::regex_iterator it{l.begin(), l.end(), regex}, end{}; it != end; ++it) { diff --git a/src/input_handler.cc b/src/input_handler.cc index ee136e24..d96a8f10 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -675,21 +675,23 @@ public: } else if (key == Key::Backspace) { + std::vector sels; for (auto& sel : context().selections()) { if (sel.cursor() == ByteCoord{0,0}) continue; - auto pos = buffer.iterator_at(sel.cursor()); - buffer.erase(utf8::previous(pos), pos); + auto pos = sel.cursor(); + sels.push_back({ buffer.char_prev(pos) }); } + if (not sels.empty()) + SelectionList{buffer, std::move(sels)}.erase(); } else if (key == Key::Delete) { + std::vector sels; for (auto& sel : context().selections()) - { - auto pos = buffer.iterator_at(sel.cursor()); - buffer.erase(pos, utf8::next(pos)); - } + sels.push_back({ sel.cursor() }); + SelectionList{buffer, std::move(sels)}.erase(); } else if (key == Key::Left) { @@ -765,22 +767,13 @@ private: void insert(memoryview strings) { - auto& buffer = context().buffer(); - auto& selections = context().selections(); - for (size_t i = 0; i < selections.size(); ++i) - { - size_t index = std::min(i, strings.size()-1); - buffer.insert(buffer.iterator_at(selections[i].cursor()), - strings[index]); - } + context().selections().insert(strings, InsertMode::InsertCursor); } void insert(Codepoint key) { auto str = codepoint_to_str(key); - auto& buffer = context().buffer(); - for (auto& sel : context().selections()) - buffer.insert(buffer.iterator_at(sel.cursor()), str); + context().selections().insert(str, InsertMode::InsertCursor); context().hooks().run_hook("InsertChar", str, context()); } @@ -789,69 +782,65 @@ private: SelectionList& selections = context().selections(); Buffer& buffer = context().buffer(); - for (auto& sel : selections) + switch (mode) { - ByteCoord anchor, cursor; - switch (mode) + case InsertMode::Insert: + for (auto& sel : selections) + sel = Selection{sel.max(), sel.min()}; + break; + case InsertMode::Replace: + selections.erase(); + break; + case InsertMode::Append: + for (auto& sel : selections) { - case InsertMode::Insert: - anchor = sel.max(); - cursor = sel.min(); - break; - case InsertMode::Replace: - anchor = cursor = Kakoune::erase(buffer, sel).coord(); - break; - case InsertMode::Append: - anchor = sel.min(); - cursor = sel.max(); + sel = Selection{sel.min(), sel.max()}; + auto& cursor = sel.cursor(); // special case for end of lines, append to current line instead if (cursor.column != buffer[cursor.line].length() - 1) cursor = buffer.char_next(cursor); - break; - - case InsertMode::OpenLineBelow: - case InsertMode::AppendAtLineEnd: - anchor = cursor = ByteCoord{sel.max().line, buffer[sel.max().line].length() - 1}; - break; - - case InsertMode::OpenLineAbove: - case InsertMode::InsertAtLineBegin: - anchor = sel.min().line; - if (mode == InsertMode::OpenLineAbove) - anchor = buffer.char_prev(anchor); - else - { - auto anchor_non_blank = buffer.iterator_at(anchor); - while (*anchor_non_blank == ' ' or *anchor_non_blank == '\t') - ++anchor_non_blank; - if (*anchor_non_blank != '\n') - anchor = anchor_non_blank.coord(); - } - cursor = anchor; - break; - case InsertMode::InsertAtNextLineBegin: - kak_assert(false); // not implemented - break; } - if (buffer.is_end(anchor)) - anchor = buffer.char_prev(anchor); - if (buffer.is_end(cursor)) - cursor = buffer.char_prev(cursor); - sel.anchor() = anchor; - sel.cursor() = cursor; - } - if (mode == InsertMode::OpenLineBelow or mode == InsertMode::OpenLineAbove) - { + break; + case InsertMode::AppendAtLineEnd: + for (auto& sel : selections) + sel = ByteCoord{sel.max().line, buffer[sel.max().line].length() - 1}; + break; + case InsertMode::OpenLineBelow: + for (auto& sel : selections) + sel = ByteCoord{sel.max().line, buffer[sel.max().line].length() - 1}; insert('\n'); - if (mode == InsertMode::OpenLineAbove) + break; + case InsertMode::OpenLineAbove: + for (auto& sel : selections) { - for (auto& sel : selections) - { - // special case, the --first line above did nothing, so we need to compensate now - if (sel.anchor() == buffer.char_next({0,0})) - sel.anchor() = sel.cursor() = ByteCoord{0,0}; - } + auto line = sel.min().line; + sel = line > 0 ? ByteCoord{line - 1, buffer[line-1].length() - 1} + : ByteCoord{0, 0}; } + insert('\n'); + // fix case where we inserted at begining + for (auto& sel : selections) + { + if (sel.anchor() == buffer.char_next({0,0})) + sel = Selection{{0,0}}; + } + break; + case InsertMode::InsertAtLineBegin: + for (auto& sel : selections) + { + ByteCoord pos = sel.min().line; + auto pos_non_blank = buffer.iterator_at(pos); + while (*pos_non_blank == ' ' or *pos_non_blank == '\t') + ++pos_non_blank; + if (*pos_non_blank != '\n') + pos = pos_non_blank.coord(); + sel = pos; + } + break; + case InsertMode::InsertAtNextLineBegin: + case InsertMode::InsertCursor: + kak_assert(false); // invalid for interactive insert + break; } selections.sort_and_merge_overlapping(); selections.check_invariant(); @@ -860,12 +849,13 @@ private: void on_disabled() override { - for (auto& sel : context().selections()) + auto& selections = context().selections(); + for (auto& sel : selections) { if (m_insert_mode == InsertMode::Append and sel.cursor().column > 0) sel.cursor() = context().buffer().char_prev(sel.cursor()); - avoid_eol(context().buffer(), sel); } + selections.avoid_eol(); } enum class Mode { Default, Complete, InsertReg }; @@ -883,9 +873,9 @@ void InputMode::reset_normal_mode() m_input_handler.reset_normal_mode(); } -InputHandler::InputHandler(Buffer& buffer, SelectionList selections, String name) +InputHandler::InputHandler(SelectionList selections, String name) : m_mode(new InputModes::Normal(*this)), - m_context(*this, buffer, std::move(selections), std::move(name)) + m_context(*this, std::move(selections), std::move(name)) { } diff --git a/src/input_handler.hh b/src/input_handler.hh index 0d9d34cc..80b8b43c 100644 --- a/src/input_handler.hh +++ b/src/input_handler.hh @@ -35,7 +35,7 @@ enum class InsertMode : unsigned; class InputHandler : public SafeCountable { public: - InputHandler(Buffer& buffer, SelectionList selections, String name = ""); + InputHandler(SelectionList selections, String name = ""); ~InputHandler(); // switch to insert mode diff --git a/src/insert_completer.cc b/src/insert_completer.cc index 7b7d52fe..27342245 100644 --- a/src/insert_completer.cc +++ b/src/insert_completer.cc @@ -185,25 +185,26 @@ void InsertCompleter::select(int offset) if (m_current_candidate < 0) m_current_candidate += m_matching_candidates.size(); const String& candidate = m_matching_candidates[m_current_candidate]; - const auto& cursor_pos = m_context.selections().main().cursor(); + auto& selections = m_context.selections(); + const auto& cursor_pos = selections.main().cursor(); const auto prefix_len = buffer.distance(m_completions.begin, cursor_pos); const auto suffix_len = std::max(0_byte, buffer.distance(cursor_pos, m_completions.end)); - const auto buffer_len = buffer.byte_count(); auto ref = buffer.string(m_completions.begin, m_completions.end); - for (auto& sel : m_context.selections()) + for (auto& sel : selections) { - auto offset = buffer.offset(sel.cursor()); - auto pos = buffer.iterator_at(sel.cursor()); - if (offset >= prefix_len and offset + suffix_len < buffer_len and + const auto& cursor = sel.cursor(); + auto pos = buffer.iterator_at(cursor); + if (cursor.column >= prefix_len and (pos + suffix_len) != buffer.end() and std::equal(ref.begin(), ref.end(), pos - prefix_len)) { pos = buffer.erase(pos - prefix_len, pos + suffix_len); buffer.insert(pos, candidate); + const_cast(selections).update(); } } - m_completions.end = cursor_pos; - m_completions.begin = buffer.advance(m_completions.end, -candidate.length()); + m_completions.end = cursor_pos; + m_completions.begin = buffer.advance(cursor_pos, -candidate.length()); m_completions.timestamp = buffer.timestamp(); if (m_context.has_ui()) m_context.ui().menu_select(m_current_candidate); @@ -226,8 +227,8 @@ void InsertCompleter::update() ByteCoord cursor = m_context.selections().main().cursor(); ByteCoord compl_beg = m_completions.begin; if (cursor.line == compl_beg.line and - is_in_range(cursor.column - compl_beg.column, - ByteCount{0}, longest_completion-1)) + is_in_range(cursor.column, compl_beg.column, + compl_beg.column + longest_completion-1)) { String prefix = m_context.buffer().string(compl_beg, cursor); diff --git a/src/line_change_watcher.hh b/src/line_change_watcher.hh deleted file mode 100644 index ce7bedfa..00000000 --- a/src/line_change_watcher.hh +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef line_change_watcher_hh_INCLUDED -#define line_change_watcher_hh_INCLUDED - -#include "buffer.hh" - -namespace Kakoune -{ - -struct LineModification -{ - LineCount old_line; // line position in the old buffer - LineCount new_line; // new line position - LineCount num_removed; // number of lines removed after this one - LineCount num_added; // number of lines added after this one - - LineCount diff() const { return new_line - old_line + num_added - num_removed; } -}; - -class LineChangeWatcher : public BufferChangeListener_AutoRegister -{ -public: - LineChangeWatcher (const Buffer& buffer) - : BufferChangeListener_AutoRegister(const_cast(buffer)) {} - - std::vector compute_modifications(); -private: - void on_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end) override; - void on_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end) override; - - struct Change - { - LineCount pos; - LineCount num; - }; - std::vector m_changes; -}; - -} - -#endif // line_change_watcher_hh_INCLUDED diff --git a/src/line_change_watcher.cc b/src/line_modification.cc similarity index 67% rename from src/line_change_watcher.cc rename to src/line_modification.cc index 2e7ba879..5da287fc 100644 --- a/src/line_change_watcher.cc +++ b/src/line_modification.cc @@ -1,13 +1,54 @@ -#include "line_change_watcher.hh" +#include "line_modification.hh" + +#include "buffer.hh" namespace Kakoune { -std::vector LineChangeWatcher::compute_modifications() +namespace +{ + +struct LineChange +{ + LineChange(const Buffer::Change& change) + { + ByteCoord begin = change.begin; + ByteCoord end = change.end; + if (change.type == Buffer::Change::Insert) + { + if (change.at_end and begin != ByteCoord{0,0}) + { + kak_assert(begin.column == 0); + --begin.line; + } + pos = begin.line; + num = end.line - begin.line; + } + else + { + if (change.at_end and begin != ByteCoord{0,0}) + { + kak_assert(begin.column == 0); + --begin.line; + } + pos = begin.line; + num = begin.line - end.line; + } + } + + LineCount pos; + LineCount num; +}; + +} + +std::vector compute_line_modifications(const Buffer& buffer, size_t timestamp) { std::vector res; - for (auto& change : m_changes) + for (auto& buf_change : buffer.changes_since(timestamp)) { + const LineChange change(buf_change); + auto pos = std::upper_bound(res.begin(), res.end(), change.pos, [](const LineCount& l, const LineModification& c) { return l < c.new_line; }); @@ -58,28 +99,7 @@ std::vector LineChangeWatcher::compute_modifications() it->new_line -= num_removed; } } - m_changes.clear(); return res; } -void LineChangeWatcher::on_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end) -{ - if (buffer.is_end(end)) - { - kak_assert(begin.column == 0); - --begin.line; - } - m_changes.push_back({begin.line, end.line - begin.line}); -} - -void LineChangeWatcher::on_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end) -{ - if (begin.line == buffer.line_count()) - { - kak_assert(begin.column == 0); - --begin.line; - } - m_changes.push_back({begin.line, begin.line - end.line}); -} - } diff --git a/src/line_modification.hh b/src/line_modification.hh new file mode 100644 index 00000000..3cd2d669 --- /dev/null +++ b/src/line_modification.hh @@ -0,0 +1,26 @@ +#ifndef line_change_watcher_hh_INCLUDED +#define line_change_watcher_hh_INCLUDED + +#include "units.hh" +#include "utils.hh" + +namespace Kakoune +{ + +class Buffer; + +struct LineModification +{ + LineCount old_line; // line position in the old buffer + LineCount new_line; // new line position + LineCount num_removed; // number of lines removed after this one + LineCount num_added; // number of lines added after this one + + LineCount diff() const { return new_line - old_line + num_added - num_removed; } +}; + +std::vector compute_line_modifications(const Buffer& buffer, size_t timestamp); + +} + +#endif // line_change_watcher_hh_INCLUDED diff --git a/src/normal.cc b/src/normal.cc index d176f527..0e4498c5 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -21,89 +21,6 @@ namespace Kakoune { -void erase(Buffer& buffer, SelectionList& selections) -{ - for (auto& sel : selections) - { - erase(buffer, sel); - avoid_eol(buffer, sel); - } - selections.check_invariant(); - buffer.check_invariant(); -} - -template -BufferIterator prepare_insert(Buffer& buffer, const Selection& sel) -{ - switch (mode) - { - case InsertMode::Insert: - return buffer.iterator_at(sel.min()); - case InsertMode::Replace: - return Kakoune::erase(buffer, sel); - case InsertMode::Append: - { - // special case for end of lines, append to current line instead - auto pos = buffer.iterator_at(sel.max()); - return *pos == '\n' ? pos : utf8::next(pos); - } - case InsertMode::InsertAtLineBegin: - return buffer.iterator_at(sel.min().line); - case InsertMode::AppendAtLineEnd: - return buffer.iterator_at({sel.max().line, buffer[sel.max().line].length() - 1}); - case InsertMode::InsertAtNextLineBegin: - return buffer.iterator_at(sel.max().line+1); - case InsertMode::OpenLineBelow: - return buffer.insert(buffer.iterator_at(sel.max().line + 1), "\n"); - case InsertMode::OpenLineAbove: - return buffer.insert(buffer.iterator_at(sel.min().line), "\n"); - } - kak_assert(false); - return {}; -} - -template -void insert(Buffer& buffer, SelectionList& selections, const String& str) -{ - for (auto& sel : selections) - { - auto pos = prepare_insert(buffer, sel); - pos = buffer.insert(pos, str); - if (mode == InsertMode::Replace and pos != buffer.end()) - { - sel.anchor() = pos.coord(); - sel.cursor() = str.empty() ? - pos.coord() : (pos + str.byte_count_to(str.char_length() - 1)).coord(); - } - avoid_eol(buffer, sel); - } - selections.check_invariant(); - buffer.check_invariant(); -} - -template -void insert(Buffer& buffer, SelectionList& selections, memoryview strings) -{ - if (strings.empty()) - return; - for (size_t i = 0; i < selections.size(); ++i) - { - auto& sel = selections[i]; - auto pos = prepare_insert(buffer, sel); - const String& str = strings[std::min(i, strings.size()-1)]; - pos = buffer.insert(pos, str); - if (mode == InsertMode::Replace and pos != buffer.end()) - { - sel.anchor() = pos.coord(); - sel.cursor() = (str.empty() ? - pos : pos + str.byte_count_to(str.char_length() - 1)).coord(); - } - avoid_eol(buffer, sel); - } - selections.check_invariant(); - buffer.check_invariant(); -} - using namespace std::placeholders; enum class SelectMode @@ -174,11 +91,11 @@ constexpr Select make_select(T func) } template -void select_coord(const Buffer& buffer, ByteCoord coord, SelectionList& selections) +void select_coord(Buffer& buffer, ByteCoord coord, SelectionList& selections) { coord = buffer.clamp(coord); if (mode == SelectMode::Replace) - selections = SelectionList { coord }; + selections = SelectionList{ buffer, coord }; else if (mode == SelectMode::Extend) { for (auto& sel : selections) @@ -407,7 +324,7 @@ void replace_with_char(Context& context, int) CharCount count = char_length(buffer, sel); strings.emplace_back(key.key, count); } - insert(buffer, selections, strings); + selections.insert(strings, InsertMode::Replace); }, "replace with char", "enter char to replace with\n"); } @@ -430,7 +347,7 @@ void for_each_char(Context& context, int) for (auto& c : sel) c = func(c); } - insert(context.buffer(), context.selections(), sels); + context.selections().insert(sels, InsertMode::Replace); } void command(Context& context, int) @@ -495,7 +412,7 @@ void pipe(Context& context, int) strings.push_back(str); } ScopedEdition edition(context); - insert(buffer, selections, strings); + selections.insert(strings, mode); }); } @@ -545,7 +462,8 @@ void erase_selections(Context& context, int) { RegisterManager::instance()['"'] = context.selections_content(); ScopedEdition edition(context); - erase(context.buffer(), context.selections()); + context.selections().erase(); + context.selections().avoid_eol(); } void cat_erase_selections(Context& context, int) @@ -555,7 +473,8 @@ void cat_erase_selections(Context& context, int) for (auto& sel : sels) str += sel; RegisterManager::instance()['"'] = memoryview(str); - erase(context.buffer(), context.selections()); + context.selections().erase(); + context.selections().avoid_eol(); } @@ -589,22 +508,21 @@ void paste(Context& context, int) } } ScopedEdition edition(context); - if (linewise) - insert(context.buffer(), context.selections(), strings); - else - insert(context.buffer(), context.selections(), strings); + context.selections().insert(strings, + linewise ? adapt_for_linewise(mode) : mode); } template void regex_prompt(Context& context, const String prompt, T func) { - DynamicSelectionList selections{context.buffer(), context.selections()}; + SelectionList selections = context.selections(); context.input_handler().prompt(prompt, "", get_color("Prompt"), complete_nothing, - [=](const String& str, PromptEvent event, Context& context) { + [=](const String& str, PromptEvent event, Context& context) mutable { try { if (event != PromptEvent::Change and context.has_ui()) context.ui().info_hide(); + selections.update(); context.selections() = selections; context.input_handler().set_prompt_colors(get_color("Prompt")); if (event == PromptEvent::Abort) @@ -718,7 +636,7 @@ void select_regex(Context& context, int) else RegisterManager::instance()['/'] = String{ex.str()}; if (not ex.empty() and not ex.str().empty()) - select_all_matches(context.buffer(), context.selections(), ex); + select_all_matches(context.selections(), ex); }); } @@ -730,7 +648,7 @@ void split_regex(Context& context, int) else RegisterManager::instance()['/'] = String{ex.str()}; if (not ex.empty() and not ex.str().empty()) - split_selections(context.buffer(), context.selections(), ex); + split_selections(context.selections(), ex); }); } @@ -738,7 +656,7 @@ void split_lines(Context& context, int) { auto& selections = context.selections(); auto& buffer = context.buffer(); - SelectionList res; + std::vector res; for (auto& sel : selections) { if (sel.anchor().line == sel.cursor().line) @@ -753,14 +671,13 @@ void split_lines(Context& context, int) res.push_back({line, {line, buffer[line].length()-1}}); res.push_back({max.line, max}); } - res.set_main_index(res.size() - 1); selections = std::move(res); } void join_select_spaces(Context& context, int) { auto& buffer = context.buffer(); - SelectionList selections; + std::vector selections; for (auto& sel : context.selections()) { for (LineCount line = sel.min().line; line <= sel.max().line; ++line) @@ -775,16 +692,19 @@ void join_select_spaces(Context& context, int) } if (selections.empty()) return; - selections.sort_and_merge_overlapping(); context.selections() = selections; ScopedEdition edition(context); - insert(buffer, context.selections(), " "); + context.selections().insert(" "_str, InsertMode::Replace); } void join(Context& context, int param) { - DynamicSelectionList sels{context.buffer(), context.selections()}; - auto restore_sels = on_scope_end([&]{ context.selections() = std::move(sels); }); + SelectionList sels{context.selections()}; + auto restore_sels = on_scope_end([&]{ + sels.update(); + context.selections() = std::move(sels); + }); + join_select_spaces(context, param); } @@ -796,7 +716,7 @@ void keep(Context& context, int) if (ex.empty()) return; const Buffer& buffer = context.buffer(); - SelectionList keep; + std::vector keep; for (auto& sel : context.selections()) { if (boost::regex_search(buffer.iterator_at(sel.min()), @@ -805,7 +725,7 @@ void keep(Context& context, int) } if (keep.empty()) throw runtime_error("no selections remaining"); - context.selections() = std::move(keep); + context.set_selections(std::move(keep)); }); } @@ -818,7 +738,7 @@ void keep_pipe(Context& context, int) return; const Buffer& buffer = context.buffer(); auto& shell_manager = ShellManager::instance(); - SelectionList keep; + std::vector keep; for (auto& sel : context.selections()) { int status = 0; @@ -829,7 +749,7 @@ void keep_pipe(Context& context, int) } if (keep.empty()) throw runtime_error("no selections remaining"); - context.selections() = std::move(keep); + context.set_selections(std::move(keep)); }); } template @@ -839,7 +759,7 @@ void indent(Context& context, int) String indent = indent_width == 0 ? "\t" : String{' ', indent_width}; auto& buffer = context.buffer(); - SelectionList sels; + std::vector sels; for (auto& sel : context.selections()) { for (auto line = sel.min().line; line < sel.max().line+1; ++line) @@ -851,7 +771,8 @@ void indent(Context& context, int) if (not sels.empty()) { ScopedEdition edition(context); - insert(buffer, sels, indent); + SelectionList selections{buffer, std::move(sels)}; + selections.insert(indent, InsertMode::Insert); } } @@ -864,7 +785,7 @@ void deindent(Context& context, int) indent_width = tabstop; auto& buffer = context.buffer(); - SelectionList sels; + std::vector sels; for (auto& sel : context.selections()) { for (auto line = sel.min().line; line < sel.max().line+1; ++line) @@ -895,7 +816,8 @@ void deindent(Context& context, int) if (not sels.empty()) { ScopedEdition edition(context); - erase(context.buffer(), sels); + SelectionList selections{context.buffer(), std::move(sels)}; + selections.erase(); } } @@ -1006,7 +928,7 @@ void rotate_selections_content(Context& context, int group) std::rotate(it, end-count, end); it = end; } - insert(context.buffer(), context.selections(), strings); + context.selections().insert(strings, InsertMode::Replace); context.selections().rotate_main(count); } @@ -1238,52 +1160,16 @@ void spaces_to_tabs(Context& context, int ts) } } -class ModifiedRangesListener : public BufferChangeListener_AutoRegister -{ -public: - ModifiedRangesListener(Buffer& buffer) - : BufferChangeListener_AutoRegister(buffer) {} - - void on_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end) - { - m_ranges.update_insert(buffer, begin, end); - auto it = std::upper_bound(m_ranges.begin(), m_ranges.end(), begin, - [](ByteCoord c, const Selection& sel) - { return c < sel.min(); }); - m_ranges.insert(it, Selection{ begin, buffer.char_prev(end) }); - } - - void on_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end) - { - m_ranges.update_erase(buffer, begin, end); - auto pos = std::min(begin, buffer.back_coord()); - auto it = std::upper_bound(m_ranges.begin(), m_ranges.end(), pos, - [](ByteCoord c, const Selection& sel) - { return c < sel.min(); }); - m_ranges.insert(it, Selection{ pos, pos }); - } - SelectionList& ranges() { return m_ranges; } - -private: - SelectionList m_ranges; -}; - -inline bool touches(const Buffer& buffer, const Selection& lhs, const Selection& rhs) -{ - return lhs.min() <= rhs.min() ? buffer.char_next(lhs.max()) >= rhs.min() - : lhs.min() <= buffer.char_next(rhs.max()); -} - void undo(Context& context, int) { - ModifiedRangesListener listener(context.buffer()); - bool res = context.buffer().undo(); - if (res and not listener.ranges().empty()) + Buffer& buffer = context.buffer(); + size_t timestamp = buffer.timestamp(); + bool res = buffer.undo(); + if (res) { - auto& selections = context.selections(); - selections = std::move(listener.ranges()); - selections.set_main_index(selections.size() - 1); - selections.merge_overlapping(std::bind(touches, std::ref(context.buffer()), _1, _2)); + auto ranges = compute_modified_ranges(buffer, timestamp); + if (not ranges.empty()) + context.set_selections(std::move(ranges)); } else if (not res) context.print_status({ "nothing left to undo", get_color("Information") }); @@ -1292,15 +1178,16 @@ void undo(Context& context, int) void redo(Context& context, int) { using namespace std::placeholders; - ModifiedRangesListener listener(context.buffer()); - bool res = context.buffer().redo(); - if (res and not listener.ranges().empty()) + Buffer& buffer = context.buffer(); + size_t timestamp = buffer.timestamp(); + bool res = buffer.redo(); + if (res) { - auto& selections = context.selections(); - selections = std::move(listener.ranges()); - selections.set_main_index(selections.size() - 1); - selections.merge_overlapping(std::bind(touches, std::ref(context.buffer()), _1, _2)); + auto ranges = compute_modified_ranges(buffer, timestamp); + if (not ranges.empty()) + context.set_selections(std::move(ranges)); } + else if (not res) context.print_status({ "nothing left to redo", get_color("Information") }); } @@ -1337,9 +1224,9 @@ void move(Context& context, int count) : context.buffer().offset_coord(sel.cursor(), offset); sel.anchor() = mode == SelectMode::Extend ? sel.anchor() : cursor; - sel.cursor() = cursor; - avoid_eol(context.buffer(), sel); + sel.cursor() = cursor; } + selections.avoid_eol(); selections.sort_and_merge_overlapping(); } @@ -1392,12 +1279,12 @@ KeyMap keymap = { '.', repeat_last_insert }, - { '%', [](Context& context, int) { select_buffer(context.buffer(), context.selections()); } }, + { '%', [](Context& context, int) { select_buffer(context.selections()); } }, { ':', command }, { '|', pipe }, { alt('|'), pipe }, - { ' ', [](Context& context, int count) { if (count == 0) clear_selections(context.buffer(), context.selections()); + { ' ', [](Context& context, int count) { if (count == 0) clear_selections(context.selections()); else keep_selection(context.selections(), count-1); } }, { alt(' '), [](Context& context, int count) { if (count == 0) flip_selections(context.selections()); else remove_selection(context.selections(), count-1); } }, diff --git a/src/normal.hh b/src/normal.hh index e72d0d20..c35027ce 100644 --- a/src/normal.hh +++ b/src/normal.hh @@ -11,18 +11,6 @@ namespace Kakoune class Context; -enum class InsertMode : unsigned -{ - Insert, - Append, - Replace, - InsertAtLineBegin, - InsertAtNextLineBegin, - AppendAtLineEnd, - OpenLineBelow, - OpenLineAbove -}; - using KeyMap = std::unordered_map>; extern KeyMap keymap; diff --git a/src/selection.cc b/src/selection.cc index e1627a0c..95b4e344 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -1,6 +1,7 @@ #include "selection.hh" #include "utf8.hh" +#include "buffer_utils.hh" namespace Kakoune { @@ -14,101 +15,518 @@ void Selection::merge_with(const Selection& range) m_anchor = std::max(m_anchor, range.m_anchor); } +SelectionList::SelectionList(Buffer& buffer, Selection s, size_t timestamp) + : m_buffer(&buffer), m_selections({ s }), m_timestamp(timestamp) +{ + check_invariant(); +} + +SelectionList::SelectionList(Buffer& buffer, Selection s) + : SelectionList(buffer, s, buffer.timestamp()) +{} + +SelectionList::SelectionList(Buffer& buffer, std::vector s, size_t timestamp) + : m_buffer(&buffer), m_selections(std::move(s)), m_timestamp(timestamp) +{ + kak_assert(size() > 0); + check_invariant(); +} + +SelectionList::SelectionList(Buffer& buffer, std::vector s) + : SelectionList(buffer, std::move(s), buffer.timestamp()) +{} + namespace { -template