kakoune/src/changes.hh
Maxime Coste 2688893156 Fix pasting after when selections are overlapping
With overlapping selections, pasting after breaks assumption of
SelectionList::for_each as our changes are no longer happening in
increasing locations.

We hence cannot rely on the ForwardChangeTracker in that case and
have to rely on the more general (and more costly) ranges update logic.

This interacts poorly with paste linewise pastes and we try to preserve
the current behaviour by tracking the last paste position.

Overall, this change really begs for overlapping selections to be
removed, but we will fix them like that for now.

Fixes #4779
2022-11-28 20:27:44 +11:00

112 lines
3.8 KiB
C++

#ifndef changes_hh_INCLUDED
#define changes_hh_INCLUDED
#include "buffer.hh"
#include "coord.hh"
namespace Kakoune
{
// 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 ForwardChangesTracker
{
BufferCoord cur_pos; // last change position at current modification
BufferCoord old_pos; // last change position at start
void update(const Buffer::Change& change);
void update(const Buffer& buffer, size_t& timestamp);
BufferCoord get_old_coord(BufferCoord coord) const;
BufferCoord get_new_coord(BufferCoord coord) const;
BufferCoord get_new_coord_tolerant(BufferCoord coord) const;
bool relevant(const Buffer::Change& change, BufferCoord old_coord) const;
};
const Buffer::Change* forward_sorted_until(const Buffer::Change* first, const Buffer::Change* last);
const Buffer::Change* backward_sorted_until(const Buffer::Change* first, const Buffer::Change* last);
template<typename Range, typename AdvanceFunc>
auto update_range(ForwardChangesTracker& changes_tracker, Range& range, AdvanceFunc&& advance_while_relevant)
{
auto& first = get_first(range);
auto& last = get_last(range);
advance_while_relevant(first);
first = changes_tracker.get_new_coord_tolerant(first);
if (last < BufferCoord{0,0})
return;
advance_while_relevant(last);
last = changes_tracker.get_new_coord_tolerant(last);
}
template<typename RangeContainer>
void update_forward(ConstArrayView<Buffer::Change> changes, RangeContainer& ranges)
{
ForwardChangesTracker changes_tracker;
auto advance_while_relevant = [&, it = changes.begin()]
(const BufferCoord& pos) mutable {
while (it != changes.end() and changes_tracker.relevant(*it, pos))
changes_tracker.update(*it++);
};
auto range_it = std::lower_bound(ranges.begin(), ranges.end(), changes.front(),
[](auto& range, const Buffer::Change& change) { return get_last(range) < change.begin; });
for (auto end = ranges.end(); range_it != end; ++range_it)
update_range(changes_tracker, *range_it, advance_while_relevant);
}
template<typename RangeContainer>
void update_backward(ConstArrayView<Buffer::Change> changes, RangeContainer& ranges)
{
ForwardChangesTracker changes_tracker;
auto advance_while_relevant = [&, it = changes.rbegin()]
(const BufferCoord& pos) mutable {
while (it != changes.rend())
{
const Buffer::Change change{it->type,
changes_tracker.get_new_coord(it->begin),
changes_tracker.get_new_coord(it->end)};
if (not changes_tracker.relevant(change, pos))
break;
changes_tracker.update(change);
++it;
}
};
for (auto& range : ranges)
update_range(changes_tracker, range, advance_while_relevant);
}
template<typename RangeContainer>
void update_ranges(Buffer& buffer, size_t timestamp, RangeContainer&& ranges)
{
if (timestamp == buffer.timestamp())
return;
auto changes = buffer.changes_since(timestamp);
for (auto change_it = changes.begin(); change_it != changes.end(); )
{
auto forward_end = forward_sorted_until(change_it, changes.end());
auto backward_end = backward_sorted_until(change_it, changes.end());
if (forward_end >= backward_end)
{
update_forward({ change_it, forward_end }, ranges);
change_it = forward_end;
}
else
{
update_backward({ change_it, backward_end }, ranges);
change_it = backward_end;
}
}
}
}
#endif // changes_hh_INCLUDED