Merge branch 'remove-buffer-change-listener'

This commit is contained in:
Maxime Coste 2014-06-09 23:54:37 +01:00
commit 3791e74743
32 changed files with 939 additions and 972 deletions

10
TODO
View File

@ -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 ?

View File

@ -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)

View File

@ -19,8 +19,6 @@ Buffer::Buffer(String name, Flags flags, std::vector<String> 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<String> 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<String> 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<Line> new_lines;
std::vector<String> 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());
}

View File

@ -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<BufferChangeListener*>& change_listeners() const { return m_change_listeners; }
void reload(std::vector<String> 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<Change> changes_since(size_t timestamp) const;
private:
void on_option_changed(const Option& option) override;
struct Line
struct LineList : std::vector<String>
{
size_t timestamp;
ByteCount start;
String content;
String& operator[](LineCount line)
{ return std::vector<String>::operator[]((int)line); }
ByteCount length() const { return content.length(); }
};
struct LineList : std::vector<Line>
{
Line& operator[](LineCount line)
{ return std::vector<Line>::operator[]((int)line); }
const Line& operator[](LineCount line) const
{ return std::vector<Line>::operator[]((int)line); }
const String& operator[](LineCount line) const
{ return std::vector<String>::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<Change> 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<BufferChangeListener*> 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<BufferChangeListener_AutoRegister,
BufferListenerRegisterFuncs, Buffer>
{
public:
BufferChangeListener_AutoRegister(Buffer& buffer)
: AutoRegister(buffer) {}
Buffer& buffer() const { return registry(); }
};
}
#include "buffer.inl.hh"

View File

@ -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::Change> 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

View File

@ -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);

View File

@ -18,7 +18,7 @@ Client::Client(std::unique_ptr<UserInterface>&& 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") });

View File

@ -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<Window>{new Window{buffer}},
DynamicSelectionList{buffer,
{ Selection{ {}, {} } } } };
SelectionList{ buffer, Selection{} } };
}
void ClientManager::add_free_window(std::unique_ptr<Window>&& 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)

View File

@ -12,7 +12,8 @@ struct client_removed{};
struct WindowAndSelections
{
std::unique_ptr<Window> window;
DynamicSelectionList selections;
SelectionList selections;
size_t timestamp;
};
class ClientManager : public Singleton<ClientManager>

View File

@ -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

View File

@ -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<Buffer&>((*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<Context&>(*this).selections();
}
std::vector<String> Context::selections_content() const
@ -196,6 +202,12 @@ std::vector<String> Context::selections_content() const
return contents;
}
void Context::set_selections(std::vector<Selection> sels)
{
*m_selections = std::move(sels);
(*m_selections).check_invariant();
}
void Context::begin_edition()
{
if (m_edition_level >= 0)

View File

@ -1,7 +1,7 @@
#ifndef context_hh_INCLUDED
#define context_hh_INCLUDED
#include "dynamic_selection_list.hh"
#include "selection.hh"
#include <boost/optional.hpp>
@ -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<String> selections_content() const;
void set_selections(std::vector<Selection> 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<Client> m_client;
friend class Client;
boost::optional<DynamicSelectionList> m_selections;
boost::optional<SelectionList> m_selections;
String m_name;
using JumpList = std::vector<DynamicSelectionList>;
using JumpList = std::vector<SelectionList>;
JumpList m_jump_list;
JumpList::iterator m_current_jump = m_jump_list.begin();
};

View File

@ -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);
}
}

View File

@ -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

View File

@ -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<T>();
}
private:
@ -195,7 +195,6 @@ public:
private:
struct Cache
{
Cache(const Buffer&){}
std::pair<LineCount, LineCount> m_range;
size_t m_timestamp = 0;
std::vector<std::vector<std::pair<ByteCoord, ByteCoord>>> 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<String::const_iterator> it{l.begin(), l.end(), regex}, end{}; it != end; ++it)
{

View File

@ -675,21 +675,23 @@ public:
}
else if (key == Key::Backspace)
{
std::vector<Selection> 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<Selection> 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<String> 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))
{
}

View File

@ -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

View File

@ -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<SelectionList&>(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);

View File

@ -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&>(buffer)) {}
std::vector<LineModification> 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<Change> m_changes;
};
}
#endif // line_change_watcher_hh_INCLUDED

View File

