From 89f016d871c9ccc516e244e8bc594defe3678daf Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 12 Oct 2017 14:38:19 +0800 Subject: [PATCH] Refactor column highlighter to make it more robust MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support arbitrary orders for column highlighters (it was previously failing when column highlighters were not applied in column order). Fix show_matching tab handling at the same time (horizontal scrolling, tab characters and show_matching were behaving badly). Window highlighting now runs user highlighters, then built-ins for each phases, instead of running all phases for user highlighters, then all phases for built-ins. We now consider unprintable character to be 1-column width as we know we will display them as "�". Fixes #1615 Fixes #1023 --- src/display_buffer.cc | 21 ++++-- src/highlighters.cc | 76 ++++++++------------- src/unicode.hh | 2 +- src/window.cc | 5 +- test/highlight/column/multi-columns/cmd | 0 test/highlight/column/multi-columns/display | 6 ++ test/highlight/column/multi-columns/in | 4 ++ test/highlight/column/multi-columns/rc | 5 ++ 8 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 test/highlight/column/multi-columns/cmd create mode 100644 test/highlight/column/multi-columns/display create mode 100644 test/highlight/column/multi-columns/in create mode 100644 test/highlight/column/multi-columns/rc diff --git a/src/display_buffer.cc b/src/display_buffer.cc index e6ae2a34..3a1449d7 100644 --- a/src/display_buffer.cc +++ b/src/display_buffer.cc @@ -92,15 +92,22 @@ DisplayLine::iterator DisplayLine::split(iterator it, BufferCoord pos) return m_atoms.insert(it, std::move(atom)); } -DisplayLine::iterator DisplayLine::split(iterator it, ColumnCount pos) +DisplayLine::iterator DisplayLine::split(iterator it, ColumnCount count) { - kak_assert(it->type() == DisplayAtom::Text); - kak_assert(pos > 0); - kak_assert(pos < it->length()); + kak_assert(count > 0); + kak_assert(count < it->length()); - DisplayAtom atom(it->m_text.substr(0, pos).str()); - it->m_text = it->m_text.substr(pos).str(); - return m_atoms.insert(it, std::move(atom)); + 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(); + return split(it, pos); } DisplayLine::iterator DisplayLine::insert(iterator it, DisplayAtom atom) diff --git a/src/highlighters.cc b/src/highlighters.cc index d21af0af..62d9b903 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -615,59 +615,37 @@ HighlighterAndId create_column_highlighter(HighlighterParameters params) if (column < 0) return; - const Buffer& buffer = context.buffer(); - const int tabstop = context.options()["tabstop"].get(); auto face = get_face(facespec); + auto win_column = context.window().position().column; for (auto& line : display_buffer.lines()) { - const LineCount buf_line = line.range().begin.line; - const ByteCount byte_col = get_byte_to_column(buffer, tabstop, {buf_line, column}); - const BufferCoord coord{buf_line, byte_col}; + auto target_col = column - win_column; + if (target_col < 0) + return; + bool found = false; - if (buffer.is_valid(coord) and not buffer.is_end(coord)) + auto first_buf = find_if(line, [](auto& atom) { return atom.has_buffer_range(); }); + for (auto atom_it = first_buf; atom_it != line.end(); ++atom_it) { - for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) + const auto atom_len = atom_it->length(); + if (target_col < atom_len) { - if (atom_it->type() != DisplayAtom::Range) - continue; - - kak_assert(atom_it->begin().line == buf_line); - if (coord >= atom_it->begin() and coord < atom_it->end()) - { - if (coord > atom_it->begin()) - atom_it = ++line.split(atom_it, coord); - if (buffer.next(coord) < atom_it->end()) - atom_it = line.split(atom_it, buffer.next(coord)); - - apply_face(face)(*atom_it); - found = true; - break; - } + if (target_col > 0) + atom_it = ++line.split(atom_it, target_col); + if (atom_it->length() > 1) + atom_it = line.split(atom_it, 1_col); + atom_it->face = merge_faces(atom_it->face, face); + found = true; + break; } + target_col -= atom_len; } - if (not found) - { - ColumnCount last_buffer_col = context.window().position().column; - for (auto& atom : line) - { - if (atom.has_buffer_range()) - { - auto pos = atom.end(); - if (pos.column == 0) - pos = {pos.line-1, buffer[pos.line-1].length()}; - if (pos != atom.begin()) - last_buffer_col = get_column(buffer, tabstop, pos); - } - } + if (found) + continue; - ColumnCount count = column - last_buffer_col; - if (count >= 0) - { - if (count > 0) - line.push_back({String{' ', count}}); - line.push_back({String{" "}, face}); - } - } + if (target_col > 0) + line.push_back({String{' ', target_col}}); + line.push_back({" ", face}); } }; @@ -930,6 +908,7 @@ void show_whitespaces(const Context& context, HighlightPass, DisplayBuffer& disp const int tabstop = context.options()["tabstop"].get(); auto whitespaceface = get_face("Whitespace"); auto& buffer = context.buffer(); + auto win_column = context.window().position().column; for (auto& line : display_buffer.lines()) { for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) @@ -952,9 +931,10 @@ void show_whitespaces(const Context& context, HighlightPass, DisplayBuffer& disp if (cp == '\t') { - int column = (int)get_column(buffer, tabstop, coord); - int count = tabstop - (column % tabstop); - atom_it->replace(tab + String(tabpad[(CharCount)0], CharCount{count-1})); + const ColumnCount column = get_column(buffer, tabstop, coord); + const ColumnCount count = tabstop - (column % tabstop) - + std::max(win_column - column, 0_col); + atom_it->replace(tab + String(tabpad[(CharCount)0], count - tab.column_length())); } else if (cp == ' ') atom_it->replace(spc.str()); @@ -996,7 +976,7 @@ HighlighterAndId show_whitespaces_factory(HighlighterParameters params) get_param("lf", "¬"), get_param("nbsp", "⍽")); - return {"show_whitespaces", make_highlighter(std::move(func))}; + return {"show_whitespaces", make_highlighter(std::move(func), HighlightPass::Move)}; } struct LineNumbersHighlighter : Highlighter diff --git a/src/unicode.hh b/src/unicode.hh index 94b0523a..01fcfb23 100644 --- a/src/unicode.hh +++ b/src/unicode.hh @@ -58,7 +58,7 @@ inline ColumnCount codepoint_width(Codepoint c) noexcept if (c == '\n') return 1; const auto width = wcwidth((wchar_t)c); - return width > 0 ? width : 0; + return width >= 0 ? width : 1; } enum class CharCategories diff --git a/src/window.cc b/src/window.cc index c8d86e14..09652a9b 100644 --- a/src/window.cc +++ b/src/window.cc @@ -136,15 +136,18 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context) buffer()[buffer_line].length() : get_byte_to_column(buffer(), tabstop, {buffer_line, m_position.column + m_range.column}); + // The display buffer always has at least one buffer atom, which might be empty if + // beg_byte == end_byte lines.emplace_back(AtomList{ {buffer(), {buffer_line, beg_byte}, {buffer_line, end_byte}} }); } m_display_buffer.compute_range(); BufferRange range{{0,0}, buffer().end_coord()}; for (auto pass : { HighlightPass::Wrap, HighlightPass::Move, HighlightPass::Colorize }) + { m_highlighters.highlight(context, pass, m_display_buffer, range); - for (auto pass : { HighlightPass::Wrap, HighlightPass::Move, HighlightPass::Colorize }) m_builtin_highlighters.highlight(context, pass, m_display_buffer, range); + } m_display_buffer.optimize(); diff --git a/test/highlight/column/multi-columns/cmd b/test/highlight/column/multi-columns/cmd new file mode 100644 index 00000000..e69de29b diff --git a/test/highlight/column/multi-columns/display b/test/highlight/column/multi-columns/display new file mode 100644 index 00000000..1caf4399 --- /dev/null +++ b/test/highlight/column/multi-columns/display @@ -0,0 +1,6 @@ +{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "1│" }, { "face": { "fg": "black", "bg": "white", "attributes": [] }, "contents": "a" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }, { "face": { "fg": "default", "bg": "red", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "blue", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "green", "attributes": [] }, "contents": " " }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "2│" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "red", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "blue", "attributes": [] }, "contents": "a" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "b" }, { "face": { "fg": "default", "bg": "green", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "3│" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "ab" }, { "face": { "fg": "default", "bg": "red", "attributes": [] }, "contents": "c" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }, { "face": { "fg": "default", "bg": "blue", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "green", "attributes": [] }, "contents": " " }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "4│" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "ab" }, { "face": { "fg": "default", "bg": "red", "attributes": [] }, "contents": "c" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "d" }, { "face": { "fg": "default", "bg": "blue", "attributes": [] }, "contents": "\u000a" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "green", "attributes": [] }, "contents": " " }]], { "fg": "default", "bg": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "attributes": [] }] } +{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] } +{ "jsonrpc": "2.0", "method": "info_hide", "params": [] } +{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "out 1:1 " }, { "face": { "fg": "black", "bg": "yellow", "attributes": [] }, "contents": "" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": " - unnamed0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "attributes": [] }] } +{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 2 }] } +{ "jsonrpc": "2.0", "method": "refresh", "params": [true] } diff --git a/test/highlight/column/multi-columns/in b/test/highlight/column/multi-columns/in new file mode 100644 index 00000000..a70bc8f5 --- /dev/null +++ b/test/highlight/column/multi-columns/in @@ -0,0 +1,4 @@ +a + ab +abc +abcd diff --git a/test/highlight/column/multi-columns/rc b/test/highlight/column/multi-columns/rc new file mode 100644 index 00000000..01eb14f6 --- /dev/null +++ b/test/highlight/column/multi-columns/rc @@ -0,0 +1,5 @@ +add-highlighter number_lines +set window tabstop 4 +add-highlighter column 3 default,red +add-highlighter column 7 default,green +add-highlighter column 5 default,blue