diff --git a/NEWS b/NEWS index 5e072269..b059e7ba 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ lnav v0.7.3: * Added a 'relative-goto' command to move the current view relative to its current position. * Experimental support for linking with jemalloc. + * The plain text view now supports filtering. Fixes: * Autotools scripts overhaul. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 263115f0..f5539c08 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,7 @@ set(diag_STAT_SRCS chunky_index.hh concise_index.hh column_namer.hh + filter_observer.hh format-text-files.hh grapher.hh grep_highlighter.hh diff --git a/src/Makefile.am b/src/Makefile.am index fd3051ba..ee9d8a11 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -90,6 +90,7 @@ noinst_HEADERS = \ default-log-formats-json.hh \ db_sub_source.hh \ environ_vtab.hh \ + filter_observer.hh \ format-text-files.hh \ grapher.hh \ grep_highlighter.hh \ @@ -119,6 +120,7 @@ noinst_HEADERS = \ logfile_sub_source.hh \ pcrepp.hh \ piper_proc.hh \ + pretty_printer.hh \ ptimec.hh \ readline_curses.hh \ readline_highlighters.hh \ diff --git a/src/Makefile.in b/src/Makefile.in index 6fcae061..bd8b03b9 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -281,22 +281,24 @@ am__noinst_HEADERS_DIST = ansi_scrubber.hh auto_fd.hh auto_mem.hh \ auto_pid.hh bookmarks.hh bottom_status_source.hh byte_array.hh \ chunky_index.hh column_namer.hh concise_index.hh \ data_scanner.hh data_parser.hh default-log-formats-json.hh \ - db_sub_source.hh environ_vtab.hh format-text-files.hh \ - grapher.hh grep_highlighter.hh grep_proc.hh help.hh help.txt \ - hist_source.hh init.sql init-sql.hh intern_string.hh \ - json_op.hh json_ptr.hh k_merge_tree.h line_buffer.hh \ - listview_curses.hh lnav.hh lnav_commands.hh lnav_config.hh \ - lnav_log.hh lnav_util.hh log_accel.hh log_data_helper.hh \ - log_data_table.hh log_format.hh log_format_loader.hh \ - logfile.hh logfile_sub_source.hh pcrepp.hh piper_proc.hh \ - ptimec.hh readline_curses.hh readline_highlighters.hh \ - sequence_matcher.hh sequence_sink.hh session_data.hh \ - shared_buffer.hh sql_util.hh sqlite-extension-func.h \ - status_controllers.hh statusview_curses.hh strnatcmp.h \ - strong_int.hh sysclip.hh termios_guard.hh term_extra.hh \ - textfile_sub_source.hh textview_curses.hh time_T.hh \ - top_status_source.hh view_curses.hh vt52_curses.hh \ - log_vtab_impl.hh log_format_impls.cc xterm_mouse.hh yajlpp.hh \ + db_sub_source.hh environ_vtab.hh filter_observer.hh \ + format-text-files.hh grapher.hh grep_highlighter.hh \ + grep_proc.hh help.hh help.txt hist_source.hh init.sql \ + init-sql.hh intern_string.hh json_op.hh json_ptr.hh \ + k_merge_tree.h line_buffer.hh listview_curses.hh lnav.hh \ + lnav_commands.hh lnav_config.hh lnav_log.hh lnav_util.hh \ + log_accel.hh log_data_helper.hh log_data_table.hh \ + log_format.hh log_format_loader.hh logfile.hh \ + logfile_sub_source.hh pcrepp.hh piper_proc.hh \ + pretty_printer.hh ptimec.hh readline_curses.hh \ + readline_highlighters.hh sequence_matcher.hh sequence_sink.hh \ + session_data.hh shared_buffer.hh sql_util.hh \ + sqlite-extension-func.h status_controllers.hh \ + statusview_curses.hh strnatcmp.h strong_int.hh sysclip.hh \ + termios_guard.hh term_extra.hh textfile_sub_source.hh \ + textview_curses.hh time_T.hh top_status_source.hh \ + view_curses.hh vt52_curses.hh log_vtab_impl.hh \ + log_format_impls.cc xterm_mouse.hh yajlpp.hh \ spookyhash/SpookyV2.h yajl/api/yajl_common.h \ yajl/api/yajl_gen.h yajl/api/yajl_parse.h yajl/api/yajl_tree.h \ yajl/yajl_alloc.h yajl/yajl_buf.h yajl/yajl_bytestack.h \ @@ -505,22 +507,24 @@ noinst_HEADERS = ansi_scrubber.hh auto_fd.hh auto_mem.hh auto_pid.hh \ bookmarks.hh bottom_status_source.hh byte_array.hh \ chunky_index.hh column_namer.hh concise_index.hh \ data_scanner.hh data_parser.hh default-log-formats-json.hh \ - db_sub_source.hh environ_vtab.hh format-text-files.hh \ - grapher.hh grep_highlighter.hh grep_proc.hh help.hh help.txt \ - hist_source.hh init.sql init-sql.hh intern_string.hh \ - json_op.hh json_ptr.hh k_merge_tree.h line_buffer.hh \ - listview_curses.hh lnav.hh lnav_commands.hh lnav_config.hh \ - lnav_log.hh lnav_util.hh log_accel.hh log_data_helper.hh \ - log_data_table.hh log_format.hh log_format_loader.hh \ - logfile.hh logfile_sub_source.hh pcrepp.hh piper_proc.hh \ - ptimec.hh readline_curses.hh readline_highlighters.hh \ - sequence_matcher.hh sequence_sink.hh session_data.hh \ - shared_buffer.hh sql_util.hh sqlite-extension-func.h \ - status_controllers.hh statusview_curses.hh strnatcmp.h \ - strong_int.hh sysclip.hh termios_guard.hh term_extra.hh \ - textfile_sub_source.hh textview_curses.hh time_T.hh \ - top_status_source.hh view_curses.hh vt52_curses.hh \ - log_vtab_impl.hh log_format_impls.cc xterm_mouse.hh yajlpp.hh \ + db_sub_source.hh environ_vtab.hh filter_observer.hh \ + format-text-files.hh grapher.hh grep_highlighter.hh \ + grep_proc.hh help.hh help.txt hist_source.hh init.sql \ + init-sql.hh intern_string.hh json_op.hh json_ptr.hh \ + k_merge_tree.h line_buffer.hh listview_curses.hh lnav.hh \ + lnav_commands.hh lnav_config.hh lnav_log.hh lnav_util.hh \ + log_accel.hh log_data_helper.hh log_data_table.hh \ + log_format.hh log_format_loader.hh logfile.hh \ + logfile_sub_source.hh pcrepp.hh piper_proc.hh \ + pretty_printer.hh ptimec.hh readline_curses.hh \ + readline_highlighters.hh sequence_matcher.hh sequence_sink.hh \ + session_data.hh shared_buffer.hh sql_util.hh \ + sqlite-extension-func.h status_controllers.hh \ + statusview_curses.hh strnatcmp.h strong_int.hh sysclip.hh \ + termios_guard.hh term_extra.hh textfile_sub_source.hh \ + textview_curses.hh time_T.hh top_status_source.hh \ + view_curses.hh vt52_curses.hh log_vtab_impl.hh \ + log_format_impls.cc xterm_mouse.hh yajlpp.hh \ spookyhash/SpookyV2.h $(am__append_1) libdiag_a_SOURCES = ansi_scrubber.cc bookmarks.cc \ collation-functions.cc db_sub_source.cc environ_vtab.cc \ diff --git a/src/bottom_status_source.hh b/src/bottom_status_source.hh index a7531a85..e6a044e0 100644 --- a/src/bottom_status_source.hh +++ b/src/bottom_status_source.hh @@ -227,17 +227,17 @@ public: } }; - void update_filtered(logfile_sub_source &lss) + void update_filtered(text_sub_source *tss) { status_field &sf = this->bss_fields[BSF_FILTERED]; - if (lss.get_filtered_count() == 0) { + if (tss == NULL || tss->get_filtered_count() == 0) { sf.clear(); } else { ui_periodic_timer &timer = ui_periodic_timer::singleton(); - if (lss.get_filtered_count() == this->bss_last_filtered_count) { + if (tss->get_filtered_count() == this->bss_last_filtered_count) { if (timer.fade_diff(this->bss_filter_counter) == 0) { this->bss_fields[BSF_FILTERED].set_role( @@ -247,10 +247,10 @@ public: else { this->bss_fields[BSF_FILTERED].set_role( view_colors::VCR_ALERT_STATUS); - this->bss_last_filtered_count = lss.get_filtered_count(); + this->bss_last_filtered_count = tss->get_filtered_count(); timer.start_fade(this->bss_filter_counter, 3); } - sf.set_value("%'9d Not Shown", lss.get_filtered_count()); + sf.set_value("%'9d Not Shown", tss->get_filtered_count()); } }; diff --git a/src/filter_observer.hh b/src/filter_observer.hh new file mode 100644 index 00000000..51309a99 --- /dev/null +++ b/src/filter_observer.hh @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2015, 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. + */ + +#ifndef __filter_observer_hh +#define __filter_observer_hh + +#include + +#include "logfile.hh" +#include "textview_curses.hh" + +class line_filter_observer : public logline_observer { +public: + line_filter_observer(filter_stack &fs, logfile *lf) + : lfo_filter_stack(fs), lfo_filter_state(lf) { + + }; + + void logline_restart(const logfile &lf) { + for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); + iter != this->lfo_filter_stack.end(); + ++iter) { + (*iter)->revert_to_last(this->lfo_filter_state); + } + }; + + void logline_new_line(const logfile &lf, logfile::const_iterator ll, shared_buffer_ref &sbr) { + long offset = std::distance(lf.begin(), ll); + + require(&lf == this->lfo_filter_state.tfs_logfile); + + this->lfo_filter_state.resize(lf.size()); + if (!this->lfo_filter_stack.empty()) { + if (lf.get_format() != NULL) { + lf.get_format()->get_subline(*ll, sbr); + } + for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); + iter != this->lfo_filter_stack.end(); + ++iter) { + if (offset >= this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]) { + (*iter)->add_line(this->lfo_filter_state, ll, sbr); + } + } + } + }; + + void logline_eof(const logfile &lf) { + for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); + iter != this->lfo_filter_stack.end(); + ++iter) { + (*iter)->end_of_message(this->lfo_filter_state); + } + }; + + bool excluded(uint32_t filter_in_mask, uint32_t filter_out_mask, + size_t offset) const { + bool filtered_in = (filter_in_mask == 0) || ( + this->lfo_filter_state.tfs_mask[offset] & filter_in_mask) != 0; + bool filtered_out = ( + this->lfo_filter_state.tfs_mask[offset] & filter_out_mask) != 0; + return !filtered_in || filtered_out; + }; + + size_t get_min_count(size_t max) const { + size_t retval = max; + + for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); + iter != this->lfo_filter_stack.end(); + ++iter) { + retval = std::min(retval, this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]); + } + + return retval; + }; + + filter_stack &lfo_filter_stack; + logfile_filter_state lfo_filter_state; +}; + +#endif diff --git a/src/help.txt b/src/help.txt index 653680c7..6cc3b94d 100644 --- a/src/help.txt +++ b/src/help.txt @@ -386,7 +386,8 @@ COMMANDS expression. This command can be used multiple times to add more lines to the display. The number of lines that are filtered out will be shown in the - bottom status bar as 'Not Shown'. + bottom status bar as 'Not Shown'. Note that filtering + only works in the log and plain text views. filter-out Do not display lines that match the given regular @@ -396,7 +397,8 @@ COMMANDS priority and the filter-out will remove lines that were matched by the 'filter-in'. The number of lines that are filtered out will be shown in the - bottom status bar as 'Not Shown'. + bottom status bar as 'Not Shown'. Note that filtering + only works in the log and plain text views. disable-filter Disable an active 'filter-in' or 'filter-out' diff --git a/src/lnav.cc b/src/lnav.cc index c9ea5c72..44a21388 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -576,6 +576,28 @@ static void add_env_possibilities(int context) } } +static void add_filter_possibilities(textview_curses *tc) +{ + readline_curses *rc = lnav_data.ld_rl_view; + text_sub_source *tss = tc->get_sub_source(); + filter_stack &fs = tss->get_filters(); + + rc->clear_possibilities(LNM_COMMAND, "disabled-filter"); + rc->clear_possibilities(LNM_COMMAND, "enabled-filter"); + for (filter_stack::iterator iter = fs.begin(); + iter != fs.end(); + ++iter) { + text_filter *tf = *iter; + + if (tf->is_enabled()) { + rc->add_possibility(LNM_COMMAND, "enabled-filter", tf->get_id()); + } + else { + rc->add_possibility(LNM_COMMAND, "disabled-filter", tf->get_id()); + } + } +} + bool setup_logline_table() { // Hidden columns don't show up in the table_info pragma. @@ -751,6 +773,41 @@ static void rebuild_hist(size_t old_count, bool force) hist_view.set_top(hs.row_for_value(old_time)); } +class textfile_callback { +public: + textfile_callback() : force(false), front_file(NULL), front_top(-1) { }; + + void closed_file(logfile *lf) { + lnav_data.ld_file_names.erase(make_pair(lf->get_filename(), lf->get_fd())); + lnav_data.ld_files.remove(lf); + delete lf; + }; + + void promote_file(logfile *lf) { + if (lnav_data.ld_log_source.insert_file(lf)) { + force = true; + } + else { + this->closed_file(lf); + } + }; + + void scanned_file(logfile *lf) { + if (!lnav_data.ld_files_to_front.empty() && + lnav_data.ld_files_to_front.front().first == + lf->get_filename()) { + front_file = lf; + front_top = lnav_data.ld_files_to_front.front().second; + + lnav_data.ld_files_to_front.pop_front(); + } + }; + + bool force; + logfile *front_file; + int front_top; +}; + void rebuild_indexes(bool force) { logfile_sub_source &lss = lnav_data.ld_log_source; @@ -772,63 +829,22 @@ void rebuild_indexes(bool force) { textfile_sub_source * tss = &lnav_data.ld_text_source; std::list::iterator iter; - logfile *front_file = NULL; - int front_top = -1; - bool new_data = false; + bool new_data; old_bottom = text_view.get_top_for_last_row(); scroll_down = (text_view.get_top() >= old_bottom && !(lnav_data.ld_flags & LNF_HEADLESS)); - for (iter = tss->tss_files.begin(); - iter != tss->tss_files.end(); ) { - logfile *lf = (*iter); + textfile_callback cb; - if (!lf->exists() || lf->is_closed()) { - lnav_data.ld_file_names.erase(make_pair(lf->get_filename(), lf->get_fd())); - iter = tss->tss_files.erase(iter); - lnav_data.ld_files.remove(lf); - delete lf; - continue; - } + new_data = tss->rescan_files(cb); + force = force || cb.force; - try { - bool new_text_data = (*iter)->rebuild_index(); - - if ((*iter)->get_format() != NULL) { - if (lnav_data.ld_log_source.insert_file(lf)) { - iter = tss->tss_files.erase(iter); - force = true; - } - else { - ++iter; - } - } - else { - new_data = new_data || new_text_data; - if (!lnav_data.ld_files_to_front.empty() && - lnav_data.ld_files_to_front.front().first == - (*iter)->get_filename()) { - front_file = *iter; - front_top = lnav_data.ld_files_to_front.front().second; - - lnav_data.ld_files_to_front.pop_front(); - } - ++iter; - } - } - catch (const line_buffer::error &e) { - // TODO: log that we dropped this file. - iter = tss->tss_files.erase(iter); - } - } - - if (front_file != NULL) { + if (cb.front_file != NULL) { ensure_view(&text_view); - if (tss->current_file() != front_file) { - tss->tss_files.remove(front_file); - tss->tss_files.push_front(front_file); + if (tss->current_file() != cb.front_file) { + tss->to_front(cb.front_file); redo_search(LNV_TEXT); text_view.reload_data(); old_bottom = vis_line_t(-1); @@ -836,11 +852,11 @@ void rebuild_indexes(bool force) new_data = false; } - if (front_top < 0) { - front_top += text_view.get_inner_height(); + if (cb.front_top < 0) { + cb.front_top += text_view.get_inner_height(); } - if (front_top < text_view.get_inner_height()) { - text_view.set_top(vis_line_t(front_top)); + if (cb.front_top < text_view.get_inner_height()) { + text_view.set_top(vis_line_t(cb.front_top)); scroll_down = false; } } @@ -873,7 +889,7 @@ void rebuild_indexes(bool force) if (!lf->exists() || lf->is_closed()) { lnav_data.ld_file_names.erase(make_pair(lf->get_filename(), lf->get_fd())); - lnav_data.ld_text_source.tss_files.remove(lf); + lnav_data.ld_text_source.remove(lf); lnav_data.ld_log_source.remove_file(lf); file_iter = lnav_data.ld_files.erase(file_iter); force = true; @@ -933,8 +949,9 @@ void rebuild_indexes(bool force) } } - lnav_data.ld_bottom_source.update_filtered(lss); - lnav_data.ld_scroll_broadcaster.invoke(lnav_data.ld_view_stack.top()); + textview_curses *tc = lnav_data.ld_view_stack.top(); + lnav_data.ld_bottom_source.update_filtered(tc->get_sub_source()); + lnav_data.ld_scroll_broadcaster.invoke(tc); } class plain_text_source @@ -1177,9 +1194,13 @@ static void open_pretty_view(void) bool toggle_view(textview_curses *toggle_tc) { - textview_curses *tc = lnav_data.ld_view_stack.top(); + textview_curses *tc = lnav_data.ld_view_stack.empty() ? NULL : lnav_data.ld_view_stack.top(); bool retval = false; + require(toggle_tc != NULL); + require(toggle_tc >= &lnav_data.ld_views[0]); + require(toggle_tc < &lnav_data.ld_views[LNV__MAX]); + if (tc == toggle_tc) { lnav_data.ld_view_stack.pop(); } @@ -1222,15 +1243,18 @@ void redo_search(lnav_view_t view_index) * Ensure that the view is on the top of the view stack. * * @param expected_tc The text view that should be on top. + * @return True if the view was already on the top of the stack. */ -void ensure_view(textview_curses *expected_tc) +bool ensure_view(textview_curses *expected_tc) { - textview_curses *tc = lnav_data.ld_view_stack.top(); + textview_curses *tc = lnav_data.ld_view_stack.empty() ? NULL : lnav_data.ld_view_stack.top(); + bool retval = true; if (tc != expected_tc) { - toggle_view(expected_tc); + retval = false; } + return retval; } static vis_line_t next_cluster( @@ -1509,9 +1533,8 @@ static void handle_paging_key(int ch) else if (tc == &lnav_data.ld_views[LNV_TEXT]) { textfile_sub_source &tss = lnav_data.ld_text_source; - if (!tss.tss_files.empty()) { - tss.tss_files.push_front(tss.tss_files.back()); - tss.tss_files.pop_back(); + if (!tss.empty()) { + tss.rotate_right(); redo_search(LNV_TEXT); } } @@ -1524,9 +1547,8 @@ static void handle_paging_key(int ch) else if (tc == &lnav_data.ld_views[LNV_TEXT]) { textfile_sub_source &tss = lnav_data.ld_text_source; - if (!tss.tss_files.empty()) { - tss.tss_files.push_back(tss.tss_files.front()); - tss.tss_files.pop_front(); + if (!tss.empty()) { + tss.rotate_left(); redo_search(LNV_TEXT); } } @@ -1956,15 +1978,13 @@ static void handle_paging_key(int ch) "line-time", buffer); } - } - add_view_text_possibilities(LNM_COMMAND, "filter", tc); - lnav_data.ld_rl_view-> - add_possibility(LNM_COMMAND, "filter", - lnav_data.ld_last_search[tc - lnav_data.ld_views]); - lnav_data.ld_rl_view->add_possibility( - LNM_COMMAND, "levelname", logline::level_names); + add_view_text_possibilities(LNM_COMMAND, "filter", tc); + lnav_data.ld_rl_view-> + add_possibility(LNM_COMMAND, "filter", + lnav_data.ld_last_search[tc - lnav_data.ld_views]); + add_filter_possibilities(tc); lnav_data.ld_mode = LNM_COMMAND; lnav_data.ld_rl_view->focus(LNM_COMMAND, ":"); lnav_data.ld_bottom_source.set_prompt("Enter an lnav command: " @@ -3133,7 +3153,7 @@ static void watch_logfile(string filename, int fd, bool required) log_info("loading new file: %s", filename.c_str()); lf->set_logfile_observer(&obs); lnav_data.ld_files.push_back(lf); - lnav_data.ld_text_source.tss_files.push_back(lf); + lnav_data.ld_text_source.push_back(lf); break; } } @@ -3565,6 +3585,9 @@ static void looper(void) lnav_data.ld_rl_view->add_possibility( LNM_COMMAND, "viewname", lnav_view_strings); + lnav_data.ld_rl_view->add_possibility( + LNM_COMMAND, "levelname", logline::level_names); + (void)signal(SIGINT, sigint); (void)signal(SIGTERM, sigint); (void)signal(SIGWINCH, sigwinch); diff --git a/src/lnav.hh b/src/lnav.hh index dddbad74..e387b737 100644 --- a/src/lnav.hh +++ b/src/lnav.hh @@ -222,7 +222,7 @@ extern struct _lnav_data lnav_data; void rebuild_indexes(bool force); -void ensure_view(textview_curses *expected_tc); +bool ensure_view(textview_curses *expected_tc); bool toggle_view(textview_curses *toggle_tc); std::string execute_command(std::string cmdline); diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index a74a8a94..b34c42f6 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -871,12 +871,6 @@ static string com_enable_filter(string cmdline, vector &args) fs.set_filter_enabled(lf, true); tss->text_filters_changed(); tc->reload_data(); - if (lnav_data.ld_rl_view != NULL) { - lnav_data.ld_rl_view->rem_possibility( - LNM_COMMAND, "disabled-filter", args[1]); - lnav_data.ld_rl_view->add_possibility( - LNM_COMMAND, "enabled-filter", args[1]); - } retval = "info: filter enabled"; } } @@ -909,12 +903,6 @@ static string com_disable_filter(string cmdline, vector &args) fs.set_filter_enabled(lf, false); tss->text_filters_changed(); tc->reload_data(); - if (lnav_data.ld_rl_view != NULL) { - lnav_data.ld_rl_view->rem_possibility( - LNM_COMMAND, "enabled-filter", args[1]); - lnav_data.ld_rl_view->add_possibility( - LNM_COMMAND, "disabled-filter", args[1]); - } retval = "info: filter disabled"; } } @@ -1205,14 +1193,14 @@ static string com_close(string cmdline, vector &args) if (tc == &lnav_data.ld_views[LNV_TEXT]) { textfile_sub_source &tss = lnav_data.ld_text_source; - if (tss.tss_files.empty()) { + if (tss.empty()) { retval = "error: no text files are opened"; } else { fn = tss.current_file()->get_filename(); tss.current_file()->close(); - if (tss.tss_files.size() == 1) { + if (tss.size() == 1) { lnav_data.ld_view_stack.pop(); } } diff --git a/src/logfile.cc b/src/logfile.cc index 54728628..6d23dee4 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -423,7 +423,9 @@ void logfile::read_full_message(logfile::iterator ll, void logfile::set_logline_observer(logline_observer *llo) { this->lf_logline_observer = llo; - this->reobserve_from(this->begin()); + if (llo != NULL) { + this->reobserve_from(this->begin()); + } } void logfile::reobserve_from(iterator iter) diff --git a/src/logfile.hh b/src/logfile.hh index a9829366..c2b7f943 100644 --- a/src/logfile.hh +++ b/src/logfile.hh @@ -304,6 +304,10 @@ public: void set_logline_observer(logline_observer *llo); + logline_observer *get_logline_observer() const { + return this->lf_logline_observer; + }; + bool operator<(const logfile &rhs) const { bool retval; diff --git a/src/logfile_sub_source.hh b/src/logfile_sub_source.hh index 58fe4a06..b4ebd8d3 100644 --- a/src/logfile_sub_source.hh +++ b/src/logfile_sub_source.hh @@ -47,77 +47,10 @@ #include "bookmarks.hh" #include "chunky_index.hh" #include "textview_curses.hh" +#include "filter_observer.hh" STRONG_INT_TYPE(uint64_t, content_line); -class line_filter_observer : public logline_observer { -public: - line_filter_observer(filter_stack &fs, logfile *lf) - : lfo_filter_stack(fs), lfo_filter_state(lf) { - - }; - - void logline_restart(const logfile &lf) { - for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); - iter != this->lfo_filter_stack.end(); - ++iter) { - (*iter)->revert_to_last(this->lfo_filter_state); - } - }; - - void logline_new_line(const logfile &lf, logfile::const_iterator ll, shared_buffer_ref &sbr) { - long offset = std::distance(lf.begin(), ll); - - require(&lf == this->lfo_filter_state.tfs_logfile); - - this->lfo_filter_state.resize(lf.size()); - if (!this->lfo_filter_stack.empty()) { - if (lf.get_format() != NULL) { - lf.get_format()->get_subline(*ll, sbr); - } - for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); - iter != this->lfo_filter_stack.end(); - ++iter) { - if (offset >= this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]) { - (*iter)->add_line(this->lfo_filter_state, ll, sbr); - } - } - } - }; - - void logline_eof(const logfile &lf) { - for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); - iter != this->lfo_filter_stack.end(); - ++iter) { - (*iter)->end_of_message(this->lfo_filter_state); - } - }; - - bool excluded(uint32_t filter_in_mask, uint32_t filter_out_mask, - size_t offset) const { - bool filtered_in = (filter_in_mask == 0) || ( - this->lfo_filter_state.tfs_mask[offset] & filter_in_mask) != 0; - bool filtered_out = ( - this->lfo_filter_state.tfs_mask[offset] & filter_out_mask) != 0; - return !filtered_in || filtered_out; - }; - - size_t get_min_count(size_t max) const { - size_t retval = max; - - for (filter_stack::iterator iter = this->lfo_filter_stack.begin(); - iter != this->lfo_filter_stack.end(); - ++iter) { - retval = std::min(retval, this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]); - } - - return retval; - }; - - filter_stack &lfo_filter_stack; - logfile_filter_state lfo_filter_state; -}; - /** * Delegate class that merges the contents of multiple log files into a single * source of data for a text view. diff --git a/src/session_data.cc b/src/session_data.cc index 7d59d8c2..0a7c1024 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -753,20 +753,30 @@ static int read_word_wrap(yajlpp_parse_context *ypc, int value) static int read_commands(yajlpp_parse_context *ypc, const unsigned char *str, size_t len) { std::string cmdline = std::string((const char *)str, len); + const char ** view_name; + int view_index; + view_name = find(lnav_view_strings, + lnav_view_strings + LNV__MAX, + ypc->get_path_fragment(-3)); + view_index = view_name - lnav_view_strings; + bool active = ensure_view(&lnav_data.ld_views[view_index]); execute_command(cmdline); + if (!active) { + lnav_data.ld_view_stack.pop(); + } return 1; } static struct json_path_handler view_info_handlers[] = { - json_path_handler("/save-time", read_save_time), - json_path_handler("/time-offset", read_time_offset), - json_path_handler("/files#", read_files), - json_path_handler("/views/([^/]+)/top_line", read_top_line), - json_path_handler("/views/([^/]+)/search", read_last_search), - json_path_handler("/views/([^/]+)/word_wrap", read_word_wrap), - json_path_handler("/commands#", read_commands), + json_path_handler("^/save-time", read_save_time), + json_path_handler("^/time-offset", read_time_offset), + json_path_handler("^/files#", read_files), + json_path_handler("^/views/([^/]+)/top_line", read_top_line), + json_path_handler("^/views/([^/]+)/search", read_last_search), + json_path_handler("^/views/([^/]+)/word_wrap", read_word_wrap), + json_path_handler("^/views/([^/]+)/commands#", read_commands), json_path_handler() }; @@ -1257,22 +1267,22 @@ void save_session(void) cmd_array.gen((*filter_iter)->to_command()); } - textview_curses::highlight_map_t &hmap = - lnav_data.ld_views[LNV_LOG].get_highlights(); - textview_curses::highlight_map_t::iterator hl_iter; + if (lpc == LNV_LOG) { + textview_curses::highlight_map_t &hmap = + lnav_data.ld_views[LNV_LOG].get_highlights(); + textview_curses::highlight_map_t::iterator hl_iter; - for (hl_iter = hmap.begin(); - hl_iter != hmap.end(); - ++hl_iter) { - if (hl_iter->first[0] == '$') { - continue; + for (hl_iter = hmap.begin(); + hl_iter != hmap.end(); + ++hl_iter) { + if (hl_iter->first[0] == '$') { + continue; + } + cmd_array.gen("highlight " + hl_iter->first); } - cmd_array.gen("highlight " + hl_iter->first); } } } - - root_map.gen("commands"); } yajl_gen_clear(handle); @@ -1281,6 +1291,8 @@ void save_session(void) fclose(file.release()); rename(view_file_tmp_name.c_str(), view_file_name.c_str()); + + log_debug("Saved session: %s", view_file_name.c_str()); } } diff --git a/src/textfile_sub_source.hh b/src/textfile_sub_source.hh index cc3462f4..33b97ba3 100644 --- a/src/textfile_sub_source.hh +++ b/src/textfile_sub_source.hh @@ -34,6 +34,7 @@ #include "logfile.hh" #include "textview_curses.hh" +#include "filter_observer.hh" class textfile_sub_source : public text_sub_source { public: @@ -41,12 +42,22 @@ public: textfile_sub_source() { }; + bool empty() const { + return this->tss_files.empty(); + } + + size_t size() const { + return this->tss_files.size(); + } + size_t text_line_count() { size_t retval = 0; if (!this->tss_files.empty()) { - retval = this->current_file()->size(); + logfile *lf = this->current_file(); + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + retval = lfo->lfo_filter_state.tfs_index.size(); } return retval; @@ -58,8 +69,9 @@ public: bool raw = false) { if (!this->tss_files.empty()) { - this->current_file()-> - read_line(this->current_file()->begin() + line, value_out); + logfile *lf = this->current_file(); + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + lf->read_line(lf->begin() + lfo->lfo_filter_state.tfs_index[line], value_out); } else { value_out.clear(); @@ -85,7 +97,9 @@ public: size_t retval = 0; if (!this->tss_files.empty()) { - retval = this->current_file()->line_length(this->current_file()->begin() + line); + logfile *lf = this->current_file(); + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + retval = lf->line_length(lf->begin() + lfo->lfo_filter_state.tfs_index[line]); } return retval; @@ -106,8 +120,135 @@ public: } return this->tss_files.front()->get_filename(); + }; + + void to_front(logfile *lf) { + this->tss_files.remove(lf); + this->tss_files.push_front(lf); + }; + + void rotate_left() { + this->tss_files.push_back(this->tss_files.front()); + this->tss_files.pop_front(); + }; + + void rotate_right() { + this->tss_files.push_front(this->tss_files.back()); + this->tss_files.pop_back(); + }; + + void remove(logfile *lf) { + file_iterator iter = std::find(this->tss_files.begin(), + this->tss_files.end(), lf); + if (iter != this->tss_files.end()) { + detach_observer(lf); + this->tss_files.erase(iter); + } + }; + + void push_back(logfile *lf) { + line_filter_observer *lfo = new line_filter_observer( + this->get_filters(), lf); + lf->set_logline_observer(lfo); + this->tss_files.push_back(lf); + }; + + template bool rescan_files(T &callback) { + file_iterator iter; + bool retval = false; + + for (iter = this->tss_files.begin(); iter != this->tss_files.end();) { + logfile *lf = (*iter); + + if (!lf->exists() || lf->is_closed()) { + iter = this->tss_files.erase(iter); + this->detach_observer(lf); + callback.closed_file(lf); + continue; + } + + try { + uint32_t old_size = lf->size(); + bool new_text_data = lf->rebuild_index(); + + if (lf->get_format() != NULL) { + iter = this->tss_files.erase(iter); + this->detach_observer(lf); + callback.promote_file(lf); + continue; + } + + retval = retval || new_text_data; + callback.scanned_file(lf); + + uint32_t filter_in_mask, filter_out_mask; + + this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask); + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + for (uint32_t lpc = old_size; lpc < lf->size(); lpc++) { + if (lfo->excluded(filter_in_mask, filter_out_mask, lpc)) { + continue; + } + lfo->lfo_filter_state.tfs_index.push_back(lpc); + } + } + catch (const line_buffer::error &e) { + iter = this->tss_files.erase(iter); + lf->close(); + this->detach_observer(lf); + callback.closed_file(lf); + continue; + } + + ++iter; + } + + return retval; + }; + + virtual void text_filters_changed() { + logfile *lf = this->current_file(); + + if (lf == NULL) { + return; + } + + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + uint32_t filter_in_mask, filter_out_mask; + + lf->reobserve_from(lf->begin() + lfo->get_min_count(lf->size())); + + this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask); + lfo->lfo_filter_state.tfs_index.clear(); + for (uint32_t lpc = 0; lpc < lf->size(); lpc++) { + if (lfo->excluded(filter_in_mask, filter_out_mask, lpc)) { + continue; + } + lfo->lfo_filter_state.tfs_index.push_back(lpc); + } + }; + + int get_filtered_count() const { + logfile *lf = this->current_file(); + int retval = 0; + + if (lf != NULL) { + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + retval = lf->size() - lfo->lfo_filter_state.tfs_index.size(); + } + return retval; } +private: + void detach_observer(logfile *lf) { + line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer(); + lf->set_logline_observer(NULL); + if (lfo != NULL) { + delete lfo; + } + }; + std::list tss_files; }; + #endif diff --git a/src/textview_curses.hh b/src/textview_curses.hh index 3f12bf06..c1603941 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -74,6 +74,7 @@ public: logfile *tfs_logfile; size_t tfs_filter_count[MAX_FILTERS]; std::vector tfs_mask; + std::vector tfs_index; }; class text_filter { @@ -345,6 +346,10 @@ public: }; + virtual int get_filtered_count() const { + return 0; + }; + private: filter_stack tss_filters; }; @@ -356,7 +361,7 @@ public: virtual void text_overlay(textview_curses &tc) { }; virtual bool text_handle_mouse(textview_curses &tc, mouse_event &me) { - return false; + return false; }; }; diff --git a/src/top_status_source.hh b/src/top_status_source.hh index be4b59ed..d109598b 100644 --- a/src/top_status_source.hh +++ b/src/top_status_source.hh @@ -145,7 +145,9 @@ public: } else { sf_format.clear(); - sf_filename.set_value(lc->get_data_source()->listview_source_name(*lc)); + if (lc->get_data_source() != NULL) { + sf_filename.set_value(lc->get_data_source()->listview_source_name(*lc)); + } } sf_format.get_value().get_attrs().push_back( string_attr(lr, &view_curses::VC_STYLE, diff --git a/test/Makefile.am b/test/Makefile.am index d73e5b32..1395d61c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -226,6 +226,7 @@ dist_noinst_DATA = \ logfile_json.json \ logfile_multiline.0 \ logfile_openam.0 \ + logfile_plain.0 \ logfile_strace_log.0 \ logfile_syslog.0 \ logfile_syslog.1 \ diff --git a/test/Makefile.in b/test/Makefile.in index b6dba6c2..86c4f67c 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -821,6 +821,7 @@ dist_noinst_DATA = \ logfile_json.json \ logfile_multiline.0 \ logfile_openam.0 \ + logfile_plain.0 \ logfile_strace_log.0 \ logfile_syslog.0 \ logfile_syslog.1 \ diff --git a/test/logfile_plain.0 b/test/logfile_plain.0 new file mode 100644 index 00000000..13bc727f --- /dev/null +++ b/test/logfile_plain.0 @@ -0,0 +1,3 @@ +Hello, World! +How are you? +Goodbye, World! diff --git a/test/test_cmds.sh b/test/test_cmds.sh index 15e930e2..776f891a 100644 --- a/test/test_cmds.sh +++ b/test/test_cmds.sh @@ -142,6 +142,24 @@ Dec 6 13:01:34 ubu-mac dnsmasq-dhcp[1840]: read /var/lib/libvirt/dnsmasq/defaul EOF +run_test ${lnav_test} -n \ + -c ":switch-to-view text" \ + -c ":filter-in World" \ + ${test_dir}/logfile_plain.0 +check_output "plain text filter-in is not working" <