diff options
Diffstat (limited to 'src/filter_sub_source.cc')
-rw-r--r-- | src/filter_sub_source.cc | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/src/filter_sub_source.cc b/src/filter_sub_source.cc new file mode 100644 index 0000000..a3bf6e4 --- /dev/null +++ b/src/filter_sub_source.cc @@ -0,0 +1,670 @@ +/** + * 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" + +using namespace lnav::roles::literals; + +filter_sub_source::filter_sub_source(std::shared_ptr<readline_curses> editor) + : fss_editor(editor) +{ + this->fss_editor->set_save_history(!(lnav_data.ld_flags & LNF_SECURE_MODE)); + 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(); + top_view->reload_data(); + 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(); + top_view->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(); + top_view->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(); + top_view->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(); + 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.get(), + 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': { + auto* top_view = *lnav_data.ld_view_stack.top(); + auto* tss = top_view->get_sub_source(); + auto& 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.get(), + 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.get(), tf->get_lang(), "*", top_view, tq); + add_filter_expr_possibilities( + this->fss_editor.get(), 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) +{ + auto* top_view = *lnav_data.ld_view_stack.top(); + auto* tss = top_view->get_sub_source(); + auto& 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) +{ + 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(COLOR_GREEN)); + } + + if (selected) { + value_out.emplace_back(line_range{0, -1}, + VC_ROLE.value(role_t::VCR_FOCUSED)); + } + + 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.value(fg_role)); + value_out.emplace_back(line_range{4, 7}, + VC_STYLE.value(text_attrs{A_BOLD})); + + value_out.emplace_back(line_range{8, 17}, + VC_STYLE.value(text_attrs{A_BOLD})); + value_out.emplace_back(line_range{23, 24}, VC_GRAPHIC.value(ACS_VLINE)); + + 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 regex_res + = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS); + + if (regex_res.isErr()) { + auto pe = regex_res.unwrapErr(); + lnav_data.ld_filter_help_status_source.fss_error.set_value( + "error: %s", pe.get_message().c_str()); + } else { + auto& hm = top_view->get_highlights(); + highlighter hl(regex_res.unwrap().to_shared()); + auto role = tf->get_type() == text_filter::EXCLUDE + ? role_t::VCR_DIFF_DELETE + : role_t::VCR_DIFF_ADD; + hl.with_role(role); + hl.with_attrs(text_attrs{A_BLINK | A_REVERSE}); + + 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() + .to_attr_line() + .get_string() + .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) +{ + static const intern_string_t INPUT_SRC = intern_string::lookup("input"); + + 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 compile_res + = lnav::pcre2pp::code::from(new_value, PCRE2_CASELESS); + + if (compile_res.isErr()) { + auto ce = compile_res.unwrapErr(); + auto um = lnav::console::to_user_message(INPUT_SRC, ce); + lnav_data.ld_exec_context.ec_error_callback_stack.back()( + um); + 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(), + compile_res.unwrap().to_shared()); + + *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) { + auto sqlerr = annotate_sql_with_error( + lnav_data.ld_db.in(), full_sql.c_str(), nullptr); + auto um + = lnav::console::user_message::error( + "invalid SQL expression") + .with_reason(sqlite3_errmsg(lnav_data.ld_db.in())) + .with_snippet(lnav::console::snippet::from( + INPUT_SRC, sqlerr)); + lnav_data.ld_exec_context.ec_error_callback_stack.back()( + um); + 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); + top_view->reload_data(); + 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 (const auto& match : matches) { + if (match == current_match) { + al.append(match, VC_STYLE.value(text_attrs{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(); +} |