diff --git a/NEWS b/NEWS index 381a5e6c..7c34a484 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ lnav v0.6.3: * Added a 'log_mark' column to the log tables that indicates whether or not a log message is bookmarked. The field is writable, so you can bookmark lines using an SQL UPDATE query. + * Added syntax-highlighting when editing SQL queries or search regexes. Fixes: * Performance improvements. @@ -27,6 +28,8 @@ lnav v0.6.3: so that you can write expressions like "log_level > 'warning'". * The log_time datetime format now matches what is returned by "datetime('now')" so that collating works correctly. + * If a search string is not valid PCRE syntax, a search is done for + the exact string instead of just returning an error. * Static-linking has been cleaned up. * OpenSSL is no longer a requirement. * Alpha support for Windows/cygwin. diff --git a/src/Makefile.am b/src/Makefile.am index 2ce9dbeb..994a4609 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -81,6 +81,7 @@ noinst_HEADERS = \ pcrepp.hh \ piper_proc.hh \ readline_curses.hh \ + readline_highlighters.hh \ sequence_matcher.hh \ sequence_sink.hh \ session_data.hh \ @@ -139,6 +140,7 @@ libdiag_a_SOURCES = \ data_scanner.cc \ data_parser.cc \ readline_curses.cc \ + readline_highlighters.cc \ session_data.cc \ sequence_matcher.cc \ shared_buffer.cc \ diff --git a/src/Makefile.in b/src/Makefile.in index 064f357b..8021f2cd 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -119,9 +119,9 @@ am_libdiag_a_OBJECTS = ansi_scrubber.$(OBJEXT) bookmarks.$(OBJEXT) \ logfile.$(OBJEXT) logfile_sub_source.$(OBJEXT) \ network-extension-functions.$(OBJEXT) data_scanner.$(OBJEXT) \ data_parser.$(OBJEXT) readline_curses.$(OBJEXT) \ - session_data.$(OBJEXT) sequence_matcher.$(OBJEXT) \ - shared_buffer.$(OBJEXT) sqlite-extension-func.$(OBJEXT) \ - statusview_curses.$(OBJEXT) \ + readline_highlighters.$(OBJEXT) session_data.$(OBJEXT) \ + sequence_matcher.$(OBJEXT) shared_buffer.$(OBJEXT) \ + sqlite-extension-func.$(OBJEXT) statusview_curses.$(OBJEXT) \ string-extension-functions.$(OBJEXT) pcrepp.$(OBJEXT) \ piper_proc.$(OBJEXT) sql_util.$(OBJEXT) \ state-extension-functions.$(OBJEXT) strnatcmp.$(OBJEXT) \ @@ -407,6 +407,7 @@ noinst_HEADERS = \ pcrepp.hh \ piper_proc.hh \ readline_curses.hh \ + readline_highlighters.hh \ sequence_matcher.hh \ sequence_sink.hh \ session_data.hh \ @@ -465,6 +466,7 @@ libdiag_a_SOURCES = \ data_scanner.cc \ data_parser.cc \ readline_curses.cc \ + readline_highlighters.cc \ session_data.cc \ sequence_matcher.cc \ shared_buffer.cc \ @@ -708,6 +710,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pcrepp.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/piper_proc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readline_curses.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readline_highlighters.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sequence_matcher.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_data.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shared_buffer.Po@am__quote@ diff --git a/src/bottom_status_source.hh b/src/bottom_status_source.hh index 38f4b584..02f71149 100644 --- a/src/bottom_status_source.hh +++ b/src/bottom_status_source.hh @@ -200,7 +200,7 @@ public: new_role = view_colors::VCR_STATUS; sf.set_cylon(false); } - this->bss_error.clear(); + // this->bss_error.clear(); sf.set_role(new_role); this->update_marks(tc); }; diff --git a/src/help.txt b/src/help.txt index d645f5d5..8cdf96d3 100644 --- a/src/help.txt +++ b/src/help.txt @@ -255,6 +255,10 @@ through the file. http://perldoc.perl.org/perlre.html + If the search string is not valid PCRE, a search + is done for the exact string instead of doing a + regex search. + : Execute an internal command. The commands are listed below. History is also supported in this context as well as tab-completion for commands and diff --git a/src/lnav.cc b/src/lnav.cc index 43438113..00b12b35 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -107,6 +107,7 @@ #include "sqlite-extension-func.h" #include "term_extra.hh" #include "log_data_helper.hh" +#include "readline_highlighters.hh" #include "yajlpp.hh" @@ -254,6 +255,8 @@ public: vis_line_t y, attr_line_t &value_out) { + view_colors &vc = view_colors::singleton(); + if (!this->fos_active || this->fos_log_helper.ldh_parser.get() == NULL) { return false; } @@ -317,10 +320,16 @@ public: } if (row < (int)this->fos_log_helper.ldh_line_values.size()) { - str = " " + this->fos_log_helper.ldh_line_values[row].lv_name; + string &name = this->fos_log_helper.ldh_line_values[row].lv_name; + + str = " " + name; int padding = this->fos_key_size - str.length() + 3; + value_out.get_attrs().push_back(string_attr( + line_range(3, 3 + name.length()), + &view_curses::VC_STYLE, + vc.attrs_for_ident(name))); str.append(padding, ' '); str += " = " + this->fos_log_helper.ldh_line_values[row].to_string(); } @@ -332,9 +341,15 @@ public: iter = this->fos_log_helper.ldh_parser->dp_pairs.begin(); std::advance(iter, row); - str = " " + this->fos_log_helper.ldh_namer->cn_names[row]; + string &name = this->fos_log_helper.ldh_namer->cn_names[row]; + + str = " " + name; int padding = this->fos_key_size - str.length() + 3; + value_out.get_attrs().push_back(string_attr( + line_range(3, 3 + name.length()), + &view_curses::VC_STYLE, + vc.attrs_for_ident(name))); str.append(padding, ' '); str += " = " + this->fos_log_helper.ldh_parser->get_element_string( iter->e_sub_elements->back()); @@ -1292,6 +1307,7 @@ static void handle_paging_key(int ch) case 'n': tc->set_top(bm[&textview_curses::BM_SEARCH].next(tc->get_top())); + lnav_data.ld_bottom_source.grep_error(""); lnav_data.ld_rl_view->set_alt_value( "Press '" ANSI_BOLD(">") "' or '" ANSI_BOLD("<") "' to scroll horizontally to a search result"); @@ -1299,6 +1315,7 @@ static void handle_paging_key(int ch) case 'N': tc->set_top(bm[&textview_curses::BM_SEARCH].prev(tc->get_top())); + lnav_data.ld_bottom_source.grep_error(""); lnav_data.ld_rl_view->set_alt_value( "Press '" ANSI_BOLD(">") "' or '" ANSI_BOLD("<") "' to scroll horizontally to a search result"); @@ -2332,15 +2349,17 @@ int sql_callback(sqlite3_stmt *stmt) return retval; } -void execute_search(lnav_view_t view, const std::string ®ex) +void execute_search(lnav_view_t view, const std::string ®ex_orig) { auto_ptr &gc = lnav_data.ld_search_child[view]; textview_curses & tc = lnav_data.ld_views[view]; + std::string regex = regex_orig; if ((gc.get() == NULL) || (regex != lnav_data.ld_last_search[view])) { const char *errptr; - pcre * code; + pcre * code = NULL; int eoff; + bool quoted = false; tc.match_reset(); @@ -2360,35 +2379,50 @@ void execute_search(lnav_view_t view, const std::string ®ex) &errptr, &eoff, NULL)) == NULL) { - lnav_data.ld_bottom_source. - grep_error("regexp error: " + string(errptr)); + lnav_data.ld_bottom_source.grep_error( + "regexp error: " + string(errptr)); + + quoted = true; + regex = pcrecpp::RE::QuoteMeta(regex); + + log_info("invalid search regex, using quoted: %s", regex.c_str()); + if ((code = pcre_compile(regex.c_str(), + PCRE_CASELESS, + &errptr, + &eoff, + NULL)) == NULL) { + log_error("Unable to compile quoted regex: %s", regex.c_str()); + } } - else { - textview_curses::highlighter - hl(code, false, view_colors::VCR_SEARCH); + if (code != NULL) { + textview_curses::highlighter hl( + code, false, view_colors::VCR_SEARCH); + + if (!quoted) { + lnav_data.ld_bottom_source.grep_error(""); + } lnav_data.ld_bottom_source.set_prompt(""); textview_curses::highlight_map_t &hm = tc.get_highlights(); hm["$search"] = hl; auto_ptr gp(new grep_proc(code, - tc, - lnav_data.ld_max_fd, - lnav_data.ld_read_fds)); + tc, + lnav_data.ld_max_fd, + lnav_data.ld_read_fds)); gp->queue_request(grep_line_t(tc.get_top())); if (tc.get_top() > 0) { - gp->queue_request(grep_line_t(0), - grep_line_t(tc.get_top())); + gp->queue_request(grep_line_t(0), grep_line_t(tc.get_top())); } gp->start(); gp->set_sink(&tc); tc.set_follow_search(true); - auto_ptr gh(new grep_highlighter(gp, "$search", - hm)); + auto_ptr gh( + new grep_highlighter(gp, "$search", hm)); gc = gh; } } @@ -3121,7 +3155,10 @@ static void looper(void) readline_curses rlc; int lpc; - search_context.set_append_character(0); + search_context + .set_append_character(0) + .set_highlighter(readline_regex_highlighter); + sql_context.set_highlighter(readline_sqlite_highlighter); listview_curses::action::broadcaster &sb = lnav_data.ld_scroll_broadcaster; diff --git a/src/pcrepp.cc b/src/pcrepp.cc index 8ab51416..289e2ff3 100644 --- a/src/pcrepp.cc +++ b/src/pcrepp.cc @@ -31,6 +31,8 @@ #include "config.h" +#include + #include "pcrepp.hh" const int JIT_STACK_MIN_SIZE = 32 * 1024; diff --git a/src/pcrepp.hh b/src/pcrepp.hh index c013153f..1fc63f01 100644 --- a/src/pcrepp.hh +++ b/src/pcrepp.hh @@ -125,6 +125,16 @@ public: return (*this)[name.c_str()]; }; + capture_t *first_valid(void) const { + for (int lpc = 1; lpc < this->pc_count; lpc++) { + if (this->pc_captures[lpc].is_valid()) { + return &this->pc_captures[lpc]; + } + } + + return NULL; + }; + protected: pcre_context(capture_t *captures, int max_count) : pc_pcre(NULL), pc_captures(captures), pc_max_count(max_count), pc_count(0) { }; @@ -484,4 +494,5 @@ private: int p_name_len; pcre_named_capture *p_named_entries; }; + #endif diff --git a/src/readline_curses.cc b/src/readline_curses.cc index 6eae4a71..0e1c9e63 100644 --- a/src/readline_curses.cc +++ b/src/readline_curses.cc @@ -55,6 +55,7 @@ #include +#include "pcrepp.hh" #include "auto_mem.hh" #include "lnav_log.hh" #include "ansi_scrubber.hh" @@ -697,4 +698,67 @@ void readline_curses::do_update(void) this->set_x(0); } vt52_curses::do_update(); + + if (this->rc_active_context != -1) { + readline_context *rc = this->rc_contexts[this->rc_active_context]; + readline_highlighter_t hl = rc->get_highlighter(); + + if (hl != NULL) { + char line[1024]; + attr_line_t al; + string &str = al.get_string(); + int rc; + + rc = mvwinnstr(this->vc_window, + this->get_actual_y(), 0, + line, sizeof(line)); + + str = string(line, rc); + + hl(al, this->vc_x); + view_curses::mvwattrline(this->vc_window, + this->get_actual_y(), 0, al, line_range(0, rc)); + + wmove(this->vc_window, this->get_actual_y(), this->vc_x); + } + } +#if 0 + { + static pcrepp captures("(? pc; + + pcre_input pi(line, 0, rc); + + while (captures.match(pc, pi)) { + pcre_context::capture_t *cap = pc.all(); + + mvwchgat(this->vc_window, this->get_actual_y(), cap->c_begin, 1, 0, + view_colors::ansi_color_pair_index(COLOR_RED, COLOR_BLACK), + NULL); + } + + if (line[this->vc_x] == ')' && line[this->vc_x - 1] != '\\') { + for (int lpc = this->vc_x; lpc > 0; lpc--) { + if (line[lpc] == '(' && line[lpc - 1] != '\\') { + mvwchgat(this->vc_window, this->get_actual_y(), lpc, 1, A_BOLD | A_REVERSE, + view_colors::ansi_color_pair_index(COLOR_CYAN, COLOR_BLACK), + NULL); + break; + } + } + } + if (line[this->vc_x] == '(' && line[this->vc_x - 1] != '\\') { + for (int lpc = this->vc_x; lpc < rc; lpc++) { + if (line[lpc] == ')' && line[lpc - 1] != '\\') { + mvwchgat(this->vc_window, this->get_actual_y(), lpc, 1, A_BOLD | A_REVERSE, + view_colors::ansi_color_pair_index(COLOR_CYAN, COLOR_BLACK), + NULL); + break; + } + } + } + + } +#endif } diff --git a/src/readline_curses.hh b/src/readline_curses.hh index 0b10e0f1..e950b58c 100644 --- a/src/readline_curses.hh +++ b/src/readline_curses.hh @@ -52,6 +52,8 @@ #include "auto_fd.hh" #include "vt52_curses.hh" +typedef void (*readline_highlighter_t)(attr_line_t &line, int x); + /** * Container for information related to different readline contexts. Since * lnav uses readline for different inputs, we need a way to keep things like @@ -147,8 +149,19 @@ public: return this->rc_case_sensitive; }; - void set_append_character(int ch) { + readline_context &set_append_character(int ch) { this->rc_append_character = ch; + + return *this; + }; + + readline_context &set_highlighter(readline_highlighter_t hl) { + this->rc_highlighter = hl; + return *this; + }; + + readline_highlighter_t get_highlighter() const { + return this->rc_highlighter; }; private: @@ -164,6 +177,7 @@ private: std::map > rc_prototypes; bool rc_case_sensitive; int rc_append_character; + readline_highlighter_t rc_highlighter; }; /** diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc new file mode 100644 index 00000000..c1139ed8 --- /dev/null +++ b/src/readline_highlighters.cc @@ -0,0 +1,361 @@ +/** + * 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 "config.h" + +#include "pcrepp.hh" +#include "sql_util.hh" +#include "readline_highlighters.hh" + +using namespace std; + +static bool check_re_prev(const string &line, int x) +{ + bool retval = false; + + if ((x > 0 && line[x - 1] != ')' && line[x - 1] != ']') && + (x < 2 || line[x - 2] != '\\')) { + retval = true; + } + + return retval; +} + +static void find_matching_bracket(attr_line_t &al, int x, char left, char right) +{ + static int matching_bracket_attrs = ( + A_BOLD|A_REVERSE|view_colors::ansi_color_pair(COLOR_GREEN, COLOR_BLACK)); + static int missing_bracket_attrs = ( + A_BOLD|A_REVERSE|view_colors::ansi_color_pair(COLOR_RED, COLOR_BLACK)); + + const string &line = al.get_string(); + + if (line[x] == right && line[x - 1] != '\\') { + for (int lpc = x; lpc > 0; lpc--) { + if (line[lpc] == left && line[lpc - 1] != '\\') { + al.get_attrs().push_back(string_attr( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + matching_bracket_attrs)); + break; + } + } + } + + if (line[x] == left && line[x - 1] != '\\') { + for (int lpc = x; lpc < line.length(); lpc++) { + if (line[lpc] == right && line[lpc - 1] != '\\') { + al.get_attrs().push_back(string_attr( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + matching_bracket_attrs)); + break; + } + } + } + + int depth = 0, last_left = -1; + + for (int lpc = 1; lpc < line.length(); lpc++) { + if (line[lpc] == left && line[lpc - 1] != '\\') { + depth += 1; + last_left = lpc; + } + else if (line[lpc] == right && line[lpc - 1] != '\\') { + if (depth > 0) { + depth -= 1; + } + else { + al.get_attrs().push_back(string_attr( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + missing_bracket_attrs)); + } + } + } + + if (depth > 0) { + al.get_attrs().push_back(string_attr( + line_range(last_left, last_left + 1), + &view_curses::VC_STYLE, + missing_bracket_attrs)); + } +} + +void readline_regex_highlighter(attr_line_t &al, int x) +{ + static int special_char = ( + A_BOLD|view_colors::ansi_color_pair(COLOR_CYAN, COLOR_BLACK)); + static int class_attrs = ( + A_BOLD|view_colors::ansi_color_pair(COLOR_MAGENTA, COLOR_BLACK)); + static int repeated_char_attrs = ( + view_colors::ansi_color_pair(COLOR_YELLOW, COLOR_BLACK)); + static int bracket_attrs = ( + view_colors::ansi_color_pair(COLOR_GREEN, COLOR_BLACK)); + static int error_attrs = ( + A_BOLD|A_REVERSE|view_colors::ansi_color_pair(COLOR_RED, COLOR_BLACK)); + + static const char *brackets[] = { + "[]", + "{}", + "()", + + NULL + }; + + string &line = al.get_string(); + + for (int lpc = 1; lpc < line.length(); lpc++) { + if (line[lpc - 1] != '\\') { + switch (line[lpc]) { + case '^': + case '$': + case '*': + case '+': + case '|': + case '.': + al.get_attrs().push_back(string_attr( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + special_char)); + + if ((line[lpc] == '*' || line[lpc] == '+') && + check_re_prev(line, lpc)) { + al.get_attrs().push_back(string_attr( + line_range(lpc - 1, lpc), + &view_curses::VC_STYLE, + repeated_char_attrs)); + } + break; + case '?': { + struct line_range lr(lpc, lpc + 1); + + if (line[lpc - 1] == '(') { + switch (line[lpc + 1]) { + case ':': + case '!': + case '>': + case '<': + case '#': + lr.lr_end += 1; + break; + } + al.get_attrs().push_back(string_attr( + lr, + &view_curses::VC_STYLE, + bracket_attrs)); + } + else { + al.get_attrs().push_back(string_attr( + lr, + &view_curses::VC_STYLE, + special_char)); + + if (check_re_prev(line, lpc)) { + al.get_attrs().push_back(string_attr( + line_range(lpc - 1, lpc), + &view_curses::VC_STYLE, + repeated_char_attrs)); + } + } + break; + } + + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + al.get_attrs().push_back(string_attr( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + bracket_attrs)); + break; + + } + } + if (line[lpc - 1] == '\\') { + switch (line[lpc]) { + case 'A': + case 'b': + case 'w': + case 'W': + case 's': + case 'S': + case 'd': + case 'D': + case 'Z': + case 'z': + al.get_attrs().push_back(string_attr( + line_range(lpc - 1, lpc + 1), + &view_curses::VC_STYLE, + class_attrs)); + break; + case ' ': + al.get_attrs().push_back(string_attr( + line_range(lpc - 1, lpc + 1), + &view_curses::VC_STYLE, + error_attrs)); + break; + default: + if (isdigit(line[lpc])) { + al.get_attrs().push_back(string_attr( + line_range(lpc - 1, lpc + 1), + &view_curses::VC_STYLE, + special_char)); + } + break; + } + } + } + + for (int lpc = 0; brackets[lpc]; lpc++) { + find_matching_bracket(al, x, brackets[lpc][0], brackets[lpc][1]); + } +} + +static string sql_keyword_re(void) +{ + string retval = "(?:"; + + for (int lpc = 0; sql_keywords[lpc]; lpc++) { + if (lpc > 0) { + retval.append("|"); + } + retval.append("\\b"); + retval.append(sql_keywords[lpc]); + retval.append("\\b"); + } + retval += ")"; + + return retval; +} + +void readline_sqlite_highlighter(attr_line_t &al, int x) +{ + static int keyword_attrs = ( + A_BOLD|view_colors::ansi_color_pair(COLOR_CYAN, COLOR_BLACK)); + static int symbol_attrs = ( + view_colors::ansi_color_pair(COLOR_MAGENTA, COLOR_BLACK)); + static int string_attrs = ( + view_colors::ansi_color_pair(COLOR_GREEN, COLOR_BLACK)); + static int error_attrs = ( + A_BOLD|A_REVERSE|view_colors::ansi_color_pair(COLOR_RED, COLOR_BLACK)); + + static string keyword_re_str = sql_keyword_re(); + static pcrepp keyword_pcre(keyword_re_str.c_str(), PCRE_CASELESS); + static pcrepp string_literal_pcre("'[^']*('(?:'[^']*')*|$)"); + static pcrepp ident_pcre("(\\b[a-z_]\\w*)|\"([^\"]+)\"|\\[([^\\]]+)]", PCRE_CASELESS); + + static const char *brackets[] = { + "[]", + "()", + + NULL + }; + + view_colors &vc = view_colors::singleton(); + pcre_context_static<30> pc; + pcre_input pi(al.get_string()); + string &line = al.get_string(); + + while (ident_pcre.match(pc, pi)) { + pcre_context::capture_t *cap = pc.first_valid(); + int attrs = vc.attrs_for_ident(pi.get_substr_start(cap), cap->length()); + struct line_range lr(cap->c_begin, cap->c_end); + + if (line[cap->c_end] == '(') { + + } + else if (!lr.contains(x) && !lr.contains(x - 1)) { + al.get_attrs().push_back(string_attr( + lr, &view_curses::VC_STYLE, attrs)); + } + } + + pi.reset(line); + + while (keyword_pcre.match(pc, pi)) { + pcre_context::capture_t *cap = pc.all(); + + al.get_attrs().push_back(string_attr( + line_range(cap->c_begin, cap->c_end), + &view_curses::VC_STYLE, + keyword_attrs)); + } + + for (int lpc = 0; lpc < line.length(); lpc++) { + switch (line[lpc]) { + case '*': + case '<': + case '>': + case '=': + case '!': + case '-': + case '+': + al.get_attrs().push_back(string_attr( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + symbol_attrs)); + break; + } + } + + pi.reset(line); + + while (string_literal_pcre.match(pc, pi)) { + pcre_context::capture_t *cap = pc.all(); + struct line_range lr(cap->c_begin, cap->c_end); + string_attrs_t &sa = al.get_attrs(); + string_attrs_t::const_iterator iter; + + while ((iter = find_string_attr(sa, lr)) != sa.end()) { + sa.erase(iter); + } + + if (line[cap->c_end - 1] != '\'') { + sa.push_back(string_attr( + line_range(cap->c_begin, cap->c_begin + 1), + &view_curses::VC_STYLE, + error_attrs)); + lr.lr_start += 1; + } + sa.push_back(string_attr( + lr, + &view_curses::VC_STYLE, + string_attrs)); + } + + for (int lpc = 0; brackets[lpc]; lpc++) { + find_matching_bracket(al, x, brackets[lpc][0], brackets[lpc][1]); + } +} diff --git a/src/readline_highlighters.hh b/src/readline_highlighters.hh new file mode 100644 index 00000000..b9318dfe --- /dev/null +++ b/src/readline_highlighters.hh @@ -0,0 +1,41 @@ +/** + * 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.hh + */ + +#ifndef __readline_highlighters_hh +#define __readline_highlighters_hh + +#include "view_curses.hh" + +void readline_regex_highlighter(attr_line_t &line, int x); + +void readline_sqlite_highlighter(attr_line_t &line, int x); + +#endif diff --git a/src/view_curses.cc b/src/view_curses.cc index 45e8be9a..4191e09d 100644 --- a/src/view_curses.cc +++ b/src/view_curses.cc @@ -34,6 +34,7 @@ #include #include +#include "lnav_log.hh" #include "view_curses.hh" using namespace std; @@ -76,7 +77,7 @@ void view_curses::mvwattrline(WINDOW *window, int y, int x, attr_line_t &al, - struct line_range &lr, + const struct line_range &lr, view_colors::role_t base_role) { int text_attrs, attrs, line_width; diff --git a/src/view_curses.hh b/src/view_curses.hh index 3581963a..9db1e908 100644 --- a/src/view_curses.hh +++ b/src/view_curses.hh @@ -61,6 +61,8 @@ #include #include +#include "lnav_log.hh" + #define KEY_CTRL_G 7 #define KEY_CTRL_R 18 #define KEY_CTRL_W 23 @@ -161,6 +163,10 @@ struct line_range { return this->lr_start <= pos && pos < this->lr_end; }; + bool contains(const struct line_range &other) const { + return this->contains(other.lr_start) && other.lr_end <= this->lr_end; + }; + bool operator<(const struct line_range &rhs) const { if (this->lr_start < rhs.lr_start) { return true; } @@ -189,12 +195,12 @@ class string_attr_type { }; typedef string_attr_type *string_attr_type_t; struct string_attr { - string_attr(struct line_range &lr, string_attr_type_t type, void *val) + string_attr(const struct line_range &lr, string_attr_type_t type, void *val) : sa_range(lr), sa_type(type) { this->sa_value.sav_ptr = val; }; - string_attr(struct line_range &lr, string_attr_type_t type, int val = 0) + string_attr(const struct line_range &lr, string_attr_type_t type, int val = 0) : sa_range(lr), sa_type(type) { this->sa_value.sav_int = val; }; @@ -229,6 +235,21 @@ find_string_attr(const string_attrs_t &sa, string_attr_type_t type) return iter; } +inline string_attrs_t::const_iterator +find_string_attr(const string_attrs_t &sa, const struct line_range &lr) +{ + string_attrs_t::const_iterator iter; + struct line_range retval; + + for (iter = sa.begin(); iter != sa.end(); ++iter) { + if (lr.contains(iter->sa_range)) { + break; + } + } + + return iter; +} + inline struct line_range find_string_attr_range(const string_attrs_t &sa, string_attr_type_t type) { @@ -490,6 +511,10 @@ public: return this->vc_role_colors[VCR_HIGHLIGHT_START + (abs(index) % HL_COLOR_COUNT)]; }; + int attrs_for_ident(const std::string &str) const { + return this->attrs_for_ident(str.c_str(), str.length()); + }; + static inline int ansi_color_pair_index(int fg, int bg) { return VC_ANSI_START + ((fg * 8) + bg); @@ -569,7 +594,7 @@ public: int y, int x, attr_line_t &al, - struct line_range &lr, + const struct line_range &lr, view_colors::role_t base_role = view_colors::VCR_TEXT); };