|
|
|
/**
|
|
|
|
* 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/attr_line.builder.hh"
|
|
|
|
#include "base/enum_util.hh"
|
|
|
|
#include "base/func_util.hh"
|
|
|
|
#include "base/opt_util.hh"
|
|
|
|
#include "bound_tags.hh"
|
|
|
|
#include "config.h"
|
|
|
|
#include "lnav.hh"
|
|
|
|
#include "readline_highlighters.hh"
|
|
|
|
#include "readline_possibilities.hh"
|
|
|
|
#include "sql_util.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_x(25);
|
|
|
|
this->fss_editor->set_width(-1);
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
filter_sub_source::register_view(textview_curses* tc)
|
|
|
|
{
|
|
|
|
text_sub_source::register_view(tc);
|
|
|
|
tc->add_child_view(this->fss_editor.get());
|
|
|
|
tc->add_child_view(&this->fss_match_view);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
|
|
|
|
{
|
|
|
|
if (this->fss_editing) {
|
|
|
|
switch (ch) {
|
|
|
|
case KEY_ESCAPE:
|
|
|
|
case KEY_CTRL(']'):
|
|
|
|
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': {
|
|
|
|
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::INCLUDE, *filter_index);
|
|
|
|
fs.add_filter(ef);
|
|
|
|
lv.set_selection(vis_line_t(fs.size() - 1));
|
|
|
|
lv.reload_data();
|
|
|
|
|
|
|
|
this->fss_editing = true;
|
|
|
|
this->tss_view->vc_enabled = false;
|
|
|
|
|
|
|
|
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->window_change();
|
|
|
|
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;
|
|
|
|
this->tss_view->vc_enabled = false;
|
|
|
|
|
|
|
|
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->window_change();
|
|
|
|
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;
|
|
|
|
this->tss_view->vc_enabled = false;
|
|
|
|
|
|
|
|
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);
|
|
|
|
if (top_view == &lnav_data.ld_views[LNV_LOG]) {
|
|
|
|
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->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) -> std::optional<size_t> {
|
|
|
|
text_sub_source* tss = tc->get_sub_source();
|
|
|
|
|
|
|
|
if (tss == nullptr) {
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
auto& fs = tss->get_filters();
|
|
|
|
return std::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);
|
|
|
|
bool selected
|
|
|
|
= lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
|
|
|
|
|
|
|
|
this->fss_curr_line.clear();
|
|
|
|
auto& al = this->fss_curr_line;
|
|
|
|
attr_line_builder alb(al);
|
|
|
|
|
|
|
|
if (selected) {
|
|
|
|
al.append(" ", VC_GRAPHIC.value(ACS_RARROW));
|
|
|
|
} else {
|
|
|
|
al.append(" ");
|
|
|
|
}
|
|
|
|
al.append(" ");
|
|
|
|
if (tf->is_enabled()) {
|
|
|
|
al.append("\u25c6"_ok);
|
|
|
|
} else {
|
|
|
|
al.append("\u25c7"_comment);
|
|
|
|
}
|
|
|
|
al.append(" ");
|
|
|
|
switch (tf->get_type()) {
|
|
|
|
case text_filter::INCLUDE:
|
|
|
|
al.append(" ").append(lnav::roles::ok("IN")).append(" ");
|
|
|
|
break;
|
|
|
|
case text_filter::EXCLUDE:
|
|
|
|
if (tf->get_lang() == filter_lang_t::REGEX) {
|
|
|
|
al.append(lnav::roles::error("OUT")).append(" ");
|
|
|
|
} else {
|
|
|
|
al.append(" ");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ensure(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NUMBER));
|
|
|
|
if (this->fss_editing && line == tc.get_selection()) {
|
|
|
|
alb.appendf(FMT_STRING("{:>9}"), "-");
|
|
|
|
} else {
|
|
|
|
alb.appendf(FMT_STRING("{:>9}"),
|
|
|
|
tss->get_filtered_count_for(tf->get_index()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
al.append(" hits ").append("|", VC_GRAPHIC.value(ACS_VLINE)).append(" ");
|
|
|
|
|
|
|
|
attr_line_t content{tf->get_id()};
|
|
|
|
switch (tf->get_lang()) {
|
|
|
|
case filter_lang_t::REGEX:
|
|
|
|
readline_regex_highlighter(content, std::nullopt);
|
|
|
|
break;
|
|
|
|
case filter_lang_t::SQL:
|
|
|
|
readline_sqlite_highlighter(content, std::nullopt);
|
|
|
|
break;
|
|
|
|
case filter_lang_t::NONE:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
al.append(content);
|
|
|
|
|
|
|
|
if (selected) {
|
|
|
|
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
|
|
|
|
}
|
|
|
|
|
|
|
|
value_out = al.get_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
filter_sub_source::text_attrs_for_line(textview_curses& tc,
|
|
|
|
int line,
|
|
|
|
string_attrs_t& value_out)
|
|
|
|
{
|
|
|
|
value_out = this->fss_curr_line.get_attrs();
|
|
|
|
}
|
|
|
|
|
|
|
|
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: {
|
|
|
|
if (new_value.empty()) {
|
|
|
|
auto sugg = top_view->get_current_search();
|
|
|
|
if (top_view->tc_selected_text) {
|
|
|
|
sugg = top_view->tc_selected_text->sti_value;
|
|
|
|
}
|
|
|
|
if (fs.get_filter(sugg) == nullptr) {
|
|
|
|
this->fss_editor->set_suggestion(sugg);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
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->tss_view->vc_enabled = true;
|
|
|
|
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->vc_enabled = true;
|
|
|
|
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_x() + rc->get_match_start());
|
|
|
|
this->fss_match_view.set_width(width + 3);
|
|
|
|
this->fss_match_view.set_needs_update();
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
filter_sub_source::text_handle_mouse(
|
|
|
|
textview_curses& tc,
|
|
|
|
const listview_curses::display_line_content_t&,
|
|
|
|
mouse_event& me)
|
|
|
|
{
|
|
|
|
if (this->fss_editing) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 1, 3)) {
|
|
|
|
this->list_input_handle_key(tc, ' ');
|
|
|
|
}
|
|
|
|
if (me.is_click_in(mouse_button_t::BUTTON_LEFT, 4, 7)) {
|
|
|
|
this->list_input_handle_key(tc, 't');
|
|
|
|
}
|
|
|
|
if (me.is_double_click_in(mouse_button_t::BUTTON_LEFT, line_range{25, -1}))
|
|
|
|
{
|
|
|
|
this->list_input_handle_key(tc, '\r');
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|