Refactor WrapHighlighter::next_split_pos to avoid non-linear complexity

Previous Implementation was constantly computing byte/column count
from the begining of the line, leading to a non-linear complexity
with respect to the length of a line.

Fixes #2146
This commit is contained in:
Maxime Coste 2018-07-08 18:51:11 +10:00
parent 51ec1194f1
commit 1b5f665664

View File

@ -643,6 +643,8 @@ struct WrapHighlighter : Highlighter
static constexpr StringView ms_id = "wrap"; static constexpr StringView ms_id = "wrap";
struct SplitPos{ ByteCount byte; ColumnCount column; };
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override
{ {
if (contains(context.disabled_ids, ms_id)) if (contains(context.disabled_ids, ms_id))
@ -665,58 +667,59 @@ struct WrapHighlighter : Highlighter
const ByteCount line_length = buffer[buf_line].length(); const ByteCount line_length = buffer[buf_line].length();
const ColumnCount indent = m_preserve_indent ? line_indent(buffer, tabstop, buf_line) : 0_col; const ColumnCount indent = m_preserve_indent ? line_indent(buffer, tabstop, buf_line) : 0_col;
auto coord = next_split_coord(buffer, wrap_column, tabstop, buf_line); auto pos = next_split_pos(buffer, wrap_column, tabstop, buf_line, {0, 0});
if (buffer.is_valid(coord) and not buffer.is_end(coord)) if (pos.byte == line_length)
continue;
for (auto atom_it = it->begin();
pos.byte != line_length and atom_it != it->end(); )
{ {
for (auto atom_it = it->begin(); const BufferCoord coord{buf_line, pos.byte};
coord.column != line_length and atom_it != it->end(); ) if (!atom_it->has_buffer_range() or
coord < atom_it->begin() or coord >= atom_it->end())
{ {
if (!atom_it->has_buffer_range() or ++atom_it;
coord < atom_it->begin() or coord >= atom_it->end()) continue;
{
++atom_it;
continue;
}
auto& line = *it;
if (coord > atom_it->begin())
atom_it = ++line.split(atom_it, coord);
DisplayLine new_line{ AtomList{ atom_it, line.end() } };
line.erase(atom_it, line.end());
ColumnCount prefix_len = 0;
if (marker_len != 0 and marker_len < wrap_column)
{
new_line.insert(new_line.begin(), {m_marker, face_marker});
prefix_len = marker_len;
}
if (indent > marker_len and indent < wrap_column)
{
auto it = new_line.insert(new_line.begin() + (marker_len > 0), {buffer, coord, coord});
it->replace(String{' ', indent - marker_len});
prefix_len = indent;
}
if (it+1 - display_buffer.lines().begin() == win_height)
{
if (cursor >= new_line.range().begin) // strip first lines if cursor is not visible
{
display_buffer.lines().erase(display_buffer.lines().begin(), display_buffer.lines().begin()+1);
--it;
}
else
{
display_buffer.lines().erase(it+1, display_buffer.lines().end());
return;
}
}
it = display_buffer.lines().insert(it+1, new_line);
coord = next_split_coord(buffer, wrap_column - prefix_len, tabstop, coord);
atom_it = it->begin();
} }
auto& line = *it;
if (coord > atom_it->begin())
atom_it = ++line.split(atom_it, coord);
DisplayLine new_line{ AtomList{ atom_it, line.end() } };
line.erase(atom_it, line.end());
ColumnCount prefix_len = 0;
if (marker_len != 0 and marker_len < wrap_column)
{
new_line.insert(new_line.begin(), {m_marker, face_marker});
prefix_len = marker_len;
}
if (indent > marker_len and indent < wrap_column)
{
auto it = new_line.insert(new_line.begin() + (marker_len > 0), {buffer, coord, coord});
it->replace(String{' ', indent - marker_len});
prefix_len = indent;
}
if (it+1 - display_buffer.lines().begin() == win_height)
{
if (cursor >= new_line.range().begin) // strip first lines if cursor is not visible
{
display_buffer.lines().erase(display_buffer.lines().begin(), display_buffer.lines().begin()+1);
--it;
}
else
{
display_buffer.lines().erase(it+1, display_buffer.lines().end());
return;
}
}
it = display_buffer.lines().insert(it+1, new_line);
pos = next_split_pos(buffer, wrap_column - prefix_len, tabstop, buf_line, pos);
atom_it = it->begin();
} }
} }
} }
@ -736,13 +739,13 @@ struct WrapHighlighter : Highlighter
auto line_wrap_count = [&](LineCount line, ColumnCount indent) { auto line_wrap_count = [&](LineCount line, ColumnCount indent) {
LineCount count = 0; LineCount count = 0;
BufferCoord coord{line};
const ByteCount line_length = buffer[line].length(); const ByteCount line_length = buffer[line].length();
SplitPos pos{0, 0};
while (true) while (true)
{ {
coord = next_split_coord(buffer, wrap_column - (coord.column == 0 ? 0_col : indent), pos = next_split_pos(buffer, wrap_column - (pos.byte == 0 ? 0_col : indent),
tabstop, coord); tabstop, line, pos);
if (coord.column == line_length) if (pos.byte == line_length)
break; break;
++count; ++count;
} }
@ -776,22 +779,21 @@ struct WrapHighlighter : Highlighter
if (buf_line == cursor.line) if (buf_line == cursor.line)
{ {
BufferCoord coord{buf_line}; SplitPos pos{0, 0};
for (LineCount count = 0; true; ++count) for (LineCount count = 0; true; ++count)
{ {
auto split_coord = next_split_coord(buffer, wrap_column - (coord.column != 0 ? prefix_len : 0_col), auto next_pos = next_split_pos(buffer, wrap_column - (pos.byte != 0 ? prefix_len : 0_col),
tabstop, coord); tabstop, buf_line, pos);
if (split_coord.column > cursor.column) if (next_pos.byte > cursor.column)
{ {
setup.cursor_pos = DisplayCoord{ setup.cursor_pos = DisplayCoord{
win_line + count, win_line + count,
get_column(buffer, tabstop, cursor) - get_column(buffer, tabstop, cursor) -
get_column(buffer, tabstop, coord) + pos.column + (pos.byte != 0 ? indent : 0_col)
(coord.column != 0 ? indent : 0_col)
}; };
break; break;
} }
coord = split_coord; pos = next_pos;
} }
kak_assert(setup.cursor_pos.column >= 0 and setup.cursor_pos.column < setup.window_range.column); kak_assert(setup.cursor_pos.column >= 0 and setup.cursor_pos.column < setup.window_range.column);
} }
@ -817,28 +819,48 @@ struct WrapHighlighter : Highlighter
unique_ids.push_back(ms_id); unique_ids.push_back(ms_id);
} }
BufferCoord next_split_coord(const Buffer& buffer, ColumnCount wrap_column, int tabstop, BufferCoord coord) const SplitPos next_split_pos(const Buffer& buffer, ColumnCount wrap_column, int tabstop, LineCount line, SplitPos current) const
{ {
auto column = get_column(buffer, tabstop, coord); const ColumnCount target_column = current.column + wrap_column;
auto col = get_byte_to_column( StringView content = buffer[line];
buffer, tabstop, {coord.line, column + wrap_column});
StringView line = buffer[coord.line];
if (col == coord.column) // Can happen if we try to wrap on a tab char
col = line.byte_count_to(line.char_count_to(coord.column)+1);
BufferCoord split_coord{coord.line, col}; SplitPos pos = current;
while (pos.byte < content.length() and pos.column < target_column)
if (m_word_wrap)
{ {
utf8::iterator<const char*> it{&line[col], line}; if (content[pos.byte] == '\t')
while (it != line.end() and it != line.begin() and is_word<WORD>(*it)) {
const ColumnCount next_column = (pos.column / tabstop + 1) * tabstop;
if (next_column > target_column and pos.byte != current.byte) // the target column was in the tab
break;
pos.column = next_column;
++pos.byte;
}
else
{
const char* it = &content[pos.byte];
const ColumnCount width = codepoint_width(utf8::read_codepoint(it, content.end()));
if (pos.column + width > target_column and pos.byte != current.byte) // the target column was in the char
break;
pos.column += width;
pos.byte = (int)(it - content.begin());
}
}
if (m_word_wrap and pos.byte < content.length()) // find a word boundary before current position
{
utf8::iterator<const char*> it{&content[pos.byte], content};
while (it != content.begin() and is_word<WORD>(*it))
--it; --it;
if (it != line.begin() and it != &line[col] and if (it != content.begin() and it != &content[pos.byte] and
(it+1) > &line[coord.column]) (it+1) > &content[current.byte])
split_coord.column = (it+1).base() - line.begin(); {
const ByteCount word_split = (it+1).base() - content.begin();
pos.column -= content.substr(word_split, pos.byte - word_split).column_length();
pos.byte = word_split;
}
} }
return split_coord; return pos;
}; };
ColumnCount line_indent(const Buffer& buffer, int tabstop, LineCount line) const ColumnCount line_indent(const Buffer& buffer, int tabstop, LineCount line) const