summaryrefslogtreecommitdiffstats
path: root/src/filter_sub_source.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/filter_sub_source.cc')
-rw-r--r--src/filter_sub_source.cc670
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();
+}