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

485 lines
17 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 "config.h"
#include "lnav.hh"
#include "filter_sub_source.hh"
#include "readline_possibilities.hh"
#include "readline_highlighters.hh"
using namespace std;
filter_sub_source::filter_sub_source()
: fss_change_wire(*this, &filter_sub_source::rl_change),
fss_perform_wire(*this, &filter_sub_source::rl_perform),
fss_abort_wire(*this, &filter_sub_source::rl_abort),
fss_display_match_wire(*this, &filter_sub_source::rl_display_matches),
fss_display_next_wire(*this, &filter_sub_source::rl_display_next)
{
this->filter_context.set_highlighter(readline_regex_highlighter)
.set_append_character(0);
this->fss_editor.add_context(LNM_FILTER, this->filter_context);
this->fss_editor.set_change_action(&this->fss_change_wire);
this->fss_editor.set_perform_action(&this->fss_perform_wire);
this->fss_editor.set_abort_action(&this->fss_abort_wire);
this->fss_editor.set_display_match_action(&this->fss_display_match_wire);
this->fss_editor.set_display_next_action(&this->fss_display_next_wire);
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(view_colors::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 '\t':
case KEY_BTAB:
case 'q':
lnav_data.ld_mode = LNM_PAGING;
lnav_data.ld_filter_view.reload_data();
return true;
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;
}
shared_ptr<text_filter> 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;
}
shared_ptr<text_filter> 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;
}
shared_ptr<text_filter> tf = *(fs.begin() + lv.get_selection());
fs.delete_filter(tf->get_id());
tss->text_filters_changed();
lv.reload_data();
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();
if (fs.full()) {
return true;
}
auto ef = make_shared<empty_filter>(text_filter::type_t::INCLUDE, fs.next_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, LNM_FILTER, "*", top_view);
lnav_data.ld_filter_status_source.tss_prompt.set_value(
"Enter a regular expression to match lines to filter in:");
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(22);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(LNM_FILTER, "", "");
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();
if (fs.full()) {
return true;
}
auto ef = make_shared<empty_filter>(text_filter::type_t::EXCLUDE, fs.next_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, LNM_FILTER, "*", top_view);
lnav_data.ld_filter_status_source.tss_prompt.set_value(
"Enter a regular expression to match lines to filter out:");
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(22);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(LNM_FILTER, "", "");
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;
}
shared_ptr<text_filter> tf = *(fs.begin() + lv.get_selection());
this->fss_editing = true;
add_view_text_possibilities(&this->fss_editor, LNM_FILTER, "*", top_view);
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(22);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(LNM_FILTER, "", tf->get_id().c_str());
this->fss_filter_state = tf->is_enabled();
tf->disable();
tss->text_filters_changed();
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();
shared_ptr<text_filter> tf = *(fs.begin() + line);
char hits[32];
value_out = " ";
switch (tf->get_type()) {
case text_filter::INCLUDE:
value_out.append(" IN ");
break;
case text_filter::EXCLUDE:
value_out.append("OUT ");
break;
default:
ensure(0);
break;
}
if (this->fss_editing && line == tc.get_selection()) {
snprintf(hits, sizeof(hits), "%6s hits | ", "-");
} else {
snprintf(hits, sizeof(hits), "%6d hits | ", tss->get_filtered_count_for(tf->get_index()));
}
value_out.append(hits);
value_out.append(tf->get_id());
}
void filter_sub_source::text_attrs_for_line(textview_curses &tc, int line,
string_attrs_t &value_out)
{
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();
shared_ptr<text_filter> tf = *(fs.begin() + line);
bool selected = lnav_data.ld_mode == LNM_FILTER && line == tc.get_selection();
int bg = selected ? COLOR_WHITE : COLOR_BLACK;
if (selected) {
value_out.emplace_back(line_range{0, 1}, &view_curses::VC_GRAPHIC, ACS_RARROW);
}
chtype enabled = tf->is_enabled() ? ACS_DIAMOND : ' ';
line_range lr{2, 3};
value_out.emplace_back(lr, &view_curses::VC_GRAPHIC, enabled);
if (tf->is_enabled()) {
value_out.emplace_back(lr, &view_curses::VC_FOREGROUND, COLOR_GREEN);
}
int fg = tf->get_type() == text_filter::INCLUDE ? COLOR_GREEN : COLOR_RED;
value_out.emplace_back(line_range{4, 7}, &view_curses::VC_FOREGROUND, fg);
value_out.emplace_back(line_range{4, 7}, &view_curses::VC_STYLE, A_BOLD);
value_out.emplace_back(line_range{8, 14}, &view_curses::VC_STYLE, A_BOLD);
value_out.emplace_back(line_range{20, 21}, &view_curses::VC_GRAPHIC, ACS_VLINE);
fg = selected ? COLOR_BLACK : COLOR_WHITE;
value_out.emplace_back(line_range{0, -1}, &view_curses::VC_FOREGROUND, fg);
value_out.emplace_back(line_range{0, -1}, &view_curses::VC_BACKGROUND, bg);
}
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();
shared_ptr<text_filter> 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();
shared_ptr<text_filter> tf = *iter;
string new_value = rc->get_line_buffer();
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_status_source.tss_error
.set_value("error: %s", errptr);
} else {
textview_curses::highlight_map_t &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["$preview"] = hl;
top_view->set_needs_update();
lnav_data.ld_filter_status_source.tss_error.clear();
}
}
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();
shared_ptr<text_filter> tf = *iter;
string new_value = rc->get_value();
auto_mem<pcre> code;
const char *errptr;
int eoff;
if (new_value.empty()) {
this->rl_abort(rc);
} else 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 = make_shared<pcre_filter>(tf->get_type(),
new_value, tf->get_index(), code.release());
*iter = pf;
tss->text_filters_changed();
}
lnav_data.ld_filter_status_source.tss_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();
shared_ptr<text_filter> tf = *iter;
lnav_data.ld_filter_status_source.tss_prompt.clear();
lnav_data.ld_filter_status_source.tss_error.clear();
top_view->get_highlights().erase("$preview");
top_view->reload_data();
fs.delete_filter("");
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 {
string 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, &view_curses::VC_STYLE, 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 = LNM_PAGING;
lnav_data.ld_filter_view.reload_data();
}