You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lnav/src/filter_sub_source.cc

661 lines
23 KiB
C++

/**
* Copyright (c) 2018, 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 "filter_sub_source.hh"
#include "base/enum_util.hh"
#include "base/func_util.hh"
#include "base/opt_util.hh"
#include "config.h"
#include "lnav.hh"
#include "readline_highlighters.hh"
#include "readline_possibilities.hh"
filter_sub_source::filter_sub_source()
{
this->fss_regex_context.set_highlighter(readline_regex_highlighter)
.set_append_character(0);
this->fss_editor.add_context(filter_lang_t::REGEX, this->fss_regex_context);
this->fss_sql_context.set_highlighter(readline_sqlite_highlighter)
.set_append_character(0);
this->fss_editor.add_context(filter_lang_t::SQL, this->fss_sql_context);
this->fss_editor.set_change_action(
bind_mem(&filter_sub_source::rl_change, this));
this->fss_editor.set_perform_action(
bind_mem(&filter_sub_source::rl_perform, this));
this->fss_editor.set_abort_action(
bind_mem(&filter_sub_source::rl_abort, this));
this->fss_editor.set_display_match_action(
bind_mem(&filter_sub_source::rl_display_matches, this));
this->fss_editor.set_display_next_action(
bind_mem(&filter_sub_source::rl_display_next, this));
this->fss_match_view.set_sub_source(&this->fss_match_source);
this->fss_match_view.set_height(0_vl);
this->fss_match_view.set_show_scrollbar(true);
this->fss_match_view.set_default_role(role_t::VCR_POPUP);
}
bool
filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
{
if (this->fss_editing) {
switch (ch) {
case KEY_CTRL_RBRACKET:
this->fss_editor.abort();
return true;
default:
this->fss_editor.handle_key(ch);
return true;
}
}
switch (ch) {
case 'f': {
auto* top_view = *lnav_data.ld_view_stack.top();
auto* tss = top_view->get_sub_source();
tss->toggle_apply_filters();
break;
}
case ' ': {
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
if (fs.empty()) {
return true;
}
auto tf = *(fs.begin() + lv.get_selection());
fs.set_filter_enabled(tf, !tf->is_enabled());
tss->text_filters_changed();
lv.reload_data();
return true;
}
case 't': {
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
if (fs.empty()) {
return true;
}
auto tf = *(fs.begin() + lv.get_selection());
if (tf->get_type() == text_filter::INCLUDE) {
tf->set_type(text_filter::EXCLUDE);
} else {
tf->set_type(text_filter::INCLUDE);
}
tss->text_filters_changed();
lv.reload_data();
return true;
}
case 'D': {
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
if (fs.empty()) {
return true;
}
auto tf = *(fs.begin() + lv.get_selection());
fs.delete_filter(tf->get_id());
lv.reload_data();
tss->text_filters_changed();
return true;
}
case 'i': {
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto filter_index = fs.next_index();
if (!filter_index) {
lnav_data.ld_filter_help_status_source.fss_error.set_value(
"error: too many filters");
return true;
}
auto ef = std::make_shared<empty_filter>(
text_filter::type_t::INCLUDE, *filter_index);
fs.add_filter(ef);
lv.set_selection(vis_line_t(fs.size() - 1));
lv.reload_data();
this->fss_editing = true;
add_view_text_possibilities(&this->fss_editor,
filter_lang_t::REGEX,
"*",
top_view,
text_quoting::regex);
this->fss_editor.set_window(lv.get_window());
this->fss_editor.set_visible(true);
this->fss_editor.set_y(lv.get_y()
+ (int) (lv.get_selection() - lv.get_top()));
this->fss_editor.set_left(25);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(filter_lang_t::REGEX, "", "");
this->fss_filter_state = true;
ef->disable();
return true;
}
case 'o': {
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto filter_index = fs.next_index();
if (!filter_index) {
lnav_data.ld_filter_help_status_source.fss_error.set_value(
"error: too many filters");
return true;
}
auto ef = std::make_shared<empty_filter>(
text_filter::type_t::EXCLUDE, *filter_index);
fs.add_filter(ef);
lv.set_selection(vis_line_t(fs.size() - 1));
lv.reload_data();
this->fss_editing = true;
add_view_text_possibilities(&this->fss_editor,
filter_lang_t::REGEX,
"*",
top_view,
text_quoting::regex);
this->fss_editor.set_window(lv.get_window());
this->fss_editor.set_visible(true);
this->fss_editor.set_y(lv.get_y()
+ (int) (lv.get_selection() - lv.get_top()));
this->fss_editor.set_left(25);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(filter_lang_t::REGEX, "", "");
this->fss_filter_state = true;
ef->disable();
return true;
}
case '\r':
case KEY_ENTER: {
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
if (fs.empty()) {
return true;
}
auto tf = *(fs.begin() + lv.get_selection());
this->fss_editing = true;
auto tq = tf->get_lang() == filter_lang_t::SQL
? text_quoting::sql
: text_quoting::regex;
add_view_text_possibilities(
&this->fss_editor, tf->get_lang(), "*", top_view, tq);
add_filter_expr_possibilities(
&this->fss_editor, filter_lang_t::SQL, "*");
this->fss_editor.set_window(lv.get_window());
this->fss_editor.set_visible(true);
this->fss_editor.set_y(lv.get_y()
+ (int) (lv.get_selection() - lv.get_top()));
this->fss_editor.set_left(25);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(tf->get_lang(), "");
this->fss_editor.rewrite_line(0, tf->get_id().c_str());
this->fss_filter_state = tf->is_enabled();
tf->disable();
tss->text_filters_changed();
return true;
}
case 'n': {
execute_command(lnav_data.ld_exec_context, "next-mark search");
return true;
}
case 'N': {
execute_command(lnav_data.ld_exec_context, "prev-mark search");
return true;
}
case '/': {
execute_command(lnav_data.ld_exec_context, "prompt search-filters");
return true;
}
default:
log_debug("unhandled %x", ch);
break;
}
return false;
}
size_t
filter_sub_source::text_line_count()
{
return (lnav_data.ld_view_stack.top() |
[](auto tc) {
text_sub_source* tss = tc->get_sub_source();
filter_stack& fs = tss->get_filters();
return nonstd::make_optional(fs.size());
})
.value_or(0);
}
size_t
filter_sub_source::text_line_width(textview_curses& curses)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
size_t retval = 0;
for (auto& filter : fs) {
retval = std::max(filter->get_id().size() + 8, retval);
}
return retval;
}
void
filter_sub_source::text_value_for_line(textview_curses& tc,
int line,
std::string& value_out,
text_sub_source::line_flags_t flags)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto tf = *(fs.begin() + line);
value_out = " ";
switch (tf->get_type()) {
case text_filter::INCLUDE:
value_out.append(" IN ");
break;
case text_filter::EXCLUDE:
if (tf->get_lang() == filter_lang_t::REGEX) {
value_out.append("OUT ");
} else {
value_out.append(" ");
}
break;
default:
ensure(0);
break;
}
if (this->fss_editing && line == tc.get_selection()) {
fmt::format_to(
std::back_inserter(value_out), FMT_STRING("{:>9} hits | "), "-");
} else {
fmt::format_to(std::back_inserter(value_out),
FMT_STRING("{:>9L} hits | "),
tss->get_filtered_count_for(tf->get_index()));
}
value_out.append(tf->get_id());
}
void
filter_sub_source::text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out)
{
auto& vcolors = view_colors::singleton();
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto tf = *(fs.begin() + line);
bool selected
= lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
if (selected) {
value_out.emplace_back(line_range{0, 1},
VC_GRAPHIC.value(ACS_RARROW));
}
chtype enabled = tf->is_enabled() ? ACS_DIAMOND : ' ';
line_range lr{2, 3};
value_out.emplace_back(lr, VC_GRAPHIC.value(enabled));
if (tf->is_enabled()) {
value_out.emplace_back(lr,
VC_FOREGROUND.value(
vcolors.ansi_to_theme_color(COLOR_GREEN)));
}
role_t fg_role = tf->get_type() == text_filter::INCLUDE ? role_t::VCR_OK
: role_t::VCR_ERROR;
value_out.emplace_back(line_range{4, 7},
VC_ROLE_FG.value(fg_role));
value_out.emplace_back(line_range{4, 7},
VC_STYLE.value(A_BOLD));
value_out.emplace_back(line_range{8, 17},
VC_STYLE.value(A_BOLD));
value_out.emplace_back(line_range{23, 24},
VC_GRAPHIC.value(ACS_VLINE));
if (selected) {
value_out.emplace_back(
line_range{0, -1},
VC_ROLE.value(role_t::VCR_FOCUSED));
}
attr_line_t content{tf->get_id()};
auto& content_attrs = content.get_attrs();
switch (tf->get_lang()) {
case filter_lang_t::REGEX:
readline_regex_highlighter(content, content.length());
break;
case filter_lang_t::SQL:
readline_sqlite_highlighter(content, content.length());
break;
case filter_lang_t::NONE:
break;
}
shift_string_attrs(content_attrs, 0, 25);
value_out.insert(
value_out.end(), content_attrs.begin(), content_attrs.end());
}
size_t
filter_sub_source::text_size_for_line(textview_curses& tc,
int line,
text_sub_source::line_flags_t raw)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto tf = *(fs.begin() + line);
return 8 + tf->get_id().size();
}
void
filter_sub_source::rl_change(readline_curses* rc)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
if (fs.empty()) {
return;
}
auto iter = fs.begin() + this->tss_view->get_selection();
auto tf = *iter;
auto new_value = rc->get_line_buffer();
switch (tf->get_lang()) {
case filter_lang_t::NONE:
break;
case filter_lang_t::REGEX: {
auto_mem<pcre> code;
const char* errptr;
int eoff;
if ((code = pcre_compile(
new_value.c_str(), PCRE_CASELESS, &errptr, &eoff, nullptr))
== nullptr)
{
lnav_data.ld_filter_help_status_source.fss_error.set_value(
"error: %s", errptr);
} else {
auto& hm = top_view->get_highlights();
highlighter hl(code.release());
int color;
if (tf->get_type() == text_filter::EXCLUDE) {
color = COLOR_RED;
} else {
color = COLOR_GREEN;
}
hl.with_attrs(view_colors::ansi_color_pair(COLOR_BLACK, color)
| A_BLINK);
hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
top_view->set_needs_update();
lnav_data.ld_filter_help_status_source.fss_error.clear();
}
break;
}
case filter_lang_t::SQL: {
auto full_sql
= fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
#ifdef SQLITE_PREPARE_PERSISTENT
auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
full_sql.c_str(),
full_sql.size(),
SQLITE_PREPARE_PERSISTENT,
stmt.out(),
nullptr);
#else
auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
full_sql.c_str(),
full_sql.size(),
stmt.out(),
nullptr);
#endif
if (retcode != SQLITE_OK) {
lnav_data.ld_filter_help_status_source.fss_error.set_value(
"error: %s", sqlite3_errmsg(lnav_data.ld_db));
} else {
auto set_res = lnav_data.ld_log_source.set_preview_sql_filter(
stmt.release());
if (set_res.isErr()) {
lnav_data.ld_filter_help_status_source.fss_error.set_value(
"error: %s", set_res.unwrapErr().c_str());
} else {
top_view->set_needs_update();
lnav_data.ld_filter_help_status_source.fss_error.clear();
}
}
break;
}
}
}
void
filter_sub_source::rl_perform(readline_curses* rc)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto iter = fs.begin() + this->tss_view->get_selection();
auto tf = *iter;
auto new_value = rc->get_value().get_string();
if (new_value.empty()) {
this->rl_abort(rc);
} else {
top_view->get_highlights().erase(
{highlight_source_t::PREVIEW, "preview"});
switch (tf->get_lang()) {
case filter_lang_t::NONE:
case filter_lang_t::REGEX: {
auto_mem<pcre> code;
const char* errptr;
int eoff;
if ((code = pcre_compile(new_value.c_str(),
PCRE_CASELESS,
&errptr,
&eoff,
nullptr))
== nullptr)
{
this->rl_abort(rc);
} else {
tf->lf_deleted = true;
tss->text_filters_changed();
auto pf = std::make_shared<pcre_filter>(tf->get_type(),
new_value,
tf->get_index(),
code.release());
*iter = pf;
tss->text_filters_changed();
}
break;
}
case filter_lang_t::SQL: {
auto full_sql
= fmt::format(FMT_STRING("SELECT 1 WHERE {}"), new_value);
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
#ifdef SQLITE_PREPARE_PERSISTENT
auto retcode = sqlite3_prepare_v3(lnav_data.ld_db.in(),
full_sql.c_str(),
full_sql.size(),
SQLITE_PREPARE_PERSISTENT,
stmt.out(),
nullptr);
#else
auto retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
full_sql.c_str(),
full_sql.size(),
stmt.out(),
nullptr);
#endif
if (retcode != SQLITE_OK) {
this->rl_abort(rc);
} else {
lnav_data.ld_log_source.set_sql_filter(new_value,
stmt.release());
tss->text_filters_changed();
}
break;
}
}
}
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
lnav_data.ld_filter_help_status_source.fss_prompt.clear();
this->fss_editing = false;
this->fss_editor.set_visible(false);
this->tss_view->reload_data();
}
void
filter_sub_source::rl_abort(readline_curses* rc)
{
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
auto iter = fs.begin() + this->tss_view->get_selection();
auto tf = *iter;
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
lnav_data.ld_filter_help_status_source.fss_prompt.clear();
lnav_data.ld_filter_help_status_source.fss_error.clear();
top_view->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
top_view->reload_data();
fs.delete_filter("");
this->tss_view->reload_data();
this->fss_editor.set_visible(false);
this->fss_editing = false;
this->tss_view->set_needs_update();
tf->set_enabled(this->fss_filter_state);
tss->text_filters_changed();
this->tss_view->reload_data();
}
void
filter_sub_source::rl_display_matches(readline_curses* rc)
{
const std::vector<std::string>& matches = rc->get_matches();
unsigned long width = 0;
if (matches.empty()) {
this->fss_match_source.clear();
this->fss_match_view.set_height(0_vl);
this->tss_view->set_needs_update();
} else {
auto current_match = rc->get_match_string();
attr_line_t al;
vis_line_t line, selected_line;
for (auto& match : matches) {
if (match == current_match) {
al.append(match, VC_STYLE.value(A_REVERSE));
selected_line = line;
} else {
al.append(match);
}
al.append(1, '\n');
width = std::max(width, (unsigned long) match.size());
line += 1_vl;
}
this->fss_match_view.set_selection(selected_line);
this->fss_match_source.replace_with(al);
this->fss_match_view.set_height(
std::min(vis_line_t(matches.size()), 3_vl));
}
this->fss_match_view.set_window(this->tss_view->get_window());
this->fss_match_view.set_y(rc->get_y() + 1);
this->fss_match_view.set_x(rc->get_left() + rc->get_match_start());
this->fss_match_view.set_width(width + 3);
this->fss_match_view.set_needs_update();
this->fss_match_view.scroll_selection_into_view();
this->fss_match_view.reload_data();
}
void
filter_sub_source::rl_display_next(readline_curses* rc)
{
textview_curses& tc = this->fss_match_view;
if (tc.get_top() >= (tc.get_top_for_last_row() - 1)) {
tc.set_top(0_vl);
} else {
tc.shift_top(tc.get_height());
}
}
void
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
{
lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_filter_view.reload_data();
}