@ -1,13 +1,54 @@
#include "line_change_watcher.hh"
#include "line_modification.hh"
#include "buffer.hh"
namespace Kakoune
{
std::vector<LineModification> 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<LineModification> compute_line_modifications(const Buffer& buffer, size_t timestamp)
{
std::vector<LineModification> 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<LineModification> 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});
}
}

26
src/line_modification.hh Normal file
View File

@ -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<LineModification> compute_line_modifications(const Buffer& buffer, size_t timestamp);
}
#endif // line_change_watcher_hh_INCLUDED

View File

@ -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<InsertMode mode>
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<InsertMode mode>
void insert(Buffer& buffer, SelectionList& selections, const String& str)
{
for (auto& sel : selections)
{
auto pos = prepare_insert<mode>(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<InsertMode mode>
void insert(Buffer& buffer, SelectionList& selections, memoryview<String> strings)
{
if (strings.empty())
return;
for (size_t i = 0; i < selections.size(); ++i)
{
auto& sel = selections[i];
auto pos = prepare_insert<mode>(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<mode, T> make_select(T func)
}
template<SelectMode mode = SelectMode::Replace>
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<InsertMode::Replace>(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<InsertMode::Replace>(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<mode>(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<String>(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<adapt_for_linewise(mode)>(context.buffer(), context.selections(), strings);
else
insert<mode>(context.buffer(), context.selections(), strings);
context.selections().insert(strings,
linewise ? adapt_for_linewise(mode) : mode);
}
template<typename T>
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<Selection> 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<Selection> 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<InsertMode::Replace>(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<Selection> 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<Selection> 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<bool indent_empty = false>
@ -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<Selection> 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<InsertMode::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<Selection> 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<InsertMode::Replace>(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<InsertMode::Replace> },
{ alt('|'), pipe<InsertMode::Append> },
{ ' ', [](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); } },

View File

@ -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<Key, std::function<void (Context& context, int param)>>;
extern KeyMap keymap;

View File

@ -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<Selection> 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<Selection> s)
: SelectionList(buffer, std::move(s), buffer.timestamp())
{}
namespace
{
template<template <bool, bool> class UpdateFunc>
void on_buffer_change(const Buffer& buffer, SelectionList& sels,
ByteCoord begin, ByteCoord end, LineCount end_line)
ByteCoord update_insert(ByteCoord coord, ByteCoord begin, ByteCoord end)
{
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>{}(buffer, update_beg->anchor(), begin, end);
UpdateFunc<false, false>{}(buffer, update_beg->cursor(), begin, end);
}
for (auto it = update_beg+1; it < update_only_line_beg; ++it)
{
UpdateFunc<false, true>{}(buffer, it->anchor(), begin, end);
UpdateFunc<false, true>{}(buffer, it->cursor(), begin, end);
}
if (end.line > begin.line)
{
for (auto it = update_only_line_beg; it != sels.end(); ++it)
{
UpdateFunc<true, true>{}(buffer, it->anchor(), begin, end);
UpdateFunc<true, true>{}(buffer, it->cursor(), begin, 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;
}
template<bool assume_different_line, bool assume_greater_than_begin>
struct UpdateInsert
ByteCoord update_erase(ByteCoord coord, ByteCoord begin, ByteCoord end)
{
void operator()(const Buffer& buffer, ByteCoord& coord,
ByteCoord begin, ByteCoord 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;
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;
}
coord.line += end.line - begin.line;
}
};
template<bool assume_different_line, bool assume_greater_than_begin>
struct UpdateErase
static bool compare_selections(const Selection& lhs, const Selection& rhs)
{
void operator()(const Buffer& buffer, ByteCoord& coord,
ByteCoord begin, ByteCoord end) const
return lhs.min() < rhs.min();
}
template<typename Iterator, typename OverlapsFunc>
Iterator merge_overlapping(Iterator begin, Iterator end, size_t& main, OverlapsFunc overlaps)
{
if (begin == end)
return begin;
kak_assert(std::is_sorted(begin, end, compare_selections));
size_t size = end - begin;
size_t i = 0;
for (size_t j = 1; j < size; ++j)
{
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)
coord = buffer.clamp(begin);
if (overlaps(begin[i], begin[j]))
{
begin[i].merge_with(begin[j]);
if (i < main)
--main;
}
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;
++i;
if (i != j)
begin[i] = std::move(begin[j]);
}
}
return begin + i + 1;
}
// 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
{
ByteCoord cur_pos; // last change position at current modification
ByteCoord old_pos; // last change position at start
void update(const Buffer::Change& change)
{
kak_assert(change.begin >= cur_pos);
if (change.type == Buffer::Change::Insert)
{
old_pos = get_old_coord(change.begin);
cur_pos = change.end;
}
else if (change.type == Buffer::Change::Erase)
{
old_pos = get_old_coord(change.end);
cur_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_old_coord(ByteCoord coord) const
{
kak_assert(cur_pos <= coord);
auto pos_change = cur_pos - old_pos;
if (cur_pos.line == coord.line)
{
kak_assert(pos_change.column <= coord.column);
coord.column -= pos_change.column;
}
coord.line -= pos_change.line;
kak_assert(old_pos <= coord);
return coord;
}
ByteCoord get_new_coord(ByteCoord coord) const
{
kak_assert(old_pos <= coord);
auto pos_change = cur_pos - old_pos;
if (old_pos.line == coord.line)
{
kak_assert(-pos_change.column <= coord.column);
coord.column += pos_change.column;
}
coord.line += pos_change.line;
kak_assert(cur_pos <= coord);
return coord;
}
ByteCoord get_new_coord_tolerant(ByteCoord coord) const
{
if (coord < old_pos)
return cur_pos;
return get_new_coord(coord);
}
bool relevant(const Buffer::Change& change, ByteCoord old_coord) const
{
auto new_coord = get_new_coord(old_coord);
return change.type == Buffer::Change::Insert ? change.begin <= new_coord
: change.begin < new_coord;
}
};
const Buffer::Change* forward_sorted_until(const Buffer::Change* first, const Buffer::Change* last)
{
if (first != last) {
const Buffer::Change* next = first;
while (++next != last) {
const auto& ref = first->type == Buffer::Change::Insert ? first->end : first->begin;
if (next->begin <= ref)
return next;
first = next;
}
}
return last;
}
void SelectionList::update_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end)
const Buffer::Change* backward_sorted_until(const Buffer::Change* first, const Buffer::Change* last)
{
on_buffer_change<UpdateInsert>(buffer, *this, begin, end, begin.line);
if (first != last) {
const Buffer::Change* next = first;
while (++next != last) {
if (first->begin <= next->end)
return next;
first = next;
}
}
return last;
}
void SelectionList::update_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end)
void update_forward(memoryview<Buffer::Change> changes, std::vector<Selection>& selections, size_t& main)
{
on_buffer_change<UpdateErase>(buffer, *this, begin, end, end.line);
ForwardChangesTracker changes_tracker;
auto change_it = changes.begin();
auto advance_while_relevant = [&](const ByteCoord& pos) mutable {
while (change_it != changes.end() and changes_tracker.relevant(*change_it, pos))
changes_tracker.update(*change_it++);
};
for (auto& sel : selections)
{
auto& sel_min = sel.min();
auto& sel_max = sel.max();
advance_while_relevant(sel_min);
sel_min = changes_tracker.get_new_coord_tolerant(sel_min);
advance_while_relevant(sel_max);
sel_max = changes_tracker.get_new_coord_tolerant(sel_max);
}
selections.erase(merge_overlapping(selections.begin(), selections.end(), main, overlaps),
selections.end());
kak_assert(std::is_sorted(selections.begin(), selections.end(), compare_selections));
}
void update_backward(memoryview<Buffer::Change> changes, std::vector<Selection>& selections, size_t& main)
{
ForwardChangesTracker changes_tracker;
using ReverseIt = std::reverse_iterator<const Buffer::Change*>;
auto change_it = ReverseIt(changes.end());
auto change_end = ReverseIt(changes.begin());
auto advance_while_relevant = [&](const ByteCoord& pos) mutable {
while (change_it != change_end)
{
auto change = *change_it;
change.begin = changes_tracker.get_new_coord(change.begin);
change.end = changes_tracker.get_new_coord(change.end);
if (not changes_tracker.relevant(change, pos))
break;
changes_tracker.update(change);
++change_it;
}
};
for (auto& sel : selections)
{
auto& sel_min = sel.min();
auto& sel_max = sel.max();
advance_while_relevant(sel_min);
sel_min = changes_tracker.get_new_coord_tolerant(sel_min);
advance_while_relevant(sel_max);
sel_max = changes_tracker.get_new_coord_tolerant(sel_max);
}
selections.erase(merge_overlapping(selections.begin(), selections.end(), main, overlaps),
selections.end());
kak_assert(std::is_sorted(selections.begin(), selections.end(), compare_selections));
}
}
std::vector<Selection> compute_modified_ranges(Buffer& buffer, size_t timestamp)
{
std::vector<Selection> ranges;
auto changes = buffer.changes_since(timestamp);
auto change_it = changes.begin();
while (change_it != changes.end())
{
auto forward_end = forward_sorted_until(change_it, changes.end());
auto backward_end = backward_sorted_until(change_it, changes.end());
size_t prev_size;
size_t dummy = 0;
if (forward_end >= backward_end)
{
update_forward({ change_it, forward_end }, ranges, dummy);
prev_size = ranges.size();
ForwardChangesTracker changes_tracker;
for (; change_it != forward_end; ++change_it)
{
if (change_it->type == Buffer::Change::Insert)
ranges.push_back({ change_it->begin, change_it->end });
else
ranges.push_back({ change_it->begin });
changes_tracker.update(*change_it);
}
}
else
{
update_backward({ change_it, backward_end }, ranges, dummy);
prev_size = ranges.size();
using ReverseIt = std::reverse_iterator<const Buffer::Change*>;
ForwardChangesTracker changes_tracker;
for (ReverseIt it{backward_end}, end{change_it}; it != end; ++it)
{
auto change = *it;
change.begin = changes_tracker.get_new_coord(change.begin);
change.end = changes_tracker.get_new_coord(change.end);
if (change.type == Buffer::Change::Insert)
ranges.push_back({ change.begin, change.end });
else
ranges.push_back({ change.begin });
changes_tracker.update(change);
}
change_it = backward_end;
}
kak_assert(std::is_sorted(ranges.begin() + prev_size, ranges.end(), compare_selections));
std::inplace_merge(ranges.begin(), ranges.begin() + prev_size, ranges.end(), compare_selections);
ranges.erase(merge_overlapping(ranges.begin(), ranges.end(), dummy, overlaps), ranges.end());
}
for (auto& sel : ranges)
{
sel.anchor() = buffer.clamp(sel.anchor());
sel.cursor() = buffer.clamp(sel.cursor());
}
auto touches = [&](const Selection& lhs, const Selection& rhs) {
return buffer.char_next(lhs.max()) >= rhs.min();
};
size_t dummy = 0;
ranges.erase(merge_overlapping(ranges.begin(), ranges.end(), dummy, touches), ranges.end());
for (auto& sel : ranges)
{
if (sel.anchor() != sel.cursor())
sel.cursor() = buffer.char_prev(sel.cursor());
}
return ranges;
}
void SelectionList::update()
{
if (m_timestamp == m_buffer->timestamp())
return;
auto changes = m_buffer->changes_since(m_timestamp);
auto change_it = changes.begin();
while (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 }, m_selections, m_main);
change_it = forward_end;
}
else
{
update_backward({ change_it, backward_end }, m_selections, m_main);
change_it = backward_end;
}
kak_assert(std::is_sorted(m_selections.begin(), m_selections.end(),
compare_selections));
}
for (auto& sel : m_selections)
{
sel.anchor() = m_buffer->clamp(sel.anchor());
sel.cursor() = m_buffer->clamp(sel.cursor());
}
m_selections.erase(merge_overlapping(begin(), end(), m_main, overlaps), end());
check_invariant();
m_timestamp = m_buffer->timestamp();
}
void SelectionList::check_invariant() const
{
#ifdef KAK_DEBUG
auto& buffer = this->buffer();
kak_assert(size() > 0);
kak_assert(m_main < size());
for (size_t i = 0; i+1 < size(); ++ i)
kak_assert((*this)[i].min() <= (*this)[i+1].min());
for (size_t i = 0; i < size(); ++i)
{
auto& sel = (*this)[i];
if (i+1 < size())
kak_assert((*this)[i].min() <= (*this)[i+1].min());
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.byte_at(sel.anchor())));
kak_assert(utf8::is_character_start(buffer.byte_at(sel.cursor())));
}
#endif
}
void SelectionList::sort_and_merge_overlapping()
{
if (size() == 1)
return;
const auto& main = this->main();
const auto main_begin = main.min();
m_main = std::count_if(begin(), end(), [&](const Selection& sel) {
auto begin = sel.min();
if (begin == main_begin)
return &sel < &main;
else
return begin < main_begin;
});
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)
{
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());
}
}
void SelectionList::avoid_eol()
{
update();
for (auto& sel : m_selections)
_avoid_eol(buffer(), sel);
}
BufferIterator prepare_insert(Buffer& buffer, const Selection& sel, InsertMode mode)
{
switch (mode)
{
case InsertMode::Insert:
return buffer.iterator_at(sel.min());
case InsertMode::InsertCursor:
return buffer.iterator_at(sel.cursor());
case InsertMode::Replace:
return 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 {};
}
void SelectionList::insert(memoryview<String> strings, InsertMode mode)
{
if (strings.empty())
return;
update();
ForwardChangesTracker changes_tracker;
for (size_t index = 0; index < m_selections.size(); ++index)
{
auto& sel = m_selections[index];
sel.anchor() = changes_tracker.get_new_coord(sel.anchor());
kak_assert(m_buffer->is_valid(sel.anchor()));
sel.cursor() = changes_tracker.get_new_coord(sel.cursor());
kak_assert(m_buffer->is_valid(sel.cursor()));
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)];
if (str.empty())
{
if (mode == InsertMode::Replace)
sel.anchor() = sel.cursor() = pos.coord();
continue;
}
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)
{
sel.anchor() = change.begin;
sel.cursor() = m_buffer->char_prev(change.end);
}
else
{
sel.anchor() = m_buffer->clamp(update_insert(sel.anchor(), change.begin, change.end));
sel.cursor() = m_buffer->clamp(update_insert(sel.cursor(), change.begin, change.end));
}
}
check_invariant();
m_buffer->check_invariant();
}
void SelectionList::erase()
{
update();
ForwardChangesTracker changes_tracker;
for (auto& sel : m_selections)
{
sel.anchor() = changes_tracker.get_new_coord(sel.anchor());
kak_assert(m_buffer->is_valid(sel.anchor()));
sel.cursor() = changes_tracker.get_new_coord(sel.cursor());
kak_assert(m_buffer->is_valid(sel.cursor()));
auto pos = Kakoune::erase(*m_buffer, sel);
sel.anchor() = sel.cursor() = m_buffer->clamp(pos.coord());
changes_tracker.update(*m_buffer, m_timestamp);
}
m_buffer->check_invariant();
}
}

