diff options
Diffstat (limited to 'src/base/snippet_highlighters.cc')
-rw-r--r-- | src/base/snippet_highlighters.cc | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/src/base/snippet_highlighters.cc b/src/base/snippet_highlighters.cc new file mode 100644 index 0000000..058fa41 --- /dev/null +++ b/src/base/snippet_highlighters.cc @@ -0,0 +1,344 @@ +/** + * Copyright (c) 2022, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "snippet_highlighters.hh" + +#include "attr_line.builder.hh" +#include "pcrepp/pcre2pp.hh" +#include "view_curses.hh" + +namespace lnav { +namespace snippets { + +static bool +is_bracket(const std::string& str, int index, bool is_lit) +{ + if (index == 0) { + return true; + } + + if (is_lit && str[index - 1] == '\\') { + return true; + } + if (!is_lit && str[index - 1] != '\\') { + return true; + } + return false; +} + +static void +find_matching_bracket( + attr_line_t& al, int x, line_range sub, char left, char right) +{ + bool is_lit = (left == 'Q'); + attr_line_builder alb(al); + const auto& line = al.get_string(); + int depth = 0; + + if (x < sub.lr_start || x > sub.lr_end) { + return; + } + + if (line[x] == right && is_bracket(line, x, is_lit)) { + for (int lpc = x - 1; lpc >= sub.lr_start; lpc--) { + if (line[lpc] == right && is_bracket(line, lpc, is_lit)) { + depth += 1; + } else if (line[lpc] == left && is_bracket(line, lpc, is_lit)) { + if (depth == 0) { + alb.overlay_attr_for_char( + lpc, VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr_for_char(lpc, + VC_ROLE.value(role_t::VCR_OK)); + break; + } + depth -= 1; + } + } + } + + if (line[x] == left && is_bracket(line, x, is_lit)) { + for (int lpc = x + 1; lpc < sub.lr_end; lpc++) { + if (line[lpc] == left && is_bracket(line, lpc, is_lit)) { + depth += 1; + } else if (line[lpc] == right && is_bracket(line, lpc, is_lit)) { + if (depth == 0) { + alb.overlay_attr_for_char( + lpc, VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr_for_char(lpc, + VC_ROLE.value(role_t::VCR_OK)); + break; + } + depth -= 1; + } + } + } + + nonstd::optional<int> first_left; + + depth = 0; + + for (auto lpc = sub.lr_start; lpc < sub.lr_end; lpc++) { + if (line[lpc] == left && is_bracket(line, lpc, is_lit)) { + depth += 1; + if (!first_left) { + first_left = lpc; + } + } else if (line[lpc] == right && is_bracket(line, lpc, is_lit)) { + if (depth > 0) { + depth -= 1; + } else { + auto lr = line_range(is_lit ? lpc - 1 : lpc, lpc + 1); + alb.overlay_attr( + lr, VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_ERROR)); + } + } + } + + if (depth > 0) { + auto lr + = line_range(is_lit ? first_left.value() - 1 : first_left.value(), + first_left.value() + 1); + alb.overlay_attr(lr, VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_ERROR)); + } +} + +static bool +check_re_prev(const std::string& line, int x) +{ + bool retval = false; + + if ((x > 0 && line[x - 1] != ')' && line[x - 1] != ']' && line[x - 1] != '*' + && line[x - 1] != '?' && line[x - 1] != '+') + && (x < 2 || line[x - 2] != '\\')) + { + retval = true; + } + + return retval; +} + +static char +safe_read(const std::string& str, std::string::size_type index) +{ + if (index < str.length()) { + return str.at(index); + } + + return 0; +} + +void +regex_highlighter(attr_line_t& al, int x, line_range sub) +{ + static const char* brackets[] = { + "[]", + "{}", + "()", + "QE", + + nullptr, + }; + + const auto& line = al.get_string(); + attr_line_builder alb(al); + bool backslash_is_quoted = false; + + for (auto lpc = sub.lr_start; lpc < sub.lr_end; lpc++) { + if (lpc == 0 || line[lpc - 1] != '\\') { + switch (line[lpc]) { + case '^': + case '$': + case '*': + case '+': + case '|': + case '.': + alb.overlay_attr_for_char( + lpc, VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + + if ((line[lpc] == '*' || line[lpc] == '+') + && check_re_prev(line, lpc)) + { + alb.overlay_attr_for_char( + lpc - 1, VC_ROLE.value(role_t::VCR_RE_REPEAT)); + } + break; + case '?': { + struct line_range lr(lpc, lpc + 1); + + if (lpc == sub.lr_start || (lpc - sub.lr_start) == 0) { + alb.overlay_attr_for_char( + lpc, + VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr_for_char( + lpc, VC_ROLE.value(role_t::VCR_ERROR)); + } else if (line[lpc - 1] == '(') { + switch (line[lpc + 1]) { + case ':': + case '!': + case '#': + lr.lr_end += 1; + break; + } + alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_OK)); + if (line[lpc + 1] == '<') { + alb.overlay_attr( + line_range(lpc + 1, lpc + 2), + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + } + } else { + alb.overlay_attr(lr, + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + + if (check_re_prev(line, lpc)) { + alb.overlay_attr_for_char( + lpc - 1, VC_ROLE.value(role_t::VCR_RE_REPEAT)); + } + } + break; + } + case '>': { + static const auto CAP_RE + = lnav::pcre2pp::code::from_const(R"(\(\?\<\w+$)"); + + auto capture_start + = string_fragment::from_str_range( + line, sub.lr_start, lpc) + .find_left_boundary(lpc - sub.lr_start - 1, + string_fragment::tag1{'('}); + + auto cap_find_res + = CAP_RE.find_in(capture_start).ignore_error(); + + if (cap_find_res) { + alb.overlay_attr( + line_range(capture_start.sf_begin + + cap_find_res->f_all.sf_begin + 3, + capture_start.sf_begin + + cap_find_res->f_all.sf_end), + VC_ROLE.value(role_t::VCR_IDENTIFIER)); + alb.overlay_attr(line_range(lpc, lpc + 1), + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + } + break; + } + + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + alb.overlay_attr_for_char(lpc, + VC_ROLE.value(role_t::VCR_OK)); + break; + } + } + if (lpc > 0 && line[lpc - 1] == '\\') { + if (backslash_is_quoted) { + backslash_is_quoted = false; + continue; + } + switch (line[lpc]) { + case '\\': + backslash_is_quoted = true; + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + break; + case 'd': + case 'D': + case 'h': + case 'H': + case 'N': + case 'R': + case 's': + case 'S': + case 'v': + case 'V': + case 'w': + case 'W': + case 'X': + + case 'A': + case 'b': + case 'B': + case 'G': + case 'Z': + case 'z': + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_SYMBOL)); + break; + case ' ': + alb.overlay_attr( + line_range(lpc - 1, lpc + 1), + VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_ERROR)); + break; + case '0': + case 'x': + if (safe_read(line, lpc + 1) == '{') { + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + } else if (isdigit(safe_read(line, lpc + 1)) + && isdigit(safe_read(line, lpc + 2))) + { + alb.overlay_attr(line_range(lpc - 1, lpc + 3), + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + } else { + alb.overlay_attr( + line_range(lpc - 1, lpc + 1), + VC_STYLE.value(text_attrs{A_BOLD | A_REVERSE})); + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_ERROR)); + } + break; + case 'Q': + case 'E': + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_OK)); + break; + default: + if (isdigit(line[lpc])) { + alb.overlay_attr(line_range(lpc - 1, lpc + 1), + VC_ROLE.value(role_t::VCR_RE_SPECIAL)); + } + break; + } + } + } + + for (int lpc = 0; brackets[lpc]; lpc++) { + find_matching_bracket(al, x, sub, brackets[lpc][0], brackets[lpc][1]); + } +} + +} // namespace snippets +} // namespace lnav |