Go back to Buffer::Change based implementation for SelectionList::update

However take into account the ordering of selections in insert and erase
methods, so that we update selection position cheaply.
This commit is contained in:
Maxime Coste 2014-05-26 20:57:10 +01:00
parent 51eae8026b
commit f49bec8021
3 changed files with 154 additions and 134 deletions

View File

@ -1163,7 +1163,7 @@ static boost::optional<SelectionList> compute_modified_ranges(Buffer& buffer, si
const ByteCoord& end = change.end; const ByteCoord& end = change.end;
if (change.type == Buffer::Change::Insert) if (change.type == Buffer::Change::Insert)
{ {
update_insert(ranges, begin, end, change.at_end); update_insert(ranges, begin, end);
auto it = std::upper_bound(ranges.begin(), ranges.end(), begin, auto it = std::upper_bound(ranges.begin(), ranges.end(), begin,
[](ByteCoord c, const Selection& sel) [](ByteCoord c, const Selection& sel)
{ return c < sel.min(); }); { return c < sel.min(); });
@ -1171,7 +1171,7 @@ static boost::optional<SelectionList> compute_modified_ranges(Buffer& buffer, si
} }
else else
{ {
update_erase(ranges, begin, end, change.at_end); update_erase(ranges, begin, end);
auto pos = begin; auto pos = begin;
if (change.at_end) if (change.at_end)
pos = begin.column ? ByteCoord{begin.line, begin.column - 1} pos = begin.column ? ByteCoord{begin.line, begin.column - 1}
@ -1185,6 +1185,12 @@ static boost::optional<SelectionList> compute_modified_ranges(Buffer& buffer, si
if (ranges.empty()) if (ranges.empty())
return {}; return {};
for (auto& sel : ranges)
{
sel.anchor() = buffer.clamp(sel.anchor());
sel.cursor() = buffer.clamp(sel.cursor());
}
SelectionList result{buffer, std::move(ranges)}; SelectionList result{buffer, std::move(ranges)};
auto touches = [&](const Selection& lhs, const Selection& rhs) { auto touches = [&](const Selection& lhs, const Selection& rhs) {

View File

@ -1,7 +1,6 @@
#include "selection.hh" #include "selection.hh"
#include "utf8.hh" #include "utf8.hh"
#include "modification.hh"
#include "buffer_utils.hh" #include "buffer_utils.hh"
namespace Kakoune namespace Kakoune
@ -16,93 +15,6 @@ void Selection::merge_with(const Selection& range)
m_anchor = std::max(m_anchor, range.m_anchor); m_anchor = std::max(m_anchor, range.m_anchor);
} }
namespace
{
template<template <bool, bool> class UpdateFunc>
void on_buffer_change(std::vector<Selection>& sels,
ByteCoord begin, ByteCoord end, bool at_end, LineCount end_line)
{
auto update_beg = std::lower_bound(sels.begin(), sels.end(), begin,
[](const Selection& s, ByteCoord c)
{ return s.max() < c; });
auto update_only_line_beg = std::upper_bound(sels.begin(), sels.end(), end_line,
[](LineCount l, const Selection& s)
{ return l < s.min().line; });
if (update_beg != update_only_line_beg)
{
// for the first one, we are not sure if min < begin
UpdateFunc<false, false>{}(update_beg->anchor(), begin, end, at_end);
UpdateFunc<false, false>{}(update_beg->cursor(), begin, end, at_end);
}
for (auto it = update_beg+1; it < update_only_line_beg; ++it)
{
UpdateFunc<false, true>{}(it->anchor(), begin, end, at_end);
UpdateFunc<false, true>{}(it->cursor(), begin, end, at_end);
}
if (end.line > begin.line)
{
for (auto it = update_only_line_beg; it != sels.end(); ++it)
{
UpdateFunc<true, true>{}(it->anchor(), begin, end, at_end);
UpdateFunc<true, true>{}(it->cursor(), begin, end, at_end);
}
}
}
template<bool assume_different_line, bool assume_greater_than_begin>
struct UpdateInsert
{
void operator()(ByteCoord& coord, ByteCoord begin, ByteCoord end,
bool at_end) const
{
if (assume_different_line)
kak_assert(begin.line < coord.line);
if (not assume_greater_than_begin and coord < begin)
return;
if (not assume_different_line and begin.line == coord.line)
coord.column = end.column + coord.column - begin.column;
coord.line += end.line - begin.line;
kak_assert(coord.line >= 0 and coord.column >= 0);
}
};
template<bool assume_different_line, bool assume_greater_than_begin>
struct UpdateErase
{
void operator()(ByteCoord& coord, ByteCoord begin, ByteCoord end,
bool at_end) const
{
if (not assume_greater_than_begin and coord < begin)
return;
if (assume_different_line)
kak_assert(end.line < coord.line);
if (not assume_different_line and coord <= end)
{
if (not at_end or begin == ByteCoord{0,0})
coord = begin;
else
coord = begin.column ? ByteCoord{begin.line, begin.column-1}
: ByteCoord{begin.line - 1};
}
else
{
if (not assume_different_line and end.line == coord.line)
{
coord.line = begin.line;
coord.column = begin.column + coord.column - end.column;
}
else
coord.line -= end.line - begin.line;
}
kak_assert(coord.line >= 0 and coord.column >= 0);
}
};
}
SelectionList::SelectionList(Buffer& buffer, Selection s, size_t timestamp) SelectionList::SelectionList(Buffer& buffer, Selection s, size_t timestamp)
: m_buffer(&buffer), m_selections({ s }), m_timestamp(timestamp) : m_buffer(&buffer), m_selections({ s }), m_timestamp(timestamp)
{ {
@ -124,14 +36,41 @@ SelectionList::SelectionList(Buffer& buffer, std::vector<Selection> s)
: SelectionList(buffer, std::move(s), buffer.timestamp()) : SelectionList(buffer, std::move(s), buffer.timestamp())
{} {}
void update_insert(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end, bool at_end) namespace
{ {
on_buffer_change<UpdateInsert>(sels, begin, end, at_end, begin.line);
ByteCoord update_insert(ByteCoord coord, ByteCoord begin, ByteCoord end)
{
if (coord < begin)
return coord;
if (begin.line == coord.line)
coord.column += end.column - begin.column;
coord.line += end.line - begin.line;
kak_assert(coord.line >= 0 and coord.column >= 0);
return coord;
} }
void update_erase(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end, bool at_end) ByteCoord update_erase(ByteCoord coord, ByteCoord begin, ByteCoord end)
{ {
on_buffer_change<UpdateErase>(sels, begin, end, at_end, end.line); if (coord < begin)
return coord;
if (coord <= end)
return begin;
if (end.line == coord.line)
coord.column -= end.column - begin.column;
coord.line -= end.line - begin.line;
kak_assert(coord.line >= 0 and coord.column >= 0);
return coord;
}
ByteCoord update_pos(ByteCoord coord, const Buffer::Change& change)
{
if (change.type == Buffer::Change::Insert)
return update_insert(coord, change.begin, change.end);
else
return update_erase(coord, change.begin, change.end);
}
} }
void SelectionList::update() void SelectionList::update()
@ -139,18 +78,19 @@ void SelectionList::update()
if (m_timestamp == m_buffer->timestamp()) if (m_timestamp == m_buffer->timestamp())
return; return;
auto modifs = compute_modifications(*m_buffer, m_timestamp); for (auto& change : m_buffer->changes_since(m_timestamp))
{
for (auto& sel : m_selections) for (auto& sel : m_selections)
{ {
auto anchor = update_pos(modifs, sel.anchor()); sel.anchor() = update_pos(sel.anchor(), change);
kak_assert(m_buffer->is_valid(anchor)); sel.cursor() = update_pos(sel.cursor(), change);
sel.anchor() = m_buffer->clamp(anchor); }
}
auto cursor = update_pos(modifs, sel.cursor()); for (auto& sel : m_selections)
kak_assert(m_buffer->is_valid(cursor)); {
sel.cursor() = m_buffer->clamp(cursor); sel.anchor() = m_buffer->clamp(sel.anchor());
sel.cursor() = m_buffer->clamp(sel.cursor());
} }
merge_overlapping(overlaps); merge_overlapping(overlaps);
check_invariant(); check_invariant();
@ -222,17 +162,6 @@ void SelectionList::avoid_eol()
_avoid_eol(buffer(), sel); _avoid_eol(buffer(), sel);
} }
void SelectionList::erase()
{
for (auto& sel : reversed(m_selections))
{
Kakoune::erase(*m_buffer, sel);
}
update();
avoid_eol();
m_buffer->check_invariant();
}
BufferIterator prepare_insert(Buffer& buffer, const Selection& sel, InsertMode mode) BufferIterator prepare_insert(Buffer& buffer, const Selection& sel, InsertMode mode)
{ {
switch (mode) switch (mode)
@ -264,42 +193,127 @@ BufferIterator prepare_insert(Buffer& buffer, const Selection& sel, InsertMode m
return {}; return {};
} }
// This tracks position changes for changes that are done
// in a forward way (each change takes place at a position)
// *after* the previous one.
struct PositionChangesTracker
{
ByteCoord last_pos;
ByteCoord pos_change;
void update(const Buffer::Change& change)
{
if (change.type == Buffer::Change::Insert)
{
pos_change.line += change.end.line - change.begin.line;
if (change.begin.line != last_pos.line)
pos_change.column = 0;
pos_change.column += change.end.column - change.begin.column;
last_pos = change.end;
}
else if (change.type == Buffer::Change::Erase)
{
pos_change.line -= change.end.line - change.begin.line;
if (last_pos.line != change.end.line)
pos_change.column = 0;
pos_change.column -= change.end.column - change.begin.column;
last_pos = change.begin;
}
}
void update(const Buffer& buffer, size_t& timestamp)
{
for (auto& change : buffer.changes_since(timestamp))
update(change);
timestamp = buffer.timestamp();
}
ByteCoord get_new_coord(ByteCoord coord)
{
if (last_pos.line - pos_change.line == coord.line)
coord.column += pos_change.column;
coord.line += pos_change.line;
return coord;
}
void update_sel(Selection& sel)
{
sel.anchor() = get_new_coord(sel.anchor());
sel.cursor() = get_new_coord(sel.cursor());
}
};
void SelectionList::insert(memoryview<String> strings, InsertMode mode) void SelectionList::insert(memoryview<String> strings, InsertMode mode)
{ {
if (strings.empty()) if (strings.empty())
return; return;
update(); update();
for (size_t i = 0; i < m_selections.size(); ++i) PositionChangesTracker changes_tracker;
for (size_t index = 0; index < m_selections.size(); ++index)
{ {
size_t index = m_selections.size() - 1 - i;
auto& sel = m_selections[index]; auto& sel = m_selections[index];
changes_tracker.update_sel(sel);
kak_assert(m_buffer->is_valid(sel.anchor()));
kak_assert(m_buffer->is_valid(sel.cursor()));
auto pos = prepare_insert(*m_buffer, sel, mode); auto pos = prepare_insert(*m_buffer, sel, mode);
changes_tracker.update(*m_buffer, m_timestamp);
const String& str = strings[std::min(index, strings.size()-1)]; const String& str = strings[std::min(index, strings.size()-1)];
pos = m_buffer->insert(pos, str); pos = m_buffer->insert(pos, str);
auto& change = m_buffer->changes_since(m_timestamp).back();
changes_tracker.update(change);
m_timestamp = m_buffer->timestamp();
if (mode == InsertMode::Replace) if (mode == InsertMode::Replace)
{ {
if (pos == m_buffer->end()) sel.anchor() = change.begin;
--pos; sel.cursor() = m_buffer->char_prev(change.end);
sel.anchor() = pos.coord(); }
sel.cursor() = (str.empty() ? else
pos : pos + str.byte_count_to(str.char_length() - 1)).coord();
// update following selections
auto changes = compute_modifications(*m_buffer, timestamp());
for (size_t j = index+1; j < m_selections.size(); ++j)
{ {
auto& sel = m_selections[j]; sel.anchor() = m_buffer->clamp(update_insert(sel.anchor(), change.begin, change.end));
sel.anchor() = update_pos(changes, sel.anchor()); sel.cursor() = m_buffer->clamp(update_insert(sel.cursor(), change.begin, change.end));
sel.cursor() = update_pos(changes, sel.cursor());
}
update_timestamp();
} }
} }
update();
check_invariant(); check_invariant();
m_buffer->check_invariant(); m_buffer->check_invariant();
} }
void SelectionList::erase()
{
update();
PositionChangesTracker changes_tracker;
for (auto& sel : m_selections)
{
changes_tracker.update_sel(sel);
auto pos = Kakoune::erase(*m_buffer, sel);
sel.anchor() = sel.cursor() = m_buffer->clamp(pos.coord());
changes_tracker.update(*m_buffer, m_timestamp);
}
avoid_eol();
m_buffer->check_invariant();
}
void update_insert(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end)
{
for (auto& sel : sels)
{
sel.anchor() = update_insert(sel.anchor(), begin, end);
sel.cursor() = update_insert(sel.cursor(), begin, end);
}
}
void update_erase(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end)
{
for (auto& sel : sels)
{
sel.anchor() = update_erase(sel.anchor(), begin, end);
sel.cursor() = update_erase(sel.cursor(), begin, end);
}
}
} }

View File

@ -165,8 +165,8 @@ private:
size_t m_timestamp; size_t m_timestamp;
}; };
void update_insert(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end, bool at_end); void update_insert(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end);
void update_erase(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end, bool at_end); void update_erase(std::vector<Selection>& sels, ByteCoord begin, ByteCoord end);
} }