View File

@ -12,7 +12,7 @@ using CaptureList = std::vector<String>;
struct Selection
{
Selection() = default;
explicit Selection(ByteCoord pos) : Selection(pos,pos) {}
Selection(ByteCoord pos) : Selection(pos,pos) {}
Selection(ByteCoord anchor, ByteCoord cursor,
CaptureList captures = {})
: m_anchor{anchor}, m_cursor{cursor},
@ -34,8 +34,11 @@ struct Selection
return m_anchor == other.m_anchor and m_cursor == other.m_cursor;
}
const ByteCoord& min() const { return std::min(m_anchor, m_cursor); }
const ByteCoord& max() const { return std::max(m_anchor, m_cursor); }
const ByteCoord& min() const { return m_anchor < m_cursor ? m_anchor : m_cursor; }
const ByteCoord& max() const { return m_anchor < m_cursor ? m_cursor : m_anchor; }
ByteCoord& min() { return m_anchor < m_cursor ? m_anchor : m_cursor; }
ByteCoord& max() { return m_anchor < m_cursor ? m_cursor : m_anchor; }
private:
ByteCoord m_anchor;
@ -50,19 +53,27 @@ inline bool overlaps(const Selection& lhs, const Selection& rhs)
: lhs.min() <= rhs.max();
}
static bool compare_selections(const Selection& lhs, const Selection& rhs)
enum class InsertMode : unsigned
{
return lhs.min() < rhs.min();
}
Insert,
InsertCursor,
Append,
Replace,
InsertAtLineBegin,
InsertAtNextLineBegin,
AppendAtLineEnd,
OpenLineBelow,
OpenLineAbove
};
struct SelectionList
{
SelectionList() = default;
SelectionList(ByteCoord c) : m_selections{Selection{c,c}} {}
SelectionList(Selection s) : m_selections{s} {}
SelectionList(Buffer& buffer, Selection s);
SelectionList(Buffer& buffer, Selection s, size_t timestamp);
SelectionList(Buffer& buffer, std::vector<Selection> s);
SelectionList(Buffer& buffer, std::vector<Selection> s, size_t timestamp);
void update_insert(const Buffer& buffer, ByteCoord begin, ByteCoord end);
void update_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end);
void update();
void check_invariant() const;
@ -73,12 +84,24 @@ struct SelectionList
void rotate_main(int count) { m_main = (m_main + count) % size(); }
void avoid_eol();
void push_back(const Selection& sel) { m_selections.push_back(sel); }
void push_back(Selection&& sel) { m_selections.push_back(std::move(sel)); }
Selection& operator[](size_t i) { return m_selections[i]; }
const Selection& operator[](size_t i) const { return m_selections[i]; }
SelectionList& operator=(std::vector<Selection> list)
{
m_selections = std::move(list);
m_main = size()-1;
sort_and_merge_overlapping();
update_timestamp();
check_invariant();
return *this;
}
using iterator = std::vector<Selection>::iterator;
iterator begin() { return m_selections.begin(); }
iterator end() { return m_selections.end(); }
@ -87,71 +110,33 @@ struct SelectionList
const_iterator begin() const { return m_selections.begin(); }
const_iterator end() const { return m_selections.end(); }
template<typename... Args>
iterator insert(Args... args)
{
return m_selections.insert(std::forward<Args>(args)...);
}
template<typename... Args>
iterator erase(Args... args)
{
return m_selections.erase(std::forward<Args>(args)...);
}
void remove(size_t index) { m_selections.erase(begin() + index); }
size_t size() const { return m_selections.size(); }
bool empty() const { return m_selections.empty(); }
bool operator==(const SelectionList& other) const { return m_selections == other.m_selections; }
bool operator!=(const SelectionList& other) const { return m_selections != other.m_selections; }
bool operator==(const SelectionList& other) const { return m_buffer == other.m_buffer and m_selections == other.m_selections; }
bool operator!=(const SelectionList& other) const { return !((*this) == other); }
template<typename OverlapsFunc>
void merge_overlapping(OverlapsFunc overlaps)
{
kak_assert(std::is_sorted(begin(), end(), compare_selections));
size_t i = 0;
for (size_t j = 1; j < size(); ++j)
{
if (overlaps((*this)[i], (*this)[j]))
{
(*this)[i].merge_with((*this)[j]);
if (i < m_main)
--m_main;
}
else
{
++i;
if (i != j)
(*this)[i] = std::move((*this)[j]);
}
}
erase(begin() + i + 1, end());
kak_assert(std::is_sorted(begin(), end(), compare_selections));
}
void sort_and_merge_overlapping();
void sort_and_merge_overlapping()
{
if (size() == 1)
return;
Buffer& buffer() const { return *m_buffer; }
const auto& main = this->main();
const auto main_begin = main.min();
m_main = std::count_if(begin(), end(), [&](const Selection& sel) {
auto begin = sel.min();
if (begin == main_begin)
return &sel < &main;
else
return begin < main_begin;
});
std::stable_sort(begin(), end(), compare_selections);
merge_overlapping(overlaps);
}
size_t timestamp() const { return m_timestamp; }
void update_timestamp() { m_timestamp = m_buffer->timestamp(); }
void insert(memoryview<String> strings, InsertMode mode);
void erase();
private:
size_t m_main = 0;
std::vector<Selection> m_selections;
safe_ptr<Buffer> m_buffer;
size_t m_timestamp;
};
std::vector<Selection> compute_modified_ranges(Buffer& buffer, size_t timestamp);
}
#endif // selection_hh_INCLUDED

