/** * 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 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 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 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(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(8); 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(); tss->text_filters_changed(); 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(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(8); 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(); tss->text_filters_changed(); 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 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(8); 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 tf = *(fs.begin() + line); 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; } 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 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); 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 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 tf = *iter; string new_value = rc->get_line_buffer(); auto_mem 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->reload_data(); 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 tf = *iter; string new_value = rc->get_value(); auto_mem 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(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 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 &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()); } }