kakoune/src/display_buffer.cc
Maxime Coste eb0e983133 Fix DisplayLine::trim_front quadratic behaviour
Erasing fully trimmed display atoms one by one means we have to
shift all the remaining ones every time. This is wasteful and we
can just erase all the fully trimmed atom in one go.

Fixes #4797
2023-02-03 11:31:13 +11:00

401 lines
11 KiB
C++

#include "display_buffer.hh"
#include "assert.hh"
#include "buffer.hh"
#include "buffer_utils.hh"
#include "face_registry.hh"
#include "utf8.hh"
#include "face_registry.hh"
namespace Kakoune
{
BufferIterator get_iterator(const Buffer& buffer, BufferCoord coord)
{
// Correct one past the end of line as next line
if (not buffer.is_end(coord) and coord.column == buffer[coord.line].length())
coord = coord.line+1;
return buffer.iterator_at(coord);
}
StringView DisplayAtom::content() const
{
switch (m_type)
{
case Range:
{
auto line = (*m_buffer)[m_range.begin.line];
if (m_range.begin.line == m_range.end.line)
return line.substr(m_range.begin.column, m_range.end.column - m_range.begin.column);
else if (m_range.begin.line+1 == m_range.end.line and m_range.end.column == 0)
return line.substr(m_range.begin.column);
break;
}
case Text:
case ReplacedRange:
return m_text;
}
kak_assert(false);
return {};
}
ColumnCount DisplayAtom::length() const
{
switch (m_type)
{
case Range:
return utf8::column_distance(get_iterator(*m_buffer, m_range.begin),
get_iterator(*m_buffer, m_range.end));
case Text:
case ReplacedRange:
return m_text.column_length();
}
kak_assert(false);
return 0;
}
bool DisplayAtom::empty() const
{
if (m_type == Range)
return m_range.begin == m_range.end;
else
return m_text.empty();
}
ColumnCount DisplayAtom::trim_begin(ColumnCount count)
{
ColumnCount res = 0;
if (m_type == Range)
{
auto it = get_iterator(*m_buffer, m_range.begin);
auto end = get_iterator(*m_buffer, m_range.end);
while (it != end and res < count)
res += codepoint_width(utf8::read_codepoint(it, end));
m_range.begin = std::min(it.coord(), m_range.end);
}
else
{
auto it = m_text.begin();
while (it != m_text.end() and res < count)
res += codepoint_width(utf8::read_codepoint(it, m_text.end()));
m_text = String{it, m_text.end()};
}
return res;
}
ColumnCount DisplayAtom::trim_end_to_length(ColumnCount count)
{
ColumnCount res = 0;
if (m_type == Range)
{
auto it = get_iterator(*m_buffer, m_range.begin);
auto end = get_iterator(*m_buffer, m_range.end);
while (it != end and res < count)
res += codepoint_width(utf8::read_codepoint(it, end));
m_range.end = std::min(it.coord(), m_range.end);
}
else
{
auto it = m_text.begin();
while (it != m_text.end() and res < count)
res += codepoint_width(utf8::read_codepoint(it, m_text.end()));
m_text = String{m_text.begin(), it};
}
return res;
}
DisplayLine::DisplayLine(AtomList atoms)
: m_atoms(std::move(atoms))
{
compute_range();
}
DisplayLine::iterator DisplayLine::split(iterator it, BufferCoord pos)
{
kak_assert(it->type() == DisplayAtom::Range);
kak_assert(it->begin() < pos);
kak_assert(it->end() > pos);
DisplayAtom atom = *it;
atom.m_range.end = pos;
it->m_range.begin = pos;
return m_atoms.insert(it, std::move(atom));
}
DisplayLine::iterator DisplayLine::split(iterator it, ColumnCount count)
{
kak_assert(count > 0);
kak_assert(count < it->length());
if (it->type() == DisplayAtom::Text or it->type() == DisplayAtom::ReplacedRange)
{
DisplayAtom atom = *it;
atom.m_text = atom.m_text.substr(0, count).str();
it->m_text = it->m_text.substr(count).str();
return m_atoms.insert(it, std::move(atom));
}
auto pos = utf8::advance(get_iterator(it->buffer(), it->begin()),
get_iterator(it->buffer(), it->end()),
count).coord();
if (pos == it->begin()) // Can happen if we try to split in the middle of a multi-column codepoint
return m_atoms.insert(it, {it->buffer(), {pos, pos}, it->face});
if (pos == it->end())
return std::prev(m_atoms.insert(std::next(it), {it->buffer(), {pos, pos}, it->face}));
return split(it, pos);
}
DisplayLine::iterator DisplayLine::split(BufferCoord pos)
{
auto it = find_if(begin(), end(), [pos](const DisplayAtom& a) {
return (a.has_buffer_range() && a.begin() >= pos) ||
(a.type() == DisplayAtom::Range and a.end() > pos);
});
if (it == end() or it->begin() >= pos)
return it;
return ++split(it, pos);
}
DisplayLine::iterator DisplayLine::insert(iterator it, DisplayAtom atom)
{
if (atom.has_buffer_range())
{
m_range.begin = std::min(m_range.begin, atom.begin());
m_range.end = std::max(m_range.end, atom.end());
}
auto res = m_atoms.insert(it, std::move(atom));
compute_range();
return res;
}
DisplayAtom& DisplayLine::push_back(DisplayAtom atom)
{
if (atom.has_buffer_range())
{
m_range.begin = std::min(m_range.begin, atom.begin());
m_range.end = std::max(m_range.end, atom.end());
}
m_atoms.push_back(std::move(atom));
return m_atoms.back();
}
DisplayLine::iterator DisplayLine::erase(iterator beg, iterator end)
{
auto res = m_atoms.erase(beg, end);
compute_range();
return res;
}
void DisplayLine::optimize()
{
if (m_atoms.empty())
return;
auto atom_it = m_atoms.begin();
for (auto next_it = atom_it + 1; next_it != m_atoms.end(); ++next_it)
{
auto& atom = *atom_it;
auto& next = *next_it;
const auto type = atom.type();
if (type == next.type() and atom.face == next.face)
{
if (type == DisplayAtom::Text)
atom.m_text += next.m_text;
else if ((type == DisplayAtom::Range or
type == DisplayAtom::ReplacedRange) and
next.begin() == atom.end())
{
atom.m_range.end = next.end();
if (type == DisplayAtom::ReplacedRange)
atom.m_text += next.m_text;
}
else
*++atom_it = std::move(*next_it);
}
else
*++atom_it = std::move(*next_it);
}
m_atoms.erase(atom_it+1, m_atoms.end());
}
ColumnCount DisplayLine::length() const
{
ColumnCount len = 0;
for (auto& atom : m_atoms)
len += atom.length();
return len;
}
bool DisplayLine::trim(ColumnCount front, ColumnCount col_count)
{
return trim_from(0_col, front, col_count);
}
bool DisplayLine::trim_from(ColumnCount first_col, ColumnCount front, ColumnCount col_count)
{
auto it = begin();
while (first_col > 0 and it != end())
{
auto len = it->length();
if (len <= first_col)
{
++it;
first_col -= len;
}
else
{
it = ++split(it, front);
first_col = 0;
}
}
auto front_it = it;
while (front > 0 and it != end())
{
front -= it->trim_begin(front);
kak_assert(it->empty() or front == 0);
if (it->empty())
++it;
}
m_atoms.erase(front_it, it);
it = begin();
for (; it != end() and col_count > 0; ++it)
col_count -= it->trim_end_to_length(col_count);
bool did_trim = it != end() && col_count == 0;
m_atoms.erase(it, end());
compute_range();
return did_trim;
}
const BufferRange init_range{ {INT_MAX, INT_MAX}, {INT_MIN, INT_MIN} };
void DisplayLine::compute_range()
{
m_range = init_range;
for (auto& atom : m_atoms)
{
if (not atom.has_buffer_range())
continue;
m_range.begin = std::min(m_range.begin, atom.begin());
m_range.end = std::max(m_range.end, atom.end());
}
if (m_range == init_range)
m_range = { { 0, 0 }, { 0, 0 } };
kak_assert(m_range.begin <= m_range.end);
}
void DisplayBuffer::compute_range()
{
m_range = init_range;
for (auto& line : m_lines)
{
m_range.begin = std::min(line.range().begin, m_range.begin);
m_range.end = std::max(line.range().end, m_range.end);
}
if (m_range == init_range)
m_range = { { 0, 0 }, { 0, 0 } };
kak_assert(m_range.begin <= m_range.end);
}
void DisplayBuffer::optimize()
{
for (auto& line : m_lines)
line.optimize();
}
DisplayLine parse_display_line(StringView line, Face& face, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)
{
DisplayLine res;
bool was_antislash = false;
auto pos = line.begin();
String content;
for (auto it = line.begin(), end = line.end(); it != end; ++it)
{
const char c = *it;
if (c == '{')
{
if (was_antislash)
{
content += StringView{pos, it};
content.back() = '{';
pos = it + 1;
}
else
{
content += StringView{pos, it};
if (not content.empty())
res.push_back({std::move(content), face});
content.clear();
auto closing = std::find(it+1, end, '}');
if (closing == end)
throw runtime_error("unclosed face definition");
if (*(it+1) == '{' and closing+1 != end and *(closing+1) == '}')
{
auto builtin_it = builtins.find(StringView{it+2, closing});
if (builtin_it == builtins.end())
throw runtime_error(format("undefined atom {}", StringView{it+2, closing}));
for (auto& atom : builtin_it->value)
res.push_back(atom);
// closing is now at the first char of "}}", advance it to the second
++closing;
}
else if (closing == it+2 and *(it+1) == '\\')
{
pos = closing + 1;
break;
}
else
face = faces[{it+1, closing}];
it = closing;
pos = closing + 1;
}
}
if (c == '\n' or c == '\t') // line breaks and tabs are forbidden, replace with space
{
content += StringView{pos, it+1};
content.back() = ' ';
pos = it + 1;
}
if (c == '\\')
{
if (was_antislash)
{
content += StringView{pos, it};
pos = it + 1;
was_antislash = false;
}
else
was_antislash = true;
}
else
was_antislash = false;
}
content += StringView{pos, line.end()};
if (not content.empty())
res.push_back({std::move(content), face});
return res;
}
DisplayLine parse_display_line(StringView line, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)
{
Face face{};
return parse_display_line(line, face, faces, builtins);
}
DisplayLineList parse_display_line_list(StringView content, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)
{
return content | split<StringView>('\n')
| transform([&, face=Face{}](StringView s) mutable {
return parse_display_line(s, face, faces, builtins);
})
| gather<DisplayLineList>();
}
}