summaryrefslogtreecommitdiffstats
path: root/src/readline_highlighters.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/readline_highlighters.cc')
-rw-r--r--src/readline_highlighters.cc480
1 files changed, 480 insertions, 0 deletions
diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc
new file mode 100644
index 0000000..58c537c
--- /dev/null
+++ b/src/readline_highlighters.cc
@@ -0,0 +1,480 @@
+/**
+ * Copyright (c) 2014, 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.
+ *
+ * @file readline_highlighters.cc
+ */
+
+#include "readline_highlighters.hh"
+
+#include "base/attr_line.builder.hh"
+#include "base/snippet_highlighters.hh"
+#include "base/string_util.hh"
+#include "config.h"
+#include "pcrepp/pcre2pp.hh"
+#include "shlex.hh"
+#include "sql_help.hh"
+#include "sql_util.hh"
+#include "view_curses.hh"
+
+static void readline_sqlite_highlighter_int(attr_line_t& al,
+ int x,
+ line_range sub);
+
+static bool
+is_bracket(const std::string& str, int index, bool is_lit)
+{
+ 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 (size_t 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 (size_t 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));
+ }
+}
+
+void
+readline_regex_highlighter(attr_line_t& al, int x)
+{
+ lnav::snippets::regex_highlighter(
+ al, x, line_range{1, (int) al.get_string().size()});
+}
+
+void
+readline_command_highlighter_int(attr_line_t& al, int x, line_range sub)
+{
+ static const auto RE_PREFIXES = lnav::pcre2pp::code::from_const(
+ R"(^:(filter-in|filter-out|delete-filter|enable-filter|disable-filter|highlight|clear-highlight|create-search-table\s+[^\s]+\s+))");
+ static const auto SH_PREFIXES = lnav::pcre2pp::code::from_const(
+ "^:(eval|open|append-to|write-to|write-csv-to|write-json-to)");
+ static const auto SQL_PREFIXES
+ = lnav::pcre2pp::code::from_const("^:(filter-expr|mark-expr)");
+ static const auto IDENT_PREFIXES
+ = lnav::pcre2pp::code::from_const("^:(tag|untag|delete-tags)");
+ static const auto COLOR_PREFIXES
+ = lnav::pcre2pp::code::from_const("^:(config)");
+ static const auto COLOR_RE = lnav::pcre2pp::code::from_const(
+ "(#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3}))");
+
+ attr_line_builder alb(al);
+ const auto& line = al.get_string();
+ auto in_frag
+ = string_fragment::from_str_range(line, sub.lr_start, sub.lr_end);
+ size_t ws_index;
+
+ ws_index = line.find(' ', sub.lr_start);
+ auto command = line.substr(sub.lr_start, ws_index);
+ if (ws_index != std::string::npos) {
+ alb.overlay_attr(line_range(sub.lr_start + 1, ws_index),
+ VC_ROLE.value(role_t::VCR_KEYWORD));
+
+ if (RE_PREFIXES.find_in(in_frag).ignore_error()) {
+ lnav::snippets::regex_highlighter(
+ al, x, line_range{(int) ws_index, sub.lr_end});
+ }
+ if (SH_PREFIXES.find_in(in_frag).ignore_error()) {
+ readline_shlex_highlighter_int(
+ al, x, line_range{(int) ws_index, sub.lr_end});
+ }
+ if (SQL_PREFIXES.find_in(in_frag).ignore_error()) {
+ readline_sqlite_highlighter_int(
+ al, x, line_range{(int) ws_index, sub.lr_end});
+ }
+ }
+ if (COLOR_PREFIXES.find_in(in_frag).ignore_error()) {
+ COLOR_RE.capture_from(in_frag).for_each(
+ [&alb](lnav::pcre2pp::match_data& md) {
+ styling::color_unit::from_str(md[0].value())
+ .then([&](const auto& rgb_fg) {
+ auto color
+ = view_colors::singleton().match_color(rgb_fg);
+ alb.template overlay_attr(to_line_range(md[0].value()),
+ VC_STYLE.value(text_attrs{
+ A_BOLD,
+ color,
+ }));
+ });
+ });
+ }
+ if (IDENT_PREFIXES.find_in(in_frag).ignore_error()
+ && ws_index != std::string::npos)
+ {
+ size_t start = ws_index, last;
+
+ do {
+ for (; start < sub.length() && isspace(line[start]); start++)
+ ;
+ for (last = start; last < sub.length() && !isspace(line[last]);
+ last++)
+ ;
+ struct line_range lr {
+ (int) start, (int) last
+ };
+
+ if (lr.length() > 0 && !lr.contains(x) && !lr.contains(x - 1)) {
+ std::string value(lr.substr(line), lr.sublen(line));
+
+ if ((command == ":tag" || command == ":untag"
+ || command == ":delete-tags")
+ && !startswith(value, "#"))
+ {
+ value = "#" + value;
+ }
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_IDENTIFIER));
+ }
+
+ start = last;
+ } while (start < sub.length());
+ }
+}
+
+void
+readline_command_highlighter(attr_line_t& al, int x)
+{
+ readline_command_highlighter_int(
+ al, x, line_range{0, (int) al.get_string().length()});
+}
+
+static void
+readline_sqlite_highlighter_int(attr_line_t& al, int x, line_range sub)
+{
+ static const char* brackets[] = {
+ "[]",
+ "()",
+
+ nullptr,
+ };
+
+ attr_line_builder alb(al);
+ const auto& line = al.get_string();
+
+ auto anno_sql = al.subline(sub.lr_start, sub.length());
+ anno_sql.get_attrs().clear();
+ annotate_sql_statement(anno_sql);
+
+ for (const auto& attr : anno_sql.al_attrs) {
+ line_range lr{
+ sub.lr_start + attr.sa_range.lr_start,
+ sub.lr_start + attr.sa_range.lr_end,
+ };
+ if (attr.sa_type == &SQL_COMMAND_ATTR
+ || attr.sa_type == &SQL_KEYWORD_ATTR)
+ {
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_KEYWORD));
+ } else if (attr.sa_type == &SQL_IDENTIFIER_ATTR) {
+ if (!attr.sa_range.contains(x) && attr.sa_range.lr_end != x) {
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_IDENTIFIER));
+ }
+ } else if (attr.sa_type == &SQL_FUNCTION_ATTR) {
+ alb.overlay_attr(
+ line_range{lr.lr_start, (int) line.find('(', lr.lr_start)},
+ VC_ROLE.value(role_t::VCR_SYMBOL));
+ } else if (attr.sa_type == &SQL_NUMBER_ATTR) {
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_NUMBER));
+ } else if (attr.sa_type == &SQL_STRING_ATTR) {
+ if (lr.length() > 1 && al.al_string[lr.lr_end - 1] == '\'') {
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_STRING));
+ } else {
+ alb.overlay_attr_for_char(
+ lr.lr_start, VC_STYLE.value(text_attrs{A_REVERSE}));
+ alb.overlay_attr_for_char(lr.lr_start,
+ VC_ROLE.value(role_t::VCR_ERROR));
+ }
+ } else if (attr.sa_type == &SQL_OPERATOR_ATTR) {
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_SYMBOL));
+ } else if (attr.sa_type == &SQL_COMMENT_ATTR) {
+ alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_COMMENT));
+ }
+ }
+
+ for (int lpc = 0; brackets[lpc]; lpc++) {
+ find_matching_bracket(al, x, sub, brackets[lpc][0], brackets[lpc][1]);
+ }
+}
+
+void
+readline_sqlite_highlighter(attr_line_t& al, int x)
+{
+ readline_sqlite_highlighter_int(
+ al, x, line_range{0, (int) al.get_string().length()});
+}
+
+void
+readline_shlex_highlighter_int(attr_line_t& al, int x, line_range sub)
+{
+ attr_line_builder alb(al);
+ const auto& str = al.get_string();
+ string_fragment cap;
+ shlex_token_t token;
+ nonstd::optional<int> quote_start;
+ shlex lexer(string_fragment{al.al_string.data(), sub.lr_start, sub.lr_end});
+
+ while (lexer.tokenize(cap, token)) {
+ switch (token) {
+ case shlex_token_t::ST_ERROR:
+ alb.overlay_attr(line_range(sub.lr_start + cap.sf_begin,
+ sub.lr_start + cap.sf_end),
+ VC_STYLE.value(text_attrs{A_REVERSE}));
+ alb.overlay_attr(line_range(sub.lr_start + cap.sf_begin,
+ sub.lr_start + cap.sf_end),
+ VC_ROLE.value(role_t::VCR_ERROR));
+ break;
+ case shlex_token_t::ST_TILDE:
+ case shlex_token_t::ST_ESCAPE:
+ alb.overlay_attr(line_range(sub.lr_start + cap.sf_begin,
+ sub.lr_start + cap.sf_end),
+ VC_ROLE.value(role_t::VCR_SYMBOL));
+ break;
+ case shlex_token_t::ST_DOUBLE_QUOTE_START:
+ case shlex_token_t::ST_SINGLE_QUOTE_START:
+ quote_start = sub.lr_start + cap.sf_begin;
+ break;
+ case shlex_token_t::ST_DOUBLE_QUOTE_END:
+ case shlex_token_t::ST_SINGLE_QUOTE_END:
+ alb.overlay_attr(
+ line_range(quote_start.value(), sub.lr_start + cap.sf_end),
+ VC_ROLE.value(role_t::VCR_STRING));
+ quote_start = nonstd::nullopt;
+ break;
+ case shlex_token_t::ST_VARIABLE_REF:
+ case shlex_token_t::ST_QUOTED_VARIABLE_REF: {
+ int extra = token == shlex_token_t::ST_VARIABLE_REF ? 0 : 1;
+ auto ident = str.substr(sub.lr_start + cap.sf_begin + 1 + extra,
+ cap.length() - 1 - extra * 2);
+ alb.overlay_attr(
+ line_range(sub.lr_start + cap.sf_begin,
+ sub.lr_start + cap.sf_begin + 1 + extra),
+ VC_ROLE.value(role_t::VCR_SYMBOL));
+ alb.overlay_attr(
+ line_range(sub.lr_start + cap.sf_begin + 1 + extra,
+ sub.lr_start + cap.sf_end - extra),
+ VC_ROLE.value(
+ x == sub.lr_start + cap.sf_end
+ || (cap.sf_begin <= x && x < cap.sf_end)
+ ? role_t::VCR_SYMBOL
+ : role_t::VCR_IDENTIFIER));
+ if (extra) {
+ alb.overlay_attr_for_char(
+ sub.lr_start + cap.sf_end - 1,
+ VC_ROLE.value(role_t::VCR_SYMBOL));
+ }
+ break;
+ }
+ case shlex_token_t::ST_WHITESPACE:
+ break;
+ }
+ }
+
+ if (quote_start) {
+ alb.overlay_attr_for_char(quote_start.value(),
+ VC_ROLE.value(role_t::VCR_ERROR));
+ }
+}
+
+void
+readline_shlex_highlighter(attr_line_t& al, int x)
+{
+ readline_shlex_highlighter_int(
+ al, x, line_range{0, (int) al.al_string.length()});
+}
+
+static void
+readline_lnav_highlighter_int(attr_line_t& al, int x, line_range sub)
+{
+ switch (al.al_string[sub.lr_start]) {
+ case ':':
+ readline_command_highlighter_int(al, x, sub);
+ break;
+ case ';':
+ readline_sqlite_highlighter_int(al,
+ x,
+ line_range{
+ sub.lr_start + 1,
+ sub.lr_end,
+ });
+ break;
+ case '|':
+ break;
+ case '/':
+ lnav::snippets::regex_highlighter(al,
+ x,
+ line_range{
+ sub.lr_start + 1,
+ sub.lr_end,
+ });
+ break;
+ }
+}
+
+void
+readline_lnav_highlighter(attr_line_t& al, int x)
+{
+ static const auto COMMENT_RE = lnav::pcre2pp::code::from_const(R"(^\s*#)");
+
+ attr_line_builder alb(al);
+ size_t start = 0, lf_pos;
+ nonstd::optional<size_t> section_start;
+
+ while ((lf_pos = al.get_string().find('\n', start)) != std::string::npos) {
+ line_range line{(int) start, (int) lf_pos};
+
+ if (line.empty()) {
+ start = lf_pos + 1;
+ continue;
+ }
+
+ auto line_frag = string_fragment::from_str_range(
+ al.al_string, line.lr_start, line.lr_end);
+
+ auto find_res = COMMENT_RE.find_in(line_frag).ignore_error();
+ if (find_res.has_value()) {
+ if (section_start) {
+ readline_lnav_highlighter_int(al,
+ x,
+ line_range{
+ (int) section_start.value(),
+ line.lr_start,
+ });
+ section_start = nonstd::nullopt;
+ }
+ alb.overlay_attr(line_range{find_res->f_all.sf_begin, line.lr_end},
+ VC_ROLE.value(role_t::VCR_COMMENT));
+ } else {
+ switch (al.al_string[line.lr_start]) {
+ case ':':
+ case ';':
+ case '|':
+ case '/':
+ if (section_start) {
+ readline_lnav_highlighter_int(
+ al,
+ x,
+ line_range{
+ (int) section_start.value(),
+ line.lr_start,
+ });
+ }
+
+ section_start = line.lr_start;
+ break;
+ }
+ }
+
+ start = lf_pos;
+ }
+
+ if (start == 0) {
+ section_start = 0;
+ }
+
+ if (section_start) {
+ readline_lnav_highlighter_int(al,
+ x,
+ line_range{
+ (int) section_start.value(),
+ (int) al.al_string.length(),
+ });
+ }
+}