summaryrefslogtreecommitdiffstats
path: root/src/base/snippet_highlighters.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/snippet_highlighters.cc')
-rw-r--r--src/base/snippet_highlighters.cc344
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