View File

@ -313,7 +313,7 @@ Selection select_paragraph(const Buffer& buffer, const Selection& selection, Obj
{
BufferIterator first = buffer.iterator_at(selection.cursor());
if (not (flags & ObjectFlags::ToEnd) and buffer.offset(first.coord()) > 1 and
if (not (flags & ObjectFlags::ToEnd) and first.coord() > ByteCoord{0,1} and
*(first-1) == '\n' and *(first-2) == '\n')
--first;
else if ((flags & ObjectFlags::ToEnd) and
@ -458,15 +458,16 @@ Selection trim_partial_lines(const Buffer& buffer, const Selection& selection)
return Selection(first.coord(), last.coord());
}
void select_buffer(const Buffer& buffer, SelectionList& selections)
void select_buffer(SelectionList& selections)
{
selections = SelectionList{ Selection({0,0}, buffer.back_coord()) };
auto& buffer = selections.buffer();
selections = SelectionList{ buffer, Selection({0,0}, buffer.back_coord()) };
}
void select_all_matches(const Buffer& buffer, SelectionList& selections,
const Regex& regex)
void select_all_matches(SelectionList& selections, const Regex& regex)
{
SelectionList result;
std::vector<Selection> result;
auto& buffer = selections.buffer();
for (auto& sel : selections)
{
auto sel_end = utf8::next(buffer.iterator_at(sel.max()));
@ -492,14 +493,13 @@ void select_all_matches(const Buffer& buffer, SelectionList& selections,
}
if (result.empty())
throw runtime_error("nothing selected");
result.set_main_index(result.size() - 1);
selections = std::move(result);
}
void split_selections(const Buffer& buffer, SelectionList& selections,
const Regex& regex)
void split_selections(SelectionList& selections, const Regex& regex)
{
SelectionList result;
std::vector<Selection> result;
auto& buffer = selections.buffer();
for (auto& sel : selections)
{
auto begin = buffer.iterator_at(sel.min());
@ -518,7 +518,6 @@ void split_selections(const Buffer& buffer, SelectionList& selections,
if (begin.coord() <= sel.max())
result.push_back({ begin.coord(), sel.max() });
}
result.set_main_index(result.size() - 1);
selections = std::move(result);
}

View File

@ -9,14 +9,14 @@
namespace Kakoune
{
inline void clear_selections(const Buffer& buffer, SelectionList& selections)
inline void clear_selections(SelectionList& selections)
{
auto& sel = selections.main();
auto& pos = sel.cursor();
avoid_eol(buffer, pos);
sel.anchor() = pos;
selections.avoid_eol();
selections = SelectionList{ std::move(sel) };
selections = SelectionList{ selections.buffer(), std::move(sel) };
}
inline void flip_selections(SelectionList& selections)
@ -31,7 +31,7 @@ inline void keep_selection(SelectionList& selections, int index)
if (index < selections.size())
{
size_t real_index = (index + selections.main_index() + 1) % selections.size();
selections = SelectionList{ std::move(selections[real_index]) };
selections = SelectionList{ selections.buffer(), std::move(selections[real_index]) };
}
selections.check_invariant();
}
@ -41,7 +41,7 @@ inline void remove_selection(SelectionList& selections, int index)
if (selections.size() > 1 and index < selections.size())
{
size_t real_index = (index + selections.main_index() + 1) % selections.size();
selections.erase(selections.begin() + real_index);
selections.remove(real_index);
size_t main_index = selections.main_index();
if (real_index <= main_index)
selections.set_main_index((main_index > 0 ? main_index
@ -220,7 +220,7 @@ Selection select_lines(const Buffer& buffer, const Selection& selection);
Selection trim_partial_lines(const Buffer& buffer, const Selection& selection);
void select_buffer(const Buffer& buffer, SelectionList& selections);
void select_buffer(SelectionList& selections);
enum Direction { Forward, Backward };
@ -278,10 +278,10 @@ Selection find_next_match(const Buffer& buffer, const Selection& sel, const Rege
return {begin.coord(), end.coord(), std::move(captures)};
}
void select_all_matches(const Buffer& buffer, SelectionList& selections,
void select_all_matches(SelectionList& selections,
const Regex& regex);
void split_selections(const Buffer& buffer, SelectionList& selections,
void split_selections(SelectionList& selections,
const Regex& separator_regex);
using CodepointPair = std::pair<Codepoint, Codepoint>;

View File

@ -25,6 +25,9 @@ void test_buffer()
kak_assert(pos.coord() == ByteCoord{1 COMMA 0});
buffer.insert(pos, "tchou kanaky\n");
kak_assert(buffer.line_count() == 5);
BufferIterator pos2 = buffer.end();
pos2 -= 9;
kak_assert(*pos2 == '?');
String str = buffer.string({ 4, 1 }, buffer.next({ 4, 5 }));
kak_assert(str == "youpi");

View File

@ -76,17 +76,16 @@ CharCount distance(Iterator begin, Iterator end)
// return true if it points to the first byte of a (either single or
// multibyte) character
template<typename Iterator>
bool is_character_start(Iterator it)
inline bool is_character_start(char c)
{
return (*it & 0xC0) != 0x80;
return (c & 0xC0) != 0x80;
}
// returns an iterator to the first byte of the character it is into
template<typename Iterator>
Iterator character_start(Iterator it)
{
while (not is_character_start(it))
while (not is_character_start(*it))
--it;
return it;
}

View File

@ -23,7 +23,7 @@ Window::Window(Buffer& buffer)
m_options(buffer.options()),
m_keymaps(buffer.keymaps())
{
InputHandler hook_handler{*m_buffer, { Selection{} } };
InputHandler hook_handler{{ *m_buffer, Selection{} }};
hook_handler.context().set_window(*this);
m_hooks.run_hook("WinCreate", buffer.name(), hook_handler.context());
m_options.register_watcher(*this);
@ -38,7 +38,7 @@ Window::Window(Buffer& buffer)
Window::~Window()
{
InputHandler hook_handler{*m_buffer, { Selection{} } };
InputHandler hook_handler{{ *m_buffer, Selection{} }};
hook_handler.context().set_window(*this);
m_hooks.run_hook("WinClose", buffer().name(), hook_handler.context());
m_options.unregister_watcher(*this);
@ -262,7 +262,7 @@ ByteCoord Window::offset_coord(ByteCoord coord, LineCount offset)
lines.emplace_back(AtomList{ {buffer(), line, line+1} });
display_buffer.compute_range();
InputHandler hook_handler{*m_buffer, { Selection{} } };
InputHandler hook_handler{{ *m_buffer, Selection{} } };
hook_handler.context().set_window(*this);
m_highlighters(hook_handler.context(), HighlightFlags::MoveOnly, display_buffer);
m_builtin_highlighters(hook_handler.context(), HighlightFlags::MoveOnly, display_buffer);
@ -274,7 +274,7 @@ ByteCoord Window::offset_coord(ByteCoord coord, LineCount offset)
void Window::on_option_changed(const Option& option)
{
String desc = option.name() + "=" + option.get_as_string();
InputHandler hook_handler{*m_buffer, { Selection{} } };
InputHandler hook_handler{{ *m_buffer, Selection{} }};
hook_handler.context().set_window(*this);
m_hooks.run_hook("WinSetOption", desc, hook_handler.context());

View File

@ -1,6 +1,7 @@
#include "word_db.hh"
#include "utils.hh"
#include "line_modification.hh"
#include "utf8_iterator.hh"
namespace Kakoune
@ -49,7 +50,7 @@ static void remove_words(WordDB::WordList& wl, const std::vector<String>& words)
}
WordDB::WordDB(const Buffer& buffer)
: m_change_watcher{buffer}
: m_buffer{&buffer}, m_timestamp{buffer.timestamp()}
{
m_line_to_words.reserve((int)buffer.line_count());
for (auto line = 0_line, end = buffer.line_count(); line < end; ++line)
@ -61,11 +62,14 @@ WordDB::WordDB(const Buffer& buffer)
void WordDB::update_db()
{
auto modifs = m_change_watcher.compute_modifications();
auto& buffer = *m_buffer;
auto modifs = compute_line_modifications(buffer, m_timestamp);
m_timestamp = buffer.timestamp();
if (modifs.empty())
return;
auto& buffer = m_change_watcher.registry();
LineToWords new_lines;
new_lines.reserve((int)buffer.line_count());
@ -88,6 +92,9 @@ void WordDB::update_db()
for (auto l = 0_line; l <= modif.num_added; ++l)
{
if (modif.new_line + l >= buffer.line_count())
break;
new_lines.push_back(get_words(buffer[modif.new_line + l]));
add_words(m_words, new_lines.back());
}

View File

@ -2,7 +2,6 @@
#define word_db_hh_INCLUDED
#include "buffer.hh"
#include "line_change_watcher.hh"
#include <map>
@ -26,7 +25,8 @@ private:
void update_db();
LineChangeWatcher m_change_watcher;
safe_ptr<const Buffer> m_buffer;
size_t m_timestamp;
WordList m_words;
LineToWords m_line_to_words;
};