kakoune/src/display_buffer.cc

421 lines
12 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());
}
return m_atoms.insert(it, std::move(atom));
}
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 DisplayLine::extract(iterator beg, iterator end)
{
if (beg == this->begin() and end == this->end())
return DisplayLine{std::move(*this)};
DisplayLine extracted{AtomList(std::move_iterator(beg), std::move_iterator(end))};
m_atoms.erase(beg, end);
if (extracted.m_range.begin == m_range.begin or
extracted.m_range.end == m_range.end)
compute_range();
return extracted;
}
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;
Optional<DisplayAtom> padding;
while (front > 0 and it != end())
{
front -= it->trim_begin(front);
kak_assert(it->empty() or front <= 0);
if (front < 0)
padding.emplace(it->has_buffer_range()
? DisplayAtom{it->buffer(), {it->begin(), it->begin()}, String{' ', -front}, it->face}
: DisplayAtom{String{' ', -front}, it->face});
if (it->empty())
++it;
}
it = m_atoms.erase(front_it, it);
if (padding)
it = m_atoms.insert(it, std::move(*padding));
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;
auto first = find_if(m_atoms, std::mem_fn(&DisplayAtom::has_buffer_range));
if (first == m_atoms.end())
m_range = { { 0, 0 }, { 0, 0 } };
else
{
auto last = std::find_if(m_atoms.rbegin(), std::reverse_iterator(first+1), std::mem_fn(&DisplayAtom::has_buffer_range));
m_range.begin = first->begin();
m_range.end = last->end();
}
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>();
}
}