From 954373d3cfa9d168f7949f12cb7b2e730d18de99 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 27 Apr 2020 13:36:12 +1000 Subject: [PATCH] Support multi-line replace-ranges This likely has lots of rough edges, but should be an initial proof of concept to support folding. --- src/display_buffer.cc | 12 +- src/display_buffer.hh | 15 ++- src/highlighters.cc | 122 +++++++++++------- .../cmd | 1 + .../in | 30 +++++ .../rc | 3 + .../script | 7 + test/highlight/replace-multiline-range/cmd | 1 + test/highlight/replace-multiline-range/in | 5 + test/highlight/replace-multiline-range/rc | 2 + test/highlight/replace-multiline-range/script | 7 + 11 files changed, 153 insertions(+), 52 deletions(-) create mode 100644 test/highlight/replace-multiline-range-pulls-new-lines/cmd create mode 100644 test/highlight/replace-multiline-range-pulls-new-lines/in create mode 100644 test/highlight/replace-multiline-range-pulls-new-lines/rc create mode 100644 test/highlight/replace-multiline-range-pulls-new-lines/script create mode 100644 test/highlight/replace-multiline-range/cmd create mode 100644 test/highlight/replace-multiline-range/in create mode 100644 test/highlight/replace-multiline-range/rc create mode 100644 test/highlight/replace-multiline-range/script diff --git a/src/display_buffer.cc b/src/display_buffer.cc index 3d68356f1..8ade40478 100644 --- a/src/display_buffer.cc +++ b/src/display_buffer.cc @@ -111,6 +111,14 @@ DisplayLine::iterator DisplayLine::split(iterator it, ColumnCount count) return split(it, pos); } +DisplayLine::iterator DisplayLine::split(BufferCoord pos) +{ + auto it = find_if(begin(), end(), [pos](const DisplayAtom& a) { return 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()) @@ -118,7 +126,9 @@ DisplayLine::iterator DisplayLine::insert(iterator it, DisplayAtom atom) 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)); + auto res = m_atoms.insert(it, std::move(atom)); + compute_range(); + return res; } void DisplayLine::push_back(DisplayAtom atom) diff --git a/src/display_buffer.hh b/src/display_buffer.hh index abd1a2447..308b19939 100644 --- a/src/display_buffer.hh +++ b/src/display_buffer.hh @@ -126,16 +126,27 @@ public: // returns an iterator to the first atom iterator split(iterator it, ColumnCount pos); + iterator split(BufferCoord pos); + iterator insert(iterator it, DisplayAtom atom); + + template + iterator insert(iterator it, It beg, It end) + { + auto res = m_atoms.insert(it, beg, end); + compute_range(); + return res; + } + iterator erase(iterator beg, iterator end); - void push_back(DisplayAtom atom); + void push_back(DisplayAtom atom); // remove first_col from the begining of the line, and make sure // the line is less that col_count character bool trim(ColumnCount first_col, ColumnCount col_count); // Merge together consecutive atoms sharing the same display attributes - void optimize(); + void optimize(); private: void compute_range(); BufferRange m_range = { { INT_MAX, INT_MAX }, { INT_MIN, INT_MIN } }; diff --git a/src/highlighters.cc b/src/highlighters.cc index 3ead3f6f5..3ccbc0972 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -92,41 +92,37 @@ void replace_range(DisplayBuffer& display_buffer, if (begin > end or end < display_buffer.range().begin or begin > display_buffer.range().end) return; - for (auto& line : display_buffer.lines()) + auto& lines = display_buffer.lines(); + auto first_it = std::lower_bound(lines.begin(), lines.end(), begin, [](const DisplayLine& l, const BufferCoord& c) { return l.range().end < c; }); + if (first_it == lines.end()) + return; + + auto first_atom_it = std::find_if(first_it->begin(), first_it->end(), [&begin](const DisplayAtom& a) { return a.has_buffer_range() and a.end() > begin; }); + first_atom_it = first_it->split(begin); + + auto last_it = std::lower_bound(first_it, lines.end(), end, [](const DisplayLine& l, const BufferCoord& c) { return l.range().end < c; }); + + if (first_it == last_it) { - auto& range = line.range(); - if ((begin == end) and begin == range.end) - return func(line, line.end()); - - if (range.end <= begin or end < range.begin) - continue; - - int beg_idx = -1, end_idx = -1; - for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) - { - if (not atom_it->has_buffer_range() or - end < atom_it->begin() or begin >= atom_it->end()) - continue; - - if (begin >= atom_it->begin()) - { - if (begin > atom_it->begin()) - atom_it = ++line.split(atom_it, begin); - beg_idx = atom_it - line.begin(); - } - if (end == atom_it->begin()) - end_idx = atom_it - line.begin(); - else if (end <= atom_it->end()) - { - if (end < atom_it->end()) - atom_it = line.split(atom_it, end); - end_idx = (atom_it - line.begin()) + 1; - } - } - - if (beg_idx != -1 and end_idx != -1) - return func(line, line.erase(line.begin() + beg_idx, line.begin() + end_idx)); + auto first_atom_idx = first_atom_it - first_it->begin(); + auto end_atom_it = first_it->split(end); + first_atom_it = first_it->erase(first_it->begin() + first_atom_idx, end_atom_it); } + else + { + first_atom_it = first_it->erase(first_atom_it, first_it->end()); + if (last_it != lines.end()) + { + auto end_atom_it = last_it->split(end); + end_atom_it = last_it->erase(last_it->begin(), end_atom_it); + + first_atom_it = first_it->insert(first_atom_it, end_atom_it, last_it->end()); + ++last_it; + } + first_it = --lines.erase(first_it+1, last_it); + } + + func(*first_it, first_atom_it); } void apply_highlighter(HighlightContext context, @@ -1495,11 +1491,11 @@ InclusiveBufferRange option_from_string(Meta::Type, String return { std::min(first, last), std::max(first, last) }; } -template +template struct OptionBasedHighlighter : Highlighter { OptionBasedHighlighter(String option_name) - : Highlighter{HighlightPass::Colorize} + : Highlighter{pass} , m_option_name{std::move(option_name)} {} static std::unique_ptr create(HighlighterParameters params, Highlighter*) @@ -1514,7 +1510,7 @@ struct OptionBasedHighlighter : Highlighter return std::make_unique(option_name); } - OptionType& get_option(const HighlightContext& context) + OptionType& get_option(const HighlightContext& context) const { return context.context.options()[m_option_name].get_mutable(); } @@ -1565,32 +1561,35 @@ private: } }; -struct ReplaceRangesHighlighter : OptionBasedHighlighter +struct ReplaceRangesHighlighter : OptionBasedHighlighter { using ReplaceRangesHighlighter::OptionBasedHighlighter::OptionBasedHighlighter; private: + static bool is_valid(Buffer& buffer, BufferCoord c) + { + return c.line >= 0 and c.column >= 0 and c.line < buffer.line_count() and c.column <= buffer[c.line].length(); + }; + + static bool is_fully_selected(const SelectionList& sels, const InclusiveBufferRange& range) + { + auto it = std::lower_bound(sels.begin(), sels.end(), range.first, [](const Selection& s, const BufferCoord& c) { return s.max() < c; }); + if (it == sels.end()) + return true; + return it->min() > range.last or (it->min() <= range.first and it->max() >= range.last); + }; + void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override { auto& buffer = context.context.buffer(); + auto& sels = context.context.selections(); auto& range_and_faces = get_option(context); update_ranges(buffer, range_and_faces.prefix, range_and_faces.list); - auto is_valid = [&buffer](BufferCoord c) { - return c.line >= 0 and c.column >= 0 and c.line < buffer.line_count() and c.column <= buffer[c.line].length(); - }; - - auto is_fully_selected = [&sels=context.context.selections()](const InclusiveBufferRange& range) { - auto it = std::lower_bound(sels.begin(), sels.end(), range.first, [](const Selection& s, const BufferCoord& c) { return s.max() < c; }); - if (it == sels.end()) - return true; - return it->min() > range.last or (it->min() <= range.first and it->max() >= range.last); - }; - for (auto& [range, spec] : range_and_faces.list) { try { - if (!is_valid(range.first) or (!is_empty(range) and !is_valid(range.last)) or !is_fully_selected(range)) + if (!is_valid(buffer, range.first) or (!is_empty(range) and !is_valid(buffer, range.last)) or !is_fully_selected(sels, range)) continue; auto replacement = parse_display_line(spec, context.context.faces()); auto end = is_empty(range) ? range.first : buffer.char_next(range.last); @@ -1607,6 +1606,31 @@ private: {} } } + + void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override + { + auto& buffer = context.context.buffer(); + auto& sels = context.context.selections(); + auto& range_and_faces = get_option(context); + update_ranges(buffer, range_and_faces.prefix, range_and_faces.list); + + for (auto& [range, spec] : range_and_faces.list) + { + if (!is_valid(buffer, range.first) or (!is_empty(range) and !is_valid(buffer, range.last)) or !is_fully_selected(sels, range)) + continue; + + if (range.first.line < setup.window_pos.line and range.last.line >= setup.window_pos.line) + setup.window_pos.line = range.first.line; + + if (range.last.line >= setup.window_pos.line and + range.first.line <= setup.window_pos.line + setup.window_range.line and + range.first.line != range.last.line) + { + auto removed_count = range.last.line - range.first.line; + setup.window_range.line += removed_count; + } + } + } }; HighlightPass parse_passes(StringView str) diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/cmd b/test/highlight/replace-multiline-range-pulls-new-lines/cmd new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/highlight/replace-multiline-range-pulls-new-lines/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/in b/test/highlight/replace-multiline-range-pulls-new-lines/in new file mode 100644 index 000000000..023f1d8db --- /dev/null +++ b/test/highlight/replace-multiline-range-pulls-new-lines/in @@ -0,0 +1,30 @@ +01 +02 +03 +04 +05 +06 +07 +08 +09 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/rc b/test/highlight/replace-multiline-range-pulls-new-lines/rc new file mode 100644 index 000000000..fd2a7b6e8 --- /dev/null +++ b/test/highlight/replace-multiline-range-pulls-new-lines/rc @@ -0,0 +1,3 @@ +declare-option range-specs ranges +add-highlighter window/ replace-ranges ranges +set-option buffer ranges %val{timestamp} 2.1,6.2|.. diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/script b/test/highlight/replace-multiline-range-pulls-new-lines/script new file mode 100644 index 000000000..6f63b6c35 --- /dev/null +++ b/test/highlight/replace-multiline-range-pulls-new-lines/script @@ -0,0 +1,7 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": ".." }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "26\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "27\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "28\u000a" }]], { "fg": "default", "bg": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "attributes": [] }] }' +ui_out '{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] }' +ui_out '{ "jsonrpc": "2.0", "method": "info_hide", "params": [] }' +ui_out '{ "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": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "attributes": [] }] }' +ui_out '{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 0 }] }' +ui_out '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' diff --git a/test/highlight/replace-multiline-range/cmd b/test/highlight/replace-multiline-range/cmd new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/highlight/replace-multiline-range/cmd @@ -0,0 +1 @@ + diff --git a/test/highlight/replace-multiline-range/in b/test/highlight/replace-multiline-range/in new file mode 100644 index 000000000..ce5cbbaa8 --- /dev/null +++ b/test/highlight/replace-multiline-range/in @@ -0,0 +1,5 @@ + +12345 +67890 +%(12345 +67890) diff --git a/test/highlight/replace-multiline-range/rc b/test/highlight/replace-multiline-range/rc new file mode 100644 index 000000000..d16727619 --- /dev/null +++ b/test/highlight/replace-multiline-range/rc @@ -0,0 +1,2 @@ +declare-option range-specs test_ranges %val{timestamp} '2.1,3.5|..' '4.2,5.4|..' +add-highlighter window/ replace-ranges test_ranges diff --git a/test/highlight/replace-multiline-range/script b/test/highlight/replace-multiline-range/script new file mode 100644 index 000000000..d15654fa5 --- /dev/null +++ b/test/highlight/replace-multiline-range/script @@ -0,0 +1,7 @@ +ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": ".." }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "white", "bg": "blue", "attributes": [] }, "contents": "1" }, { "face": { "fg": "white", "bg": "blue", "attributes": [] }, "contents": ".." }, { "face": { "fg": "black", "bg": "white", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "\u000a" }]], { "fg": "default", "bg": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "attributes": [] }] }' +ui_out '{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] }' +ui_out '{ "jsonrpc": "2.0", "method": "info_hide", "params": [] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "attributes": [] }, "contents": "out 5:5 " }, { "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": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "attributes": [] }] }' +ui_out '{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 2, "column": 3 }] }' +ui_out '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'