Merge branch 'remove-buffer-change-listener'
This commit is contained in:
commit
3791e74743
10
TODO
10
TODO
|
@ -60,16 +60,6 @@
|
||||||
the interpreters, regex for finding used kak variables (ability to
|
the interpreters, regex for finding used kak variables (ability to
|
||||||
add direct python/perl/ruby/whatever evaluation).
|
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
|
* User defined text objects
|
||||||
|
|
||||||
* multiple parameters support for normal mode commands ?
|
* multiple parameters support for normal mode commands ?
|
||||||
|
|
|
@ -105,8 +105,8 @@ def build_pretty_printer():
|
||||||
pp = gdb.printing.RegexpCollectionPrettyPrinter("kakoune")
|
pp = gdb.printing.RegexpCollectionPrettyPrinter("kakoune")
|
||||||
pp.add_printer('memoryview', '^Kakoune::memoryview<.*>$', MemoryView)
|
pp.add_printer('memoryview', '^Kakoune::memoryview<.*>$', MemoryView)
|
||||||
pp.add_printer('LineAndColumn', '^Kakoune::LineAndColumn<.*>$', LineAndColumn)
|
pp.add_printer('LineAndColumn', '^Kakoune::LineAndColumn<.*>$', LineAndColumn)
|
||||||
pp.add_printer('BufferCoord', '^Kakoune::BufferCoord$', LineAndColumn)
|
pp.add_printer('ByteCoord', '^Kakoune::ByteCoord$', LineAndColumn)
|
||||||
pp.add_printer('DisplayCoord', '^Kakoune::DisplayCoord$', LineAndColumn)
|
pp.add_printer('CharCoord', '^Kakoune::CharCoord$', LineAndColumn)
|
||||||
pp.add_printer('BufferIterator', '^Kakoune::BufferIterator$', BufferIterator)
|
pp.add_printer('BufferIterator', '^Kakoune::BufferIterator$', BufferIterator)
|
||||||
pp.add_printer('String', '^Kakoune::String$', String)
|
pp.add_printer('String', '^Kakoune::String$', String)
|
||||||
pp.add_printer('Option', '^Kakoune::Option$', Option)
|
pp.add_printer('Option', '^Kakoune::Option$', Option)
|
||||||
|
|
308
src/buffer.cc
308
src/buffer.cc
|
@ -19,8 +19,6 @@ Buffer::Buffer(String name, Flags flags, std::vector<String> lines,
|
||||||
m_flags(flags | Flags::NoUndo),
|
m_flags(flags | Flags::NoUndo),
|
||||||
m_history(), m_history_cursor(m_history.begin()),
|
m_history(), m_history_cursor(m_history.begin()),
|
||||||
m_last_save_undo_index(0),
|
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_fs_timestamp(fs_timestamp),
|
||||||
m_hooks(GlobalHooks::instance()),
|
m_hooks(GlobalHooks::instance()),
|
||||||
m_options(GlobalOptions::instance()),
|
m_options(GlobalOptions::instance()),
|
||||||
|
@ -32,15 +30,15 @@ Buffer::Buffer(String name, Flags flags, std::vector<String> lines,
|
||||||
if (lines.empty())
|
if (lines.empty())
|
||||||
lines.emplace_back("\n");
|
lines.emplace_back("\n");
|
||||||
|
|
||||||
ByteCount pos = 0;
|
|
||||||
m_lines.reserve(lines.size());
|
m_lines.reserve(lines.size());
|
||||||
for (auto& line : lines)
|
for (auto& line : lines)
|
||||||
{
|
{
|
||||||
kak_assert(not line.empty() and line.back() == '\n');
|
kak_assert(not line.empty() and line.back() == '\n');
|
||||||
m_lines.emplace_back(Line{ m_timestamp, pos, std::move(line) });
|
m_lines.emplace_back(std::move(line));
|
||||||
pos += m_lines.back().length();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_changes.push_back({ Change::Insert, {0,0}, line_count(), true });
|
||||||
|
|
||||||
if (flags & Flags::File)
|
if (flags & Flags::File)
|
||||||
{
|
{
|
||||||
if (flags & Flags::New)
|
if (flags & Flags::New)
|
||||||
|
@ -68,38 +66,30 @@ Buffer::~Buffer()
|
||||||
m_options.unregister_watcher(*this);
|
m_options.unregister_watcher(*this);
|
||||||
BufferManager::instance().unregister_buffer(*this);
|
BufferManager::instance().unregister_buffer(*this);
|
||||||
m_values.clear();
|
m_values.clear();
|
||||||
kak_assert(m_change_listeners.empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Buffer::reload(std::vector<String> lines, time_t fs_timestamp)
|
void Buffer::reload(std::vector<String> lines, time_t fs_timestamp)
|
||||||
{
|
{
|
||||||
// use back coord to simulate the persistance of the last end of line
|
m_changes.push_back({ Change::Erase, {0,0}, back_coord(), true });
|
||||||
// as buffers are expected to never be empty.
|
|
||||||
for (auto listener : m_change_listeners)
|
|
||||||
listener->on_erase(*this, {0,0}, back_coord());
|
|
||||||
|
|
||||||
m_history.clear();
|
m_history.clear();
|
||||||
m_current_undo_group.clear();
|
m_current_undo_group.clear();
|
||||||
m_history_cursor = m_history.begin();
|
m_history_cursor = m_history.begin();
|
||||||
m_last_save_undo_index = 0;
|
m_last_save_undo_index = 0;
|
||||||
m_lines.clear();
|
m_lines.clear();
|
||||||
++m_timestamp;
|
|
||||||
|
|
||||||
if (lines.empty())
|
if (lines.empty())
|
||||||
lines.emplace_back("\n");
|
lines.emplace_back("\n");
|
||||||
|
|
||||||
ByteCount pos = 0;
|
|
||||||
m_lines.reserve(lines.size());
|
m_lines.reserve(lines.size());
|
||||||
for (auto& line : lines)
|
for (auto& line : lines)
|
||||||
{
|
{
|
||||||
kak_assert(not line.empty() and line.back() == '\n');
|
kak_assert(not line.empty() and line.back() == '\n');
|
||||||
m_lines.emplace_back(Line{ m_timestamp, pos, std::move(line) });
|
m_lines.emplace_back(std::move(line));
|
||||||
pos += m_lines.back().length();
|
|
||||||
}
|
}
|
||||||
m_fs_timestamp = fs_timestamp;
|
m_fs_timestamp = fs_timestamp;
|
||||||
|
|
||||||
for (auto listener : m_change_listeners)
|
m_changes.push_back({ Change::Insert, {0,0}, back_coord(), true });
|
||||||
listener->on_insert(*this, {0,0}, back_coord());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String Buffer::display_name() const
|
String Buffer::display_name() const
|
||||||
|
@ -141,7 +131,7 @@ ByteCoord Buffer::clamp(ByteCoord coord) const
|
||||||
|
|
||||||
ByteCoord Buffer::offset_coord(ByteCoord coord, CharCount offset)
|
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,
|
auto character = std::max(0_char, std::min(line.char_count_to(coord.column) + offset,
|
||||||
line.char_length() - 1));
|
line.char_length() - 1));
|
||||||
return {coord.line, line.byte_count_to(character)};
|
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)
|
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 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));
|
character = std::max(0_char, std::min(character, content.char_length() - 2));
|
||||||
return {line, content.byte_count_to(character)};
|
return {line, content.byte_count_to(character)};
|
||||||
|
@ -168,7 +158,7 @@ String Buffer::string(ByteCoord begin, ByteCoord end) const
|
||||||
ByteCount count = -1;
|
ByteCount count = -1;
|
||||||
if (line == end.line)
|
if (line == end.line)
|
||||||
count = end.column - start;
|
count = end.column - start;
|
||||||
res += m_lines[line].content.substr(start, count);
|
res += m_lines[line].substr(start, count);
|
||||||
}
|
}
|
||||||
return res;
|
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()
|
void Buffer::commit_undo_group()
|
||||||
{
|
{
|
||||||
if (m_flags & Flags::NoUndo)
|
if (m_flags & Flags::NoUndo)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UndoGroupOptimizer::optimize(m_current_undo_group);
|
|
||||||
|
|
||||||
if (m_current_undo_group.empty())
|
if (m_current_undo_group.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -429,14 +231,11 @@ bool Buffer::redo()
|
||||||
void Buffer::check_invariant() const
|
void Buffer::check_invariant() const
|
||||||
{
|
{
|
||||||
#ifdef KAK_DEBUG
|
#ifdef KAK_DEBUG
|
||||||
ByteCount start = 0;
|
|
||||||
kak_assert(not m_lines.empty());
|
kak_assert(not m_lines.empty());
|
||||||
for (auto& line : m_lines)
|
for (auto& line : m_lines)
|
||||||
{
|
{
|
||||||
kak_assert(line.start == start);
|
|
||||||
kak_assert(line.length() > 0);
|
kak_assert(line.length() > 0);
|
||||||
kak_assert(line.content.back() == '\n');
|
kak_assert(line.back() == '\n');
|
||||||
start += line.length();
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -448,15 +247,9 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content)
|
||||||
if (content.empty())
|
if (content.empty())
|
||||||
return pos;
|
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 begin;
|
||||||
ByteCoord end;
|
ByteCoord end;
|
||||||
|
bool at_end = false;
|
||||||
// if we inserted at the end of the buffer, we have created a new
|
// if we inserted at the end of the buffer, we have created a new
|
||||||
// line without inserting a '\n'
|
// line without inserting a '\n'
|
||||||
if (is_end(pos))
|
if (is_end(pos))
|
||||||
|
@ -466,22 +259,23 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content)
|
||||||
{
|
{
|
||||||
if (content[i] == '\n')
|
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;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (start != content.length())
|
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 };
|
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
|
else
|
||||||
{
|
{
|
||||||
String prefix = m_lines[pos.line].content.substr(0, pos.column);
|
String prefix = m_lines[pos.line].substr(0, pos.column);
|
||||||
String suffix = m_lines[pos.line].content.substr(pos.column);
|
String suffix = m_lines[pos.line].substr(pos.column);
|
||||||
|
|
||||||
std::vector<Line> new_lines;
|
std::vector<String> new_lines;
|
||||||
|
|
||||||
ByteCount start = 0;
|
ByteCount start = 0;
|
||||||
for (ByteCount i = 0; i < content.length(); ++i)
|
for (ByteCount i = 0; i < content.length(); ++i)
|
||||||
|
@ -492,18 +286,17 @@ ByteCoord Buffer::do_insert(ByteCoord pos, const String& content)
|
||||||
if (start == 0)
|
if (start == 0)
|
||||||
{
|
{
|
||||||
line_content = prefix + line_content;
|
line_content = prefix + line_content;
|
||||||
new_lines.push_back({ m_timestamp, offset + start - prefix.length(),
|
new_lines.push_back(std::move(line_content));
|
||||||
std::move(line_content) });
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
new_lines.push_back({ m_timestamp, offset + start, std::move(line_content) });
|
new_lines.push_back(std::move(line_content));
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (start == 0)
|
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())
|
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;
|
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() };
|
end = ByteCoord{ last_line, m_lines[last_line].length() - suffix.length() };
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto listener : m_change_listeners)
|
m_changes.push_back({ Change::Insert, begin, end, at_end });
|
||||||
listener->on_insert(*this, begin, end);
|
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,11 +318,9 @@ ByteCoord Buffer::do_erase(ByteCoord begin, ByteCoord end)
|
||||||
{
|
{
|
||||||
kak_assert(is_valid(begin));
|
kak_assert(is_valid(begin));
|
||||||
kak_assert(is_valid(end));
|
kak_assert(is_valid(end));
|
||||||
++m_timestamp;
|
String prefix = m_lines[begin.line].substr(0, begin.column);
|
||||||
const ByteCount length = distance(begin, end);
|
String suffix = m_lines[end.line].substr(end.column);
|
||||||
String prefix = m_lines[begin.line].content.substr(0, begin.column);
|
String new_line = prefix + suffix;
|
||||||
String suffix = m_lines[end.line].content.substr(end.column);
|
|
||||||
Line new_line = { m_timestamp, m_lines[begin.line].start, prefix + suffix };
|
|
||||||
|
|
||||||
ByteCoord next;
|
ByteCoord next;
|
||||||
if (new_line.length() != 0)
|
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};
|
next = is_end(begin) ? end_coord() : ByteCoord{begin.line, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (LineCount i = begin.line+1; i < line_count(); ++i)
|
m_changes.push_back({ Change::Erase, begin, end, is_end(begin) });
|
||||||
m_lines[i].start -= length;
|
|
||||||
|
|
||||||
for (auto listener : m_change_listeners)
|
|
||||||
listener->on_erase(*this, begin, end);
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,26 +414,44 @@ void Buffer::notify_saved()
|
||||||
m_flags &= ~Flags::New;
|
m_flags &= ~Flags::New;
|
||||||
size_t history_cursor_index = m_history_cursor - m_history.begin();
|
size_t history_cursor_index = m_history_cursor - m_history.begin();
|
||||||
if (m_last_save_undo_index != history_cursor_index)
|
if (m_last_save_undo_index != history_cursor_index)
|
||||||
{
|
|
||||||
++m_timestamp;
|
|
||||||
m_last_save_undo_index = history_cursor_index;
|
m_last_save_undo_index = history_cursor_index;
|
||||||
}
|
|
||||||
m_fs_timestamp = get_fs_timestamp(m_name);
|
m_fs_timestamp = get_fs_timestamp(m_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteCoord Buffer::advance(ByteCoord coord, ByteCount count) const
|
ByteCoord Buffer::advance(ByteCoord coord, ByteCount count) const
|
||||||
{
|
{
|
||||||
ByteCount off = Kakoune::clamp(offset(coord) + count, 0_byte, byte_count());
|
if (count > 0)
|
||||||
auto it = std::upper_bound(m_lines.begin(), m_lines.end(), off,
|
{
|
||||||
[](ByteCount s, const Line& l) { return s < l.start; }) - 1;
|
auto line = coord.line;
|
||||||
return { LineCount{ (int)(it - m_lines.begin()) }, off - it->start };
|
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
|
ByteCoord Buffer::char_next(ByteCoord coord) const
|
||||||
{
|
{
|
||||||
if (coord.column < m_lines[coord.line].length() - 1)
|
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);
|
coord.column += utf8::codepoint_size(line.begin() + (int)coord.column);
|
||||||
// Handle invalid utf-8
|
// Handle invalid utf-8
|
||||||
if (coord.column >= line.length())
|
if (coord.column >= line.length())
|
||||||
|
@ -678,7 +482,7 @@ ByteCoord Buffer::char_prev(ByteCoord coord) const
|
||||||
}
|
}
|
||||||
else
|
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());
|
coord.column = (int)(utf8::character_start(line.begin() + (int)coord.column - 1) - line.begin());
|
||||||
}
|
}
|
||||||
return coord;
|
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)
|
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());
|
m_hooks.run_hook(hook_name, param, hook_handler.context());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,15 +63,6 @@ private:
|
||||||
ByteCoord m_coord;
|
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
|
// A Buffer is a in-memory representation of a file
|
||||||
//
|
//
|
||||||
// The Buffer class permits to read and mutate this file
|
// The Buffer class permits to read and mutate this file
|
||||||
|
@ -104,7 +95,6 @@ public:
|
||||||
BufferIterator erase(BufferIterator begin, BufferIterator end);
|
BufferIterator erase(BufferIterator begin, BufferIterator end);
|
||||||
|
|
||||||
size_t timestamp() const;
|
size_t timestamp() const;
|
||||||
size_t line_timestamp(LineCount line) const;
|
|
||||||
time_t fs_timestamp() const;
|
time_t fs_timestamp() const;
|
||||||
void set_fs_timestamp(time_t ts);
|
void set_fs_timestamp(time_t ts);
|
||||||
|
|
||||||
|
@ -115,7 +105,6 @@ public:
|
||||||
String string(ByteCoord begin, ByteCoord end) const;
|
String string(ByteCoord begin, ByteCoord end) const;
|
||||||
|
|
||||||
char byte_at(ByteCoord c) const;
|
char byte_at(ByteCoord c) const;
|
||||||
ByteCount offset(ByteCoord c) const;
|
|
||||||
ByteCount distance(ByteCoord begin, ByteCoord end) const;
|
ByteCount distance(ByteCoord begin, ByteCoord end) const;
|
||||||
ByteCoord advance(ByteCoord coord, ByteCount count) const;
|
ByteCoord advance(ByteCoord coord, ByteCount count) const;
|
||||||
ByteCoord next(ByteCoord coord) const;
|
ByteCoord next(ByteCoord coord) const;
|
||||||
|
@ -134,11 +123,10 @@ public:
|
||||||
|
|
||||||
BufferIterator begin() const;
|
BufferIterator begin() const;
|
||||||
BufferIterator end() const;
|
BufferIterator end() const;
|
||||||
ByteCount byte_count() const;
|
|
||||||
LineCount line_count() const;
|
LineCount line_count() const;
|
||||||
|
|
||||||
const String& operator[](LineCount line) 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
|
// returns an iterator at given coordinates. clamp line_and_column
|
||||||
BufferIterator iterator_at(ByteCoord coord) const;
|
BufferIterator iterator_at(ByteCoord coord) const;
|
||||||
|
@ -170,30 +158,30 @@ public:
|
||||||
|
|
||||||
void run_hook_in_own_context(const String& hook_name, const String& param);
|
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 reload(std::vector<String> lines, time_t fs_timestamp = InvalidTime);
|
||||||
|
|
||||||
void check_invariant() const;
|
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:
|
private:
|
||||||
|
|
||||||
void on_option_changed(const Option& option) override;
|
void on_option_changed(const Option& option) override;
|
||||||
|
|
||||||
struct Line
|
struct LineList : std::vector<String>
|
||||||
{
|
{
|
||||||
size_t timestamp;
|
String& operator[](LineCount line)
|
||||||
ByteCount start;
|
{ return std::vector<String>::operator[]((int)line); }
|
||||||
String content;
|
|
||||||
|
|
||||||
ByteCount length() const { return content.length(); }
|
const String& operator[](LineCount line) const
|
||||||
};
|
{ return std::vector<String>::operator[]((int)line); }
|
||||||
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); }
|
|
||||||
};
|
};
|
||||||
LineList m_lines;
|
LineList m_lines;
|
||||||
|
|
||||||
|
@ -215,14 +203,11 @@ private:
|
||||||
void revert_modification(const Modification& modification);
|
void revert_modification(const Modification& modification);
|
||||||
|
|
||||||
size_t m_last_save_undo_index;
|
size_t m_last_save_undo_index;
|
||||||
size_t m_timestamp;
|
|
||||||
|
std::vector<Change> m_changes;
|
||||||
|
|
||||||
time_t m_fs_timestamp;
|
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;
|
OptionManager m_options;
|
||||||
HookManager m_hooks;
|
HookManager m_hooks;
|
||||||
KeymapManager m_keymaps;
|
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"
|
#include "buffer.inl.hh"
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Kakoune
|
||||||
inline char Buffer::byte_at(ByteCoord c) const
|
inline char Buffer::byte_at(ByteCoord c) const
|
||||||
{
|
{
|
||||||
kak_assert(c.line < line_count() and c.column < m_lines[c.line].length());
|
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
|
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
|
inline ByteCount Buffer::distance(ByteCoord begin, ByteCoord end) const
|
||||||
{
|
{
|
||||||
return offset(end) - offset(begin);
|
if (begin > end)
|
||||||
}
|
return -distance(end, begin);
|
||||||
|
ByteCount res = 0;
|
||||||
inline ByteCount Buffer::offset(ByteCoord c) const
|
for (LineCount l = begin.line; l <= end.line; ++l)
|
||||||
{
|
{
|
||||||
if (c.line == line_count())
|
ByteCount len = m_lines[l].length();
|
||||||
return m_lines.back().start + m_lines.back().length();
|
res += len;
|
||||||
return m_lines[c.line].start + c.column;
|
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
|
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
|
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() - 1 and c.column == m_lines.back().length()) or
|
||||||
(c.line == line_count() and c.column == 0);
|
(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() });
|
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
|
inline LineCount Buffer::line_count() const
|
||||||
{
|
{
|
||||||
return LineCount(m_lines.size());
|
return LineCount(m_lines.size());
|
||||||
|
@ -88,12 +89,13 @@ inline LineCount Buffer::line_count() const
|
||||||
|
|
||||||
inline size_t Buffer::timestamp() 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
|
inline ByteCoord Buffer::back_coord() const
|
||||||
|
|
|
@ -24,20 +24,6 @@ inline CharCount char_length(const Buffer& buffer, const Selection& range)
|
||||||
utf8::next(buffer.iterator_at(range.max())));
|
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 get_column(const Buffer& buffer,
|
||||||
CharCount tabstop, ByteCoord coord);
|
CharCount tabstop, ByteCoord coord);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ Client::Client(std::unique_ptr<UserInterface>&& ui,
|
||||||
EnvVarMap env_vars,
|
EnvVarMap env_vars,
|
||||||
String name)
|
String name)
|
||||||
: m_ui{std::move(ui)}, m_window{std::move(window)},
|
: 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)},
|
std::move(name)},
|
||||||
m_env_vars(env_vars)
|
m_env_vars(env_vars)
|
||||||
{
|
{
|
||||||
|
@ -102,7 +102,7 @@ static void reload_buffer(Context& context, const String& filename)
|
||||||
if (not buf)
|
if (not buf)
|
||||||
return;
|
return;
|
||||||
context.change_buffer(*buf);
|
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.window().set_position(view_pos);
|
||||||
context.print_status({ "'" + buf->display_name() + "' reloaded",
|
context.print_status({ "'" + buf->display_name() + "' reloaded",
|
||||||
get_color("Information") });
|
get_color("Information") });
|
||||||
|
|
|
@ -94,18 +94,18 @@ WindowAndSelections ClientManager::get_free_window(Buffer& buffer)
|
||||||
w->forget_timestamp();
|
w->forget_timestamp();
|
||||||
WindowAndSelections res = std::move(*it);
|
WindowAndSelections res = std::move(*it);
|
||||||
m_free_windows.erase(it.base()-1);
|
m_free_windows.erase(it.base()-1);
|
||||||
|
res.selections.update();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return WindowAndSelections{ std::unique_ptr<Window>{new Window{buffer}},
|
return WindowAndSelections{ std::unique_ptr<Window>{new Window{buffer}},
|
||||||
DynamicSelectionList{buffer,
|
SelectionList{ buffer, Selection{} } };
|
||||||
{ Selection{ {}, {} } } } };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientManager::add_free_window(std::unique_ptr<Window>&& window, SelectionList selections)
|
void ClientManager::add_free_window(std::unique_ptr<Window>&& window, SelectionList selections)
|
||||||
{
|
{
|
||||||
Buffer& buffer = window->buffer();
|
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)
|
void ClientManager::ensure_no_client_uses_buffer(Buffer& buffer)
|
||||||
|
|
|
@ -12,7 +12,8 @@ struct client_removed{};
|
||||||
struct WindowAndSelections
|
struct WindowAndSelections
|
||||||
{
|
{
|
||||||
std::unique_ptr<Window> window;
|
std::unique_ptr<Window> window;
|
||||||
DynamicSelectionList selections;
|
SelectionList selections;
|
||||||
|
size_t timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ClientManager : public Singleton<ClientManager>
|
class ClientManager : public Singleton<ClientManager>
|
||||||
|
|
|
@ -131,7 +131,8 @@ void edit(const ParametersParser& parser, Context& context)
|
||||||
int column = param_count > 2 and not parser[2].empty() ?
|
int column = param_count > 2 and not parser[2].empty() ?
|
||||||
std::max(0, str_to_int(parser[2]) - 1) : 0;
|
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())
|
if (context.has_window())
|
||||||
context.window().center_line(context.selections().main().cursor().line);
|
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)
|
for (auto& name : names)
|
||||||
{
|
{
|
||||||
Buffer& buffer = BufferManager::instance().get_buffer(name);
|
Buffer& buffer = BufferManager::instance().get_buffer(name);
|
||||||
InputHandler input_handler{buffer, ( Selection{} )};
|
InputHandler input_handler{{ buffer, Selection{} }};
|
||||||
func(parser, input_handler.context());
|
func(parser, input_handler.context());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -986,7 +987,7 @@ void context_wrap(const ParametersParser& parser, Context& context, Func func)
|
||||||
|
|
||||||
if (parser.has_option("draft"))
|
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
|
// We do not want this draft context to commit undo groups if the real one is
|
||||||
// going to commit the whole thing later
|
// 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"))
|
if (parser.has_option("itersel"))
|
||||||
{
|
{
|
||||||
DynamicSelectionList sels{real_context->buffer(), real_context->selections()};
|
SelectionList sels{real_context->selections()};
|
||||||
ScopedEdition edition{input_handler.context()};
|
ScopedEdition edition{input_handler.context()};
|
||||||
for (auto& sel : sels)
|
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());
|
func(parser, input_handler.context());
|
||||||
|
|
||||||
|
if (&sels.buffer() != &input_handler.context().buffer())
|
||||||
|
throw runtime_error("the buffer has changed while iterating on selections");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -10,10 +10,10 @@ namespace Kakoune
|
||||||
Context::Context() = default;
|
Context::Context() = default;
|
||||||
Context::~Context() = default;
|
Context::~Context() = default;
|
||||||
|
|
||||||
Context::Context(InputHandler& input_handler, Buffer& buffer,
|
Context::Context(InputHandler& input_handler, SelectionList selections,
|
||||||
SelectionList selections, String name)
|
String name)
|
||||||
: m_input_handler{&input_handler},
|
: m_input_handler{&input_handler},
|
||||||
m_selections{{buffer, std::move(selections)}},
|
m_selections{std::move(selections)},
|
||||||
m_name(std::move(name))
|
m_name(std::move(name))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ Buffer& Context::buffer() const
|
||||||
{
|
{
|
||||||
if (not has_buffer())
|
if (not has_buffer())
|
||||||
throw runtime_error("no buffer in context");
|
throw runtime_error("no buffer in context");
|
||||||
return (*m_selections).registry();
|
return const_cast<Buffer&>((*m_selections).buffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
Window& Context::window() const
|
Window& Context::window() const
|
||||||
|
@ -103,32 +103,37 @@ void Context::push_jump()
|
||||||
if (m_current_jump != m_jump_list.end())
|
if (m_current_jump != m_jump_list.end())
|
||||||
{
|
{
|
||||||
auto begin = m_current_jump;
|
auto begin = m_current_jump;
|
||||||
if (&buffer() != &begin->buffer() or
|
if (&buffer() != &begin->buffer() or *begin != jump)
|
||||||
(const SelectionList&)(*begin) != jump)
|
|
||||||
++begin;
|
++begin;
|
||||||
m_jump_list.erase(begin, m_jump_list.end());
|
m_jump_list.erase(begin, m_jump_list.end());
|
||||||
}
|
}
|
||||||
m_jump_list.erase(std::remove(begin(m_jump_list), end(m_jump_list), jump),
|
m_jump_list.erase(std::remove(begin(m_jump_list), end(m_jump_list), jump),
|
||||||
end(m_jump_list));
|
end(m_jump_list));
|
||||||
m_jump_list.push_back({buffer(), jump});
|
m_jump_list.push_back(jump);
|
||||||
m_current_jump = m_jump_list.end();
|
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
|
if (m_current_jump != m_jump_list.end() and
|
||||||
m_current_jump + 1 != m_jump_list.end())
|
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");
|
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
|
if (m_current_jump != m_jump_list.end() and
|
||||||
*m_current_jump != selections())
|
*m_current_jump != selections())
|
||||||
{
|
{
|
||||||
push_jump();
|
push_jump();
|
||||||
return *--m_current_jump;
|
SelectionList& res = *--m_current_jump;
|
||||||
|
res.update();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
if (m_current_jump != m_jump_list.begin())
|
if (m_current_jump != m_jump_list.begin())
|
||||||
{
|
{
|
||||||
|
@ -137,7 +142,9 @@ const DynamicSelectionList& Context::jump_backward()
|
||||||
push_jump();
|
push_jump();
|
||||||
--m_current_jump;
|
--m_current_jump;
|
||||||
}
|
}
|
||||||
return *--m_current_jump;
|
SelectionList& res = *--m_current_jump;
|
||||||
|
res.update();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
throw runtime_error("no previous jump");
|
throw runtime_error("no previous jump");
|
||||||
}
|
}
|
||||||
|
@ -168,7 +175,7 @@ void Context::change_buffer(Buffer& buffer)
|
||||||
if (has_client())
|
if (has_client())
|
||||||
client().change_buffer(buffer);
|
client().change_buffer(buffer);
|
||||||
else
|
else
|
||||||
m_selections = DynamicSelectionList{ buffer };
|
m_selections = SelectionList{buffer, Selection{}};
|
||||||
if (has_input_handler())
|
if (has_input_handler())
|
||||||
input_handler().reset_normal_mode();
|
input_handler().reset_normal_mode();
|
||||||
}
|
}
|
||||||
|
@ -177,14 +184,13 @@ SelectionList& Context::selections()
|
||||||
{
|
{
|
||||||
if (not m_selections)
|
if (not m_selections)
|
||||||
throw runtime_error("no selections in context");
|
throw runtime_error("no selections in context");
|
||||||
|
(*m_selections).update();
|
||||||
return *m_selections;
|
return *m_selections;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectionList& Context::selections() const
|
const SelectionList& Context::selections() const
|
||||||
{
|
{
|
||||||
if (not m_selections)
|
return const_cast<Context&>(*this).selections();
|
||||||
throw runtime_error("no selections in context");
|
|
||||||
return *m_selections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<String> Context::selections_content() const
|
std::vector<String> Context::selections_content() const
|
||||||
|
@ -196,6 +202,12 @@ std::vector<String> Context::selections_content() const
|
||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Context::set_selections(std::vector<Selection> sels)
|
||||||
|
{
|
||||||
|
*m_selections = std::move(sels);
|
||||||
|
(*m_selections).check_invariant();
|
||||||
|
}
|
||||||
|
|
||||||
void Context::begin_edition()
|
void Context::begin_edition()
|
||||||
{
|
{
|
||||||
if (m_edition_level >= 0)
|
if (m_edition_level >= 0)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef context_hh_INCLUDED
|
#ifndef context_hh_INCLUDED
|
||||||
#define context_hh_INCLUDED
|
#define context_hh_INCLUDED
|
||||||
|
|
||||||
#include "dynamic_selection_list.hh"
|
#include "selection.hh"
|
||||||
|
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ class Context
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Context();
|
Context();
|
||||||
Context(InputHandler& input_handler, Buffer& buffer, SelectionList selections, String name = "");
|
Context(InputHandler& input_handler, SelectionList selections,
|
||||||
|
String name = "");
|
||||||
~Context();
|
~Context();
|
||||||
|
|
||||||
Context(const Context&) = delete;
|
Context(const Context&) = delete;
|
||||||
|
@ -50,6 +51,7 @@ public:
|
||||||
SelectionList& selections();
|
SelectionList& selections();
|
||||||
const SelectionList& selections() const;
|
const SelectionList& selections() const;
|
||||||
std::vector<String> selections_content() const;
|
std::vector<String> selections_content() const;
|
||||||
|
void set_selections(std::vector<Selection> sels);
|
||||||
|
|
||||||
void change_buffer(Buffer& buffer);
|
void change_buffer(Buffer& buffer);
|
||||||
|
|
||||||
|
@ -63,8 +65,8 @@ public:
|
||||||
void print_status(DisplayLine status) const;
|
void print_status(DisplayLine status) const;
|
||||||
|
|
||||||
void push_jump();
|
void push_jump();
|
||||||
const DynamicSelectionList& jump_forward();
|
const SelectionList& jump_forward();
|
||||||
const DynamicSelectionList& jump_backward();
|
const SelectionList& jump_backward();
|
||||||
void forget_jumps_to_buffer(Buffer& buffer);
|
void forget_jumps_to_buffer(Buffer& buffer);
|
||||||
|
|
||||||
const String& name() const { return m_name; }
|
const String& name() const { return m_name; }
|
||||||
|
@ -84,11 +86,11 @@ private:
|
||||||
safe_ptr<Client> m_client;
|
safe_ptr<Client> m_client;
|
||||||
|
|
||||||
friend class Client;
|
friend class Client;
|
||||||
boost::optional<DynamicSelectionList> m_selections;
|
boost::optional<SelectionList> m_selections;
|
||||||
|
|
||||||
String m_name;
|
String m_name;
|
||||||
|
|
||||||
using JumpList = std::vector<DynamicSelectionList>;
|
using JumpList = std::vector<SelectionList>;
|
||||||
JumpList m_jump_list;
|
JumpList m_jump_list;
|
||||||
JumpList::iterator m_current_jump = m_jump_list.begin();
|
JumpList::iterator m_current_jump = m_jump_list.begin();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include "color_registry.hh"
|
#include "color_registry.hh"
|
||||||
#include "context.hh"
|
#include "context.hh"
|
||||||
#include "display_buffer.hh"
|
#include "display_buffer.hh"
|
||||||
#include "line_change_watcher.hh"
|
#include "line_modification.hh"
|
||||||
#include "option_types.hh"
|
#include "option_types.hh"
|
||||||
#include "register_manager.hh"
|
#include "register_manager.hh"
|
||||||
#include "string.hh"
|
#include "string.hh"
|
||||||
|
@ -158,7 +158,7 @@ struct BufferSideCache
|
||||||
{
|
{
|
||||||
Value& cache_val = buffer.values()[m_id];
|
Value& cache_val = buffer.values()[m_id];
|
||||||
if (not cache_val)
|
if (not cache_val)
|
||||||
cache_val = Value(T{buffer});
|
cache_val = Value(T{});
|
||||||
return cache_val.as<T>();
|
return cache_val.as<T>();
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
|
@ -195,7 +195,6 @@ public:
|
||||||
private:
|
private:
|
||||||
struct Cache
|
struct Cache
|
||||||
{
|
{
|
||||||
Cache(const Buffer&){}
|
|
||||||
std::pair<LineCount, LineCount> m_range;
|
std::pair<LineCount, LineCount> m_range;
|
||||||
size_t m_timestamp = 0;
|
size_t m_timestamp = 0;
|
||||||
std::vector<std::vector<std::pair<ByteCoord, ByteCoord>>> m_matches;
|
std::vector<std::vector<std::pair<ByteCoord, ByteCoord>>> m_matches;
|
||||||
|
@ -699,10 +698,8 @@ private:
|
||||||
: lhs.begin < rhs.begin;
|
: lhs.begin < rhs.begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Cache : public LineChangeWatcher
|
struct Cache
|
||||||
{
|
{
|
||||||
Cache(const Buffer& buffer) : LineChangeWatcher(buffer) {}
|
|
||||||
|
|
||||||
size_t timestamp = 0;
|
size_t timestamp = 0;
|
||||||
MatchList begin_matches;
|
MatchList begin_matches;
|
||||||
MatchList end_matches;
|
MatchList end_matches;
|
||||||
|
@ -724,7 +721,7 @@ private:
|
||||||
}
|
}
|
||||||
else
|
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.begin_matches, m_begin);
|
||||||
update_matches(buffer, modifs, cache.end_matches, m_end);
|
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(),
|
beg_it = std::upper_bound(beg_it, cache.begin_matches.end(),
|
||||||
*end_it, compare_matches_end);
|
*end_it, compare_matches_end);
|
||||||
}
|
}
|
||||||
cache.timestamp = buffer.timestamp();
|
cache.timestamp = buf_timestamp;
|
||||||
return cache.regions;
|
return cache.regions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,15 +772,15 @@ private:
|
||||||
auto modif_it = std::lower_bound(modifs.begin(), modifs.end(), it->line,
|
auto modif_it = std::lower_bound(modifs.begin(), modifs.end(), it->line,
|
||||||
[](const LineModification& c, const LineCount& l)
|
[](const LineModification& c, const LineCount& l)
|
||||||
{ return c.old_line < 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);
|
auto& prev = *(modif_it-1);
|
||||||
erase = it->line <= prev.old_line + prev.num_removed;
|
erase = it->line <= prev.old_line + prev.num_removed;
|
||||||
it->line += prev.diff();
|
it->line += prev.diff();
|
||||||
}
|
}
|
||||||
erase = erase or (it->line >= buffer.line_count() or
|
erase = erase or (it->line >= buffer.line_count());
|
||||||
it->timestamp < buffer.line_timestamp(it->line));
|
|
||||||
|
|
||||||
if (not erase)
|
if (not erase)
|
||||||
{
|
{
|
||||||
|
@ -804,9 +801,10 @@ private:
|
||||||
// try to find new matches in each updated lines
|
// try to find new matches in each updated lines
|
||||||
for (auto& modif : modifs)
|
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];
|
auto& l = buffer[line];
|
||||||
for (boost::regex_iterator<String::const_iterator> it{l.begin(), l.end(), regex}, end{}; it != end; ++it)
|
for (boost::regex_iterator<String::const_iterator> it{l.begin(), l.end(), regex}, end{}; it != end; ++it)
|
||||||
{
|
{
|
||||||
|
|
|
@ -675,21 +675,23 @@ public:
|
||||||
}
|
}
|
||||||
else if (key == Key::Backspace)
|
else if (key == Key::Backspace)
|
||||||
{
|
{
|
||||||
|
std::vector<Selection> sels;
|
||||||
for (auto& sel : context().selections())
|
for (auto& sel : context().selections())
|
||||||
{
|
{
|
||||||
if (sel.cursor() == ByteCoord{0,0})
|
if (sel.cursor() == ByteCoord{0,0})
|
||||||
continue;
|
continue;
|
||||||
auto pos = buffer.iterator_at(sel.cursor());
|
auto pos = sel.cursor();
|
||||||
buffer.erase(utf8::previous(pos), pos);
|
sels.push_back({ buffer.char_prev(pos) });
|
||||||
}
|
}
|
||||||
|
if (not sels.empty())
|
||||||
|
SelectionList{buffer, std::move(sels)}.erase();
|
||||||
}
|
}
|
||||||
else if (key == Key::Delete)
|
else if (key == Key::Delete)
|
||||||
{
|
{
|
||||||
|
std::vector<Selection> sels;
|
||||||
for (auto& sel : context().selections())
|
for (auto& sel : context().selections())
|
||||||
{
|
sels.push_back({ sel.cursor() });
|
||||||
auto pos = buffer.iterator_at(sel.cursor());
|
SelectionList{buffer, std::move(sels)}.erase();
|
||||||
buffer.erase(pos, utf8::next(pos));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (key == Key::Left)
|
else if (key == Key::Left)
|
||||||
{
|
{
|
||||||
|
@ -765,22 +767,13 @@ private:
|
||||||
|
|
||||||
void insert(memoryview<String> strings)
|
void insert(memoryview<String> strings)
|
||||||
{
|
{
|
||||||
auto& buffer = context().buffer();
|
context().selections().insert(strings, InsertMode::InsertCursor);
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void insert(Codepoint key)
|
void insert(Codepoint key)
|
||||||
{
|
{
|
||||||
auto str = codepoint_to_str(key);
|
auto str = codepoint_to_str(key);
|
||||||
auto& buffer = context().buffer();
|
context().selections().insert(str, InsertMode::InsertCursor);
|
||||||
for (auto& sel : context().selections())
|
|
||||||
buffer.insert(buffer.iterator_at(sel.cursor()), str);
|
|
||||||
context().hooks().run_hook("InsertChar", str, context());
|
context().hooks().run_hook("InsertChar", str, context());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -789,69 +782,65 @@ private:
|
||||||
SelectionList& selections = context().selections();
|
SelectionList& selections = context().selections();
|
||||||
Buffer& buffer = context().buffer();
|
Buffer& buffer = context().buffer();
|
||||||
|
|
||||||
for (auto& sel : selections)
|
switch (mode)
|
||||||
{
|
{
|
||||||
ByteCoord anchor, cursor;
|
case InsertMode::Insert:
|
||||||
switch (mode)
|
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:
|
sel = Selection{sel.min(), sel.max()};
|
||||||
anchor = sel.max();
|
auto& cursor = sel.cursor();
|
||||||
cursor = sel.min();
|
|
||||||
break;
|
|
||||||
case InsertMode::Replace:
|
|
||||||
anchor = cursor = Kakoune::erase(buffer, sel).coord();
|
|
||||||
break;
|
|
||||||
case InsertMode::Append:
|
|
||||||
anchor = sel.min();
|
|
||||||
cursor = sel.max();
|
|
||||||
// special case for end of lines, append to current line instead
|
// special case for end of lines, append to current line instead
|
||||||
if (cursor.column != buffer[cursor.line].length() - 1)
|
if (cursor.column != buffer[cursor.line].length() - 1)
|
||||||
cursor = buffer.char_next(cursor);
|
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))
|
break;
|
||||||
anchor = buffer.char_prev(anchor);
|
case InsertMode::AppendAtLineEnd:
|
||||||
if (buffer.is_end(cursor))
|
for (auto& sel : selections)
|
||||||
cursor = buffer.char_prev(cursor);
|
sel = ByteCoord{sel.max().line, buffer[sel.max().line].length() - 1};
|
||||||
sel.anchor() = anchor;
|
break;
|
||||||
sel.cursor() = cursor;
|
case InsertMode::OpenLineBelow:
|
||||||
}
|
for (auto& sel : selections)
|
||||||
if (mode == InsertMode::OpenLineBelow or mode == InsertMode::OpenLineAbove)
|
sel = ByteCoord{sel.max().line, buffer[sel.max().line].length() - 1};
|
||||||
{
|
|
||||||
insert('\n');
|
insert('\n');
|
||||||
if (mode == InsertMode::OpenLineAbove)
|
break;
|
||||||
|
case InsertMode::OpenLineAbove:
|
||||||
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
for (auto& sel : selections)
|
auto line = sel.min().line;
|
||||||
{
|
sel = line > 0 ? ByteCoord{line - 1, buffer[line-1].length() - 1}
|
||||||
// special case, the --first line above did nothing, so we need to compensate now
|
: ByteCoord{0, 0};
|
||||||
if (sel.anchor() == buffer.char_next({0,0}))
|
|
||||||
sel.anchor() = sel.cursor() = 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.sort_and_merge_overlapping();
|
||||||
selections.check_invariant();
|
selections.check_invariant();
|
||||||
|
@ -860,12 +849,13 @@ private:
|
||||||
|
|
||||||
void on_disabled() override
|
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)
|
if (m_insert_mode == InsertMode::Append and sel.cursor().column > 0)
|
||||||
sel.cursor() = context().buffer().char_prev(sel.cursor());
|
sel.cursor() = context().buffer().char_prev(sel.cursor());
|
||||||
avoid_eol(context().buffer(), sel);
|
|
||||||
}
|
}
|
||||||
|
selections.avoid_eol();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode { Default, Complete, InsertReg };
|
enum class Mode { Default, Complete, InsertReg };
|
||||||
|
@ -883,9 +873,9 @@ void InputMode::reset_normal_mode()
|
||||||
m_input_handler.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_mode(new InputModes::Normal(*this)),
|
||||||
m_context(*this, buffer, std::move(selections), std::move(name))
|
m_context(*this, std::move(selections), std::move(name))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ enum class InsertMode : unsigned;
|
||||||
class InputHandler : public SafeCountable
|
class InputHandler : public SafeCountable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
InputHandler(Buffer& buffer, SelectionList selections, String name = "");
|
InputHandler(SelectionList selections, String name = "");
|
||||||
~InputHandler();
|
~InputHandler();
|
||||||
|
|
||||||
// switch to insert mode
|
// switch to insert mode
|
||||||
|
|
|
@ -185,25 +185,26 @@ void InsertCompleter::select(int offset)
|
||||||
if (m_current_candidate < 0)
|
if (m_current_candidate < 0)
|
||||||
m_current_candidate += m_matching_candidates.size();
|
m_current_candidate += m_matching_candidates.size();
|
||||||
const String& candidate = m_matching_candidates[m_current_candidate];
|
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 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 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);
|
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());
|
const auto& cursor = sel.cursor();
|
||||||
auto pos = buffer.iterator_at(sel.cursor());
|
auto pos = buffer.iterator_at(cursor);
|
||||||
if (offset >= prefix_len and offset + suffix_len < buffer_len and
|
if (cursor.column >= prefix_len and (pos + suffix_len) != buffer.end() and
|
||||||
std::equal(ref.begin(), ref.end(), pos - prefix_len))
|
std::equal(ref.begin(), ref.end(), pos - prefix_len))
|
||||||
{
|
{
|
||||||
pos = buffer.erase(pos - prefix_len, pos + suffix_len);
|
pos = buffer.erase(pos - prefix_len, pos + suffix_len);
|
||||||
buffer.insert(pos, candidate);
|
buffer.insert(pos, candidate);
|
||||||
|
const_cast<SelectionList&>(selections).update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_completions.end = cursor_pos;
|
m_completions.end = cursor_pos;
|
||||||
m_completions.begin = buffer.advance(m_completions.end, -candidate.length());
|
m_completions.begin = buffer.advance(cursor_pos, -candidate.length());
|
||||||
m_completions.timestamp = buffer.timestamp();
|
m_completions.timestamp = buffer.timestamp();
|
||||||
if (m_context.has_ui())
|
if (m_context.has_ui())
|
||||||
m_context.ui().menu_select(m_current_candidate);
|
m_context.ui().menu_select(m_current_candidate);
|
||||||
|
@ -226,8 +227,8 @@ void InsertCompleter::update()
|
||||||
ByteCoord cursor = m_context.selections().main().cursor();
|
ByteCoord cursor = m_context.selections().main().cursor();
|
||||||
ByteCoord compl_beg = m_completions.begin;
|
ByteCoord compl_beg = m_completions.begin;
|
||||||
if (cursor.line == compl_beg.line and
|
if (cursor.line == compl_beg.line and
|
||||||
is_in_range(cursor.column - compl_beg.column,
|
is_in_range(cursor.column, compl_beg.column,
|
||||||
ByteCount{0}, longest_completion-1))
|
compl_beg.column + longest_completion-1))
|
||||||
{
|
{
|
||||||
String prefix = m_context.buffer().string(compl_beg, cursor);
|
String prefix = m_context.buffer().string(compl_beg, cursor);
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -1,13 +1,54 @@
|
||||||
#include "line_change_watcher.hh"
|
#include "line_modification.hh"
|
||||||
|
|
||||||
|
#include "buffer.hh"
|
||||||
|
|
||||||
namespace Kakoune
|
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;
|
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,
|
auto pos = std::upper_bound(res.begin(), res.end(), change.pos,
|
||||||
[](const LineCount& l, const LineModification& c)
|
[](const LineCount& l, const LineModification& c)
|
||||||
{ return l < c.new_line; });
|
{ return l < c.new_line; });
|
||||||
|
@ -58,28 +99,7 @@ std::vector<LineModification> LineChangeWatcher::compute_modifications()
|
||||||
it->new_line -= num_removed;
|
it->new_line -= num_removed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_changes.clear();
|
|
||||||
return res;
|
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
26
src/line_modification.hh
Normal 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
|
223
src/normal.cc
223
src/normal.cc
|
@ -21,89 +21,6 @@
|
||||||
namespace Kakoune
|
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;
|
using namespace std::placeholders;
|
||||||
|
|
||||||
enum class SelectMode
|
enum class SelectMode
|
||||||
|
@ -174,11 +91,11 @@ constexpr Select<mode, T> make_select(T func)
|
||||||
}
|
}
|
||||||
|
|
||||||
template<SelectMode mode = SelectMode::Replace>
|
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);
|
coord = buffer.clamp(coord);
|
||||||
if (mode == SelectMode::Replace)
|
if (mode == SelectMode::Replace)
|
||||||
selections = SelectionList { coord };
|
selections = SelectionList{ buffer, coord };
|
||||||
else if (mode == SelectMode::Extend)
|
else if (mode == SelectMode::Extend)
|
||||||
{
|
{
|
||||||
for (auto& sel : selections)
|
for (auto& sel : selections)
|
||||||
|
@ -407,7 +324,7 @@ void replace_with_char(Context& context, int)
|
||||||
CharCount count = char_length(buffer, sel);
|
CharCount count = char_length(buffer, sel);
|
||||||
strings.emplace_back(key.key, count);
|
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");
|
}, "replace with char", "enter char to replace with\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +347,7 @@ void for_each_char(Context& context, int)
|
||||||
for (auto& c : sel)
|
for (auto& c : sel)
|
||||||
c = func(c);
|
c = func(c);
|
||||||
}
|
}
|
||||||
insert<InsertMode::Replace>(context.buffer(), context.selections(), sels);
|
context.selections().insert(sels, InsertMode::Replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void command(Context& context, int)
|
void command(Context& context, int)
|
||||||
|
@ -495,7 +412,7 @@ void pipe(Context& context, int)
|
||||||
strings.push_back(str);
|
strings.push_back(str);
|
||||||
}
|
}
|
||||||
ScopedEdition edition(context);
|
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();
|
RegisterManager::instance()['"'] = context.selections_content();
|
||||||
ScopedEdition edition(context);
|
ScopedEdition edition(context);
|
||||||
erase(context.buffer(), context.selections());
|
context.selections().erase();
|
||||||
|
context.selections().avoid_eol();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cat_erase_selections(Context& context, int)
|
void cat_erase_selections(Context& context, int)
|
||||||
|
@ -555,7 +473,8 @@ void cat_erase_selections(Context& context, int)
|
||||||
for (auto& sel : sels)
|
for (auto& sel : sels)
|
||||||
str += sel;
|
str += sel;
|
||||||
RegisterManager::instance()['"'] = memoryview<String>(str);
|
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);
|
ScopedEdition edition(context);
|
||||||
if (linewise)
|
context.selections().insert(strings,
|
||||||
insert<adapt_for_linewise(mode)>(context.buffer(), context.selections(), strings);
|
linewise ? adapt_for_linewise(mode) : mode);
|
||||||
else
|
|
||||||
insert<mode>(context.buffer(), context.selections(), strings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void regex_prompt(Context& context, const String prompt, T func)
|
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,
|
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
|
try
|
||||||
{
|
{
|
||||||
if (event != PromptEvent::Change and context.has_ui())
|
if (event != PromptEvent::Change and context.has_ui())
|
||||||
context.ui().info_hide();
|
context.ui().info_hide();
|
||||||
|
selections.update();
|
||||||
context.selections() = selections;
|
context.selections() = selections;
|
||||||
context.input_handler().set_prompt_colors(get_color("Prompt"));
|
context.input_handler().set_prompt_colors(get_color("Prompt"));
|
||||||
if (event == PromptEvent::Abort)
|
if (event == PromptEvent::Abort)
|
||||||
|
@ -718,7 +636,7 @@ void select_regex(Context& context, int)
|
||||||
else
|
else
|
||||||
RegisterManager::instance()['/'] = String{ex.str()};
|
RegisterManager::instance()['/'] = String{ex.str()};
|
||||||
if (not ex.empty() and not ex.str().empty())
|
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
|
else
|
||||||
RegisterManager::instance()['/'] = String{ex.str()};
|
RegisterManager::instance()['/'] = String{ex.str()};
|
||||||
if (not ex.empty() and not ex.str().empty())
|
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& selections = context.selections();
|
||||||
auto& buffer = context.buffer();
|
auto& buffer = context.buffer();
|
||||||
SelectionList res;
|
std::vector<Selection> res;
|
||||||
for (auto& sel : selections)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
if (sel.anchor().line == sel.cursor().line)
|
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({line, {line, buffer[line].length()-1}});
|
||||||
res.push_back({max.line, max});
|
res.push_back({max.line, max});
|
||||||
}
|
}
|
||||||
res.set_main_index(res.size() - 1);
|
|
||||||
selections = std::move(res);
|
selections = std::move(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
void join_select_spaces(Context& context, int)
|
void join_select_spaces(Context& context, int)
|
||||||
{
|
{
|
||||||
auto& buffer = context.buffer();
|
auto& buffer = context.buffer();
|
||||||
SelectionList selections;
|
std::vector<Selection> selections;
|
||||||
for (auto& sel : context.selections())
|
for (auto& sel : context.selections())
|
||||||
{
|
{
|
||||||
for (LineCount line = sel.min().line; line <= sel.max().line; ++line)
|
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())
|
if (selections.empty())
|
||||||
return;
|
return;
|
||||||
selections.sort_and_merge_overlapping();
|
|
||||||
context.selections() = selections;
|
context.selections() = selections;
|
||||||
ScopedEdition edition(context);
|
ScopedEdition edition(context);
|
||||||
insert<InsertMode::Replace>(buffer, context.selections(), " ");
|
context.selections().insert(" "_str, InsertMode::Replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
void join(Context& context, int param)
|
void join(Context& context, int param)
|
||||||
{
|
{
|
||||||
DynamicSelectionList sels{context.buffer(), context.selections()};
|
SelectionList sels{context.selections()};
|
||||||
auto restore_sels = on_scope_end([&]{ context.selections() = std::move(sels); });
|
auto restore_sels = on_scope_end([&]{
|
||||||
|
sels.update();
|
||||||
|
context.selections() = std::move(sels);
|
||||||
|
});
|
||||||
|
|
||||||
join_select_spaces(context, param);
|
join_select_spaces(context, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -796,7 +716,7 @@ void keep(Context& context, int)
|
||||||
if (ex.empty())
|
if (ex.empty())
|
||||||
return;
|
return;
|
||||||
const Buffer& buffer = context.buffer();
|
const Buffer& buffer = context.buffer();
|
||||||
SelectionList keep;
|
std::vector<Selection> keep;
|
||||||
for (auto& sel : context.selections())
|
for (auto& sel : context.selections())
|
||||||
{
|
{
|
||||||
if (boost::regex_search(buffer.iterator_at(sel.min()),
|
if (boost::regex_search(buffer.iterator_at(sel.min()),
|
||||||
|
@ -805,7 +725,7 @@ void keep(Context& context, int)
|
||||||
}
|
}
|
||||||
if (keep.empty())
|
if (keep.empty())
|
||||||
throw runtime_error("no selections remaining");
|
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;
|
return;
|
||||||
const Buffer& buffer = context.buffer();
|
const Buffer& buffer = context.buffer();
|
||||||
auto& shell_manager = ShellManager::instance();
|
auto& shell_manager = ShellManager::instance();
|
||||||
SelectionList keep;
|
std::vector<Selection> keep;
|
||||||
for (auto& sel : context.selections())
|
for (auto& sel : context.selections())
|
||||||
{
|
{
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
@ -829,7 +749,7 @@ void keep_pipe(Context& context, int)
|
||||||
}
|
}
|
||||||
if (keep.empty())
|
if (keep.empty())
|
||||||
throw runtime_error("no selections remaining");
|
throw runtime_error("no selections remaining");
|
||||||
context.selections() = std::move(keep);
|
context.set_selections(std::move(keep));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
template<bool indent_empty = false>
|
template<bool indent_empty = false>
|
||||||
|
@ -839,7 +759,7 @@ void indent(Context& context, int)
|
||||||
String indent = indent_width == 0 ? "\t" : String{' ', indent_width};
|
String indent = indent_width == 0 ? "\t" : String{' ', indent_width};
|
||||||
|
|
||||||
auto& buffer = context.buffer();
|
auto& buffer = context.buffer();
|
||||||
SelectionList sels;
|
std::vector<Selection> sels;
|
||||||
for (auto& sel : context.selections())
|
for (auto& sel : context.selections())
|
||||||
{
|
{
|
||||||
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
|
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())
|
if (not sels.empty())
|
||||||
{
|
{
|
||||||
ScopedEdition edition(context);
|
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;
|
indent_width = tabstop;
|
||||||
|
|
||||||
auto& buffer = context.buffer();
|
auto& buffer = context.buffer();
|
||||||
SelectionList sels;
|
std::vector<Selection> sels;
|
||||||
for (auto& sel : context.selections())
|
for (auto& sel : context.selections())
|
||||||
{
|
{
|
||||||
for (auto line = sel.min().line; line < sel.max().line+1; ++line)
|
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())
|
if (not sels.empty())
|
||||||
{
|
{
|
||||||
ScopedEdition edition(context);
|
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);
|
std::rotate(it, end-count, end);
|
||||||
it = end;
|
it = end;
|
||||||
}
|
}
|
||||||
insert<InsertMode::Replace>(context.buffer(), context.selections(), strings);
|
context.selections().insert(strings, InsertMode::Replace);
|
||||||
context.selections().rotate_main(count);
|
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)
|
void undo(Context& context, int)
|
||||||
{
|
{
|
||||||
ModifiedRangesListener listener(context.buffer());
|
Buffer& buffer = context.buffer();
|
||||||
bool res = context.buffer().undo();
|
size_t timestamp = buffer.timestamp();
|
||||||
if (res and not listener.ranges().empty())
|
bool res = buffer.undo();
|
||||||
|
if (res)
|
||||||
{
|
{
|
||||||
auto& selections = context.selections();
|
auto ranges = compute_modified_ranges(buffer, timestamp);
|
||||||
selections = std::move(listener.ranges());
|
if (not ranges.empty())
|
||||||
selections.set_main_index(selections.size() - 1);
|
context.set_selections(std::move(ranges));
|
||||||
selections.merge_overlapping(std::bind(touches, std::ref(context.buffer()), _1, _2));
|
|
||||||
}
|
}
|
||||||
else if (not res)
|
else if (not res)
|
||||||
context.print_status({ "nothing left to undo", get_color("Information") });
|
context.print_status({ "nothing left to undo", get_color("Information") });
|
||||||
|
@ -1292,15 +1178,16 @@ void undo(Context& context, int)
|
||||||
void redo(Context& context, int)
|
void redo(Context& context, int)
|
||||||
{
|
{
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
ModifiedRangesListener listener(context.buffer());
|
Buffer& buffer = context.buffer();
|
||||||
bool res = context.buffer().redo();
|
size_t timestamp = buffer.timestamp();
|
||||||
if (res and not listener.ranges().empty())
|
bool res = buffer.redo();
|
||||||
|
if (res)
|
||||||
{
|
{
|
||||||
auto& selections = context.selections();
|
auto ranges = compute_modified_ranges(buffer, timestamp);
|
||||||
selections = std::move(listener.ranges());
|
if (not ranges.empty())
|
||||||
selections.set_main_index(selections.size() - 1);
|
context.set_selections(std::move(ranges));
|
||||||
selections.merge_overlapping(std::bind(touches, std::ref(context.buffer()), _1, _2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (not res)
|
else if (not res)
|
||||||
context.print_status({ "nothing left to redo", get_color("Information") });
|
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);
|
: context.buffer().offset_coord(sel.cursor(), offset);
|
||||||
|
|
||||||
sel.anchor() = mode == SelectMode::Extend ? sel.anchor() : cursor;
|
sel.anchor() = mode == SelectMode::Extend ? sel.anchor() : cursor;
|
||||||
sel.cursor() = cursor;
|
sel.cursor() = cursor;
|
||||||
avoid_eol(context.buffer(), sel);
|
|
||||||
}
|
}
|
||||||
|
selections.avoid_eol();
|
||||||
selections.sort_and_merge_overlapping();
|
selections.sort_and_merge_overlapping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1392,12 +1279,12 @@ KeyMap keymap =
|
||||||
|
|
||||||
{ '.', repeat_last_insert },
|
{ '.', repeat_last_insert },
|
||||||
|
|
||||||
{ '%', [](Context& context, int) { select_buffer(context.buffer(), context.selections()); } },
|
{ '%', [](Context& context, int) { select_buffer(context.selections()); } },
|
||||||
|
|
||||||
{ ':', command },
|
{ ':', command },
|
||||||
{ '|', pipe<InsertMode::Replace> },
|
{ '|', pipe<InsertMode::Replace> },
|
||||||
{ alt('|'), pipe<InsertMode::Append> },
|
{ 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); } },
|
else keep_selection(context.selections(), count-1); } },
|
||||||
{ alt(' '), [](Context& context, int count) { if (count == 0) flip_selections(context.selections());
|
{ alt(' '), [](Context& context, int count) { if (count == 0) flip_selections(context.selections());
|
||||||
else remove_selection(context.selections(), count-1); } },
|
else remove_selection(context.selections(), count-1); } },
|
||||||
|
|
|
@ -11,18 +11,6 @@ namespace Kakoune
|
||||||
|
|
||||||
class Context;
|
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)>>;
|
using KeyMap = std::unordered_map<Key, std::function<void (Context& context, int param)>>;
|
||||||
extern KeyMap keymap;
|
extern KeyMap keymap;
|
||||||
|
|
||||||
|
|
552
src/selection.cc
552
src/selection.cc
|
@ -1,6 +1,7 @@
|
||||||
#include "selection.hh"
|
#include "selection.hh"
|
||||||
|
|
||||||
#include "utf8.hh"
|
#include "utf8.hh"
|
||||||
|
#include "buffer_utils.hh"
|
||||||
|
|
||||||
namespace Kakoune
|
namespace Kakoune
|
||||||
{
|
{
|
||||||
|
@ -14,101 +15,518 @@ void Selection::merge_with(const Selection& range)
|
||||||
m_anchor = std::max(m_anchor, range.m_anchor);
|
m_anchor = std::max(m_anchor, range.m_anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
template<template <bool, bool> class UpdateFunc>
|
ByteCoord update_insert(ByteCoord coord, ByteCoord begin, ByteCoord end)
|
||||||
void on_buffer_change(const Buffer& buffer, SelectionList& sels,
|
|
||||||
ByteCoord begin, ByteCoord end, LineCount end_line)
|
|
||||||
{
|
{
|
||||||
auto update_beg = std::lower_bound(sels.begin(), sels.end(), begin,
|
if (coord < begin)
|
||||||
[](const Selection& s, ByteCoord c)
|
return coord;
|
||||||
{ return s.max() < c; });
|
if (begin.line == coord.line)
|
||||||
auto update_only_line_beg = std::upper_bound(sels.begin(), sels.end(), end_line,
|
coord.column += end.column - begin.column;
|
||||||
[](LineCount l, const Selection& s)
|
coord.line += end.line - begin.line;
|
||||||
{ return l < s.min().line; });
|
kak_assert(coord.line >= 0 and coord.column >= 0);
|
||||||
|
return coord;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool assume_different_line, bool assume_greater_than_begin>
|
ByteCoord update_erase(ByteCoord coord, ByteCoord begin, ByteCoord end)
|
||||||
struct UpdateInsert
|
|
||||||
{
|
{
|
||||||
void operator()(const Buffer& buffer, ByteCoord& coord,
|
if (coord < begin)
|
||||||
ByteCoord begin, ByteCoord end) const
|
return coord;
|
||||||
{
|
if (coord <= end)
|
||||||
if (assume_different_line)
|
return begin;
|
||||||
kak_assert(begin.line < coord.line);
|
if (end.line == coord.line)
|
||||||
if (not assume_greater_than_begin and coord < begin)
|
coord.column -= end.column - begin.column;
|
||||||
return;
|
coord.line -= end.line - begin.line;
|
||||||
if (not assume_different_line and begin.line == coord.line)
|
kak_assert(coord.line >= 0 and coord.column >= 0);
|
||||||
coord.column = end.column + coord.column - begin.column;
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
coord.line += end.line - begin.line;
|
static bool compare_selections(const Selection& lhs, const Selection& rhs)
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<bool assume_different_line, bool assume_greater_than_begin>
|
|
||||||
struct UpdateErase
|
|
||||||
{
|
{
|
||||||
void operator()(const Buffer& buffer, ByteCoord& coord,
|
return lhs.min() < rhs.min();
|
||||||
ByteCoord begin, ByteCoord end) const
|
}
|
||||||
|
|
||||||
|
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)
|
if (overlaps(begin[i], begin[j]))
|
||||||
return;
|
{
|
||||||
if (assume_different_line)
|
begin[i].merge_with(begin[j]);
|
||||||
kak_assert(end.line < coord.line);
|
if (i < main)
|
||||||
if (not assume_different_line and coord <= end)
|
--main;
|
||||||
coord = buffer.clamp(begin);
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (not assume_different_line and end.line == coord.line)
|
++i;
|
||||||
{
|
if (i != j)
|
||||||
coord.line = begin.line;
|
begin[i] = std::move(begin[j]);
|
||||||
coord.column = begin.column + coord.column - end.column;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
coord.line -= end.line - begin.line;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
void SelectionList::check_invariant() const
|
||||||
{
|
{
|
||||||
|
#ifdef KAK_DEBUG
|
||||||
|
auto& buffer = this->buffer();
|
||||||
kak_assert(size() > 0);
|
kak_assert(size() > 0);
|
||||||
kak_assert(m_main < size());
|
kak_assert(m_main < size());
|
||||||
for (size_t i = 0; i+1 < size(); ++ i)
|
for (size_t i = 0; i < size(); ++i)
|
||||||
kak_assert((*this)[i].min() <= (*this)[i+1].min());
|
{
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
113
src/selection.hh
113
src/selection.hh
|
@ -12,7 +12,7 @@ using CaptureList = std::vector<String>;
|
||||||
struct Selection
|
struct Selection
|
||||||
{
|
{
|
||||||
Selection() = default;
|
Selection() = default;
|
||||||
explicit Selection(ByteCoord pos) : Selection(pos,pos) {}
|
Selection(ByteCoord pos) : Selection(pos,pos) {}
|
||||||
Selection(ByteCoord anchor, ByteCoord cursor,
|
Selection(ByteCoord anchor, ByteCoord cursor,
|
||||||
CaptureList captures = {})
|
CaptureList captures = {})
|
||||||
: m_anchor{anchor}, m_cursor{cursor},
|
: m_anchor{anchor}, m_cursor{cursor},
|
||||||
|
@ -34,8 +34,11 @@ struct Selection
|
||||||
return m_anchor == other.m_anchor and m_cursor == other.m_cursor;
|
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& min() const { return m_anchor < m_cursor ? m_anchor : m_cursor; }
|
||||||
const ByteCoord& max() const { return std::max(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:
|
private:
|
||||||
ByteCoord m_anchor;
|
ByteCoord m_anchor;
|
||||||
|
@ -50,19 +53,27 @@ inline bool overlaps(const Selection& lhs, const Selection& rhs)
|
||||||
: lhs.min() <= rhs.max();
|
: 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
|
struct SelectionList
|
||||||
{
|
{
|
||||||
SelectionList() = default;
|
SelectionList(Buffer& buffer, Selection s);
|
||||||
SelectionList(ByteCoord c) : m_selections{Selection{c,c}} {}
|
SelectionList(Buffer& buffer, Selection s, size_t timestamp);
|
||||||
SelectionList(Selection s) : m_selections{s} {}
|
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();
|
||||||
void update_erase(const Buffer& buffer, ByteCoord begin, ByteCoord end);
|
|
||||||
|
|
||||||
void check_invariant() const;
|
void check_invariant() const;
|
||||||
|
|
||||||
|
@ -73,12 +84,24 @@ struct SelectionList
|
||||||
|
|
||||||
void rotate_main(int count) { m_main = (m_main + count) % size(); }
|
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(const Selection& sel) { m_selections.push_back(sel); }
|
||||||
void push_back(Selection&& sel) { m_selections.push_back(std::move(sel)); }
|
void push_back(Selection&& sel) { m_selections.push_back(std::move(sel)); }
|
||||||
|
|
||||||
Selection& operator[](size_t i) { return m_selections[i]; }
|
Selection& operator[](size_t i) { return m_selections[i]; }
|
||||||
const Selection& operator[](size_t i) const { 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;
|
using iterator = std::vector<Selection>::iterator;
|
||||||
iterator begin() { return m_selections.begin(); }
|
iterator begin() { return m_selections.begin(); }
|
||||||
iterator end() { return m_selections.end(); }
|
iterator end() { return m_selections.end(); }
|
||||||
|
@ -87,71 +110,33 @@ struct SelectionList
|
||||||
const_iterator begin() const { return m_selections.begin(); }
|
const_iterator begin() const { return m_selections.begin(); }
|
||||||
const_iterator end() const { return m_selections.end(); }
|
const_iterator end() const { return m_selections.end(); }
|
||||||
|
|
||||||
template<typename... Args>
|
void remove(size_t index) { m_selections.erase(begin() + index); }
|
||||||
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)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t size() const { return m_selections.size(); }
|
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_buffer == other.m_buffer and m_selections == other.m_selections; }
|
||||||
bool operator!=(const SelectionList& other) const { return m_selections != other.m_selections; }
|
bool operator!=(const SelectionList& other) const { return !((*this) == other); }
|
||||||
|
|
||||||
template<typename OverlapsFunc>
|
void sort_and_merge_overlapping();
|
||||||
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()
|
Buffer& buffer() const { return *m_buffer; }
|
||||||
{
|
|
||||||
if (size() == 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto& main = this->main();
|
size_t timestamp() const { return m_timestamp; }
|
||||||
const auto main_begin = main.min();
|
void update_timestamp() { m_timestamp = m_buffer->timestamp(); }
|
||||||
m_main = std::count_if(begin(), end(), [&](const Selection& sel) {
|
|
||||||
auto begin = sel.min();
|
void insert(memoryview<String> strings, InsertMode mode);
|
||||||
if (begin == main_begin)
|
void erase();
|
||||||
return &sel < &main;
|
|
||||||
else
|
|
||||||
return begin < main_begin;
|
|
||||||
});
|
|
||||||
std::stable_sort(begin(), end(), compare_selections);
|
|
||||||
merge_overlapping(overlaps);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t m_main = 0;
|
size_t m_main = 0;
|
||||||
std::vector<Selection> m_selections;
|
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
|
#endif // selection_hh_INCLUDED
|
||||||
|
|
|
@ -313,7 +313,7 @@ Selection select_paragraph(const Buffer& buffer, const Selection& selection, Obj
|
||||||
{
|
{
|
||||||
BufferIterator first = buffer.iterator_at(selection.cursor());
|
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-1) == '\n' and *(first-2) == '\n')
|
||||||
--first;
|
--first;
|
||||||
else if ((flags & ObjectFlags::ToEnd) and
|
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());
|
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,
|
void select_all_matches(SelectionList& selections, const Regex& regex)
|
||||||
const Regex& regex)
|
|
||||||
{
|
{
|
||||||
SelectionList result;
|
std::vector<Selection> result;
|
||||||
|
auto& buffer = selections.buffer();
|
||||||
for (auto& sel : selections)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
auto sel_end = utf8::next(buffer.iterator_at(sel.max()));
|
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())
|
if (result.empty())
|
||||||
throw runtime_error("nothing selected");
|
throw runtime_error("nothing selected");
|
||||||
result.set_main_index(result.size() - 1);
|
|
||||||
selections = std::move(result);
|
selections = std::move(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void split_selections(const Buffer& buffer, SelectionList& selections,
|
void split_selections(SelectionList& selections, const Regex& regex)
|
||||||
const Regex& regex)
|
|
||||||
{
|
{
|
||||||
SelectionList result;
|
std::vector<Selection> result;
|
||||||
|
auto& buffer = selections.buffer();
|
||||||
for (auto& sel : selections)
|
for (auto& sel : selections)
|
||||||
{
|
{
|
||||||
auto begin = buffer.iterator_at(sel.min());
|
auto begin = buffer.iterator_at(sel.min());
|
||||||
|
@ -518,7 +518,6 @@ void split_selections(const Buffer& buffer, SelectionList& selections,
|
||||||
if (begin.coord() <= sel.max())
|
if (begin.coord() <= sel.max())
|
||||||
result.push_back({ begin.coord(), sel.max() });
|
result.push_back({ begin.coord(), sel.max() });
|
||||||
}
|
}
|
||||||
result.set_main_index(result.size() - 1);
|
|
||||||
selections = std::move(result);
|
selections = std::move(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,14 @@
|
||||||
namespace Kakoune
|
namespace Kakoune
|
||||||
{
|
{
|
||||||
|
|
||||||
inline void clear_selections(const Buffer& buffer, SelectionList& selections)
|
inline void clear_selections(SelectionList& selections)
|
||||||
{
|
{
|
||||||
auto& sel = selections.main();
|
auto& sel = selections.main();
|
||||||
auto& pos = sel.cursor();
|
auto& pos = sel.cursor();
|
||||||
avoid_eol(buffer, pos);
|
|
||||||
sel.anchor() = 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)
|
inline void flip_selections(SelectionList& selections)
|
||||||
|
@ -31,7 +31,7 @@ inline void keep_selection(SelectionList& selections, int index)
|
||||||
if (index < selections.size())
|
if (index < selections.size())
|
||||||
{
|
{
|
||||||
size_t real_index = (index + selections.main_index() + 1) % 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();
|
selections.check_invariant();
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ inline void remove_selection(SelectionList& selections, int index)
|
||||||
if (selections.size() > 1 and index < selections.size())
|
if (selections.size() > 1 and index < selections.size())
|
||||||
{
|
{
|
||||||
size_t real_index = (index + selections.main_index() + 1) % 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();
|
size_t main_index = selections.main_index();
|
||||||
if (real_index <= main_index)
|
if (real_index <= main_index)
|
||||||
selections.set_main_index((main_index > 0 ? 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);
|
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 };
|
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)};
|
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);
|
const Regex& regex);
|
||||||
|
|
||||||
void split_selections(const Buffer& buffer, SelectionList& selections,
|
void split_selections(SelectionList& selections,
|
||||||
const Regex& separator_regex);
|
const Regex& separator_regex);
|
||||||
|
|
||||||
using CodepointPair = std::pair<Codepoint, Codepoint>;
|
using CodepointPair = std::pair<Codepoint, Codepoint>;
|
||||||
|
|
|
@ -25,6 +25,9 @@ void test_buffer()
|
||||||
kak_assert(pos.coord() == ByteCoord{1 COMMA 0});
|
kak_assert(pos.coord() == ByteCoord{1 COMMA 0});
|
||||||
buffer.insert(pos, "tchou kanaky\n");
|
buffer.insert(pos, "tchou kanaky\n");
|
||||||
kak_assert(buffer.line_count() == 5);
|
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 }));
|
String str = buffer.string({ 4, 1 }, buffer.next({ 4, 5 }));
|
||||||
kak_assert(str == "youpi");
|
kak_assert(str == "youpi");
|
||||||
|
|
|
@ -76,17 +76,16 @@ CharCount distance(Iterator begin, Iterator end)
|
||||||
|
|
||||||
// return true if it points to the first byte of a (either single or
|
// return true if it points to the first byte of a (either single or
|
||||||
// multibyte) character
|
// multibyte) character
|
||||||
template<typename Iterator>
|
inline bool is_character_start(char c)
|
||||||
bool is_character_start(Iterator it)
|
|
||||||
{
|
{
|
||||||
return (*it & 0xC0) != 0x80;
|
return (c & 0xC0) != 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns an iterator to the first byte of the character it is into
|
// returns an iterator to the first byte of the character it is into
|
||||||
template<typename Iterator>
|
template<typename Iterator>
|
||||||
Iterator character_start(Iterator it)
|
Iterator character_start(Iterator it)
|
||||||
{
|
{
|
||||||
while (not is_character_start(it))
|
while (not is_character_start(*it))
|
||||||
--it;
|
--it;
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ Window::Window(Buffer& buffer)
|
||||||
m_options(buffer.options()),
|
m_options(buffer.options()),
|
||||||
m_keymaps(buffer.keymaps())
|
m_keymaps(buffer.keymaps())
|
||||||
{
|
{
|
||||||
InputHandler hook_handler{*m_buffer, { Selection{} } };
|
InputHandler hook_handler{{ *m_buffer, Selection{} }};
|
||||||
hook_handler.context().set_window(*this);
|
hook_handler.context().set_window(*this);
|
||||||
m_hooks.run_hook("WinCreate", buffer.name(), hook_handler.context());
|
m_hooks.run_hook("WinCreate", buffer.name(), hook_handler.context());
|
||||||
m_options.register_watcher(*this);
|
m_options.register_watcher(*this);
|
||||||
|
@ -38,7 +38,7 @@ Window::Window(Buffer& buffer)
|
||||||
|
|
||||||
Window::~Window()
|
Window::~Window()
|
||||||
{
|
{
|
||||||
InputHandler hook_handler{*m_buffer, { Selection{} } };
|
InputHandler hook_handler{{ *m_buffer, Selection{} }};
|
||||||
hook_handler.context().set_window(*this);
|
hook_handler.context().set_window(*this);
|
||||||
m_hooks.run_hook("WinClose", buffer().name(), hook_handler.context());
|
m_hooks.run_hook("WinClose", buffer().name(), hook_handler.context());
|
||||||
m_options.unregister_watcher(*this);
|
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} });
|
lines.emplace_back(AtomList{ {buffer(), line, line+1} });
|
||||||
display_buffer.compute_range();
|
display_buffer.compute_range();
|
||||||
|
|
||||||
InputHandler hook_handler{*m_buffer, { Selection{} } };
|
InputHandler hook_handler{{ *m_buffer, Selection{} } };
|
||||||
hook_handler.context().set_window(*this);
|
hook_handler.context().set_window(*this);
|
||||||
m_highlighters(hook_handler.context(), HighlightFlags::MoveOnly, display_buffer);
|
m_highlighters(hook_handler.context(), HighlightFlags::MoveOnly, display_buffer);
|
||||||
m_builtin_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)
|
void Window::on_option_changed(const Option& option)
|
||||||
{
|
{
|
||||||
String desc = option.name() + "=" + option.get_as_string();
|
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);
|
hook_handler.context().set_window(*this);
|
||||||
m_hooks.run_hook("WinSetOption", desc, hook_handler.context());
|
m_hooks.run_hook("WinSetOption", desc, hook_handler.context());
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "word_db.hh"
|
#include "word_db.hh"
|
||||||
|
|
||||||
#include "utils.hh"
|
#include "utils.hh"
|
||||||
|
#include "line_modification.hh"
|
||||||
#include "utf8_iterator.hh"
|
#include "utf8_iterator.hh"
|
||||||
|
|
||||||
namespace Kakoune
|
namespace Kakoune
|
||||||
|
@ -49,7 +50,7 @@ static void remove_words(WordDB::WordList& wl, const std::vector<String>& words)
|
||||||
}
|
}
|
||||||
|
|
||||||
WordDB::WordDB(const Buffer& buffer)
|
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());
|
m_line_to_words.reserve((int)buffer.line_count());
|
||||||
for (auto line = 0_line, end = buffer.line_count(); line < end; ++line)
|
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()
|
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())
|
if (modifs.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& buffer = m_change_watcher.registry();
|
|
||||||
|
|
||||||
LineToWords new_lines;
|
LineToWords new_lines;
|
||||||
new_lines.reserve((int)buffer.line_count());
|
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)
|
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]));
|
new_lines.push_back(get_words(buffer[modif.new_line + l]));
|
||||||
add_words(m_words, new_lines.back());
|
add_words(m_words, new_lines.back());
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#define word_db_hh_INCLUDED
|
#define word_db_hh_INCLUDED
|
||||||
|
|
||||||
#include "buffer.hh"
|
#include "buffer.hh"
|
||||||
#include "line_change_watcher.hh"
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
@ -26,7 +25,8 @@ private:
|
||||||
|
|
||||||
void update_db();
|
void update_db();
|
||||||
|
|
||||||
LineChangeWatcher m_change_watcher;
|
safe_ptr<const Buffer> m_buffer;
|
||||||
|
size_t m_timestamp;
|
||||||
WordList m_words;
|
WordList m_words;
|
||||||
LineToWords m_line_to_words;
|
LineToWords m_line_to_words;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user