diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ffc1362..ffab751a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -479,6 +479,7 @@ add_library( fstat_vtab.hh fts_fuzzy_match.hh gantt_source.hh + gantt_status_source.hh grep_highlighter.hh hasher.hh help_text.hh diff --git a/src/Makefile.am b/src/Makefile.am index 8aac6168..cf8c96fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -215,6 +215,7 @@ noinst_HEADERS = \ fstat_vtab.hh \ fts_fuzzy_match.hh \ gantt_source.hh \ + gantt_status_source.hh \ grep_highlighter.hh \ grep_proc.hh \ hasher.hh \ diff --git a/src/base/math_util.hh b/src/base/math_util.hh index 842b319a..fbd79392 100644 --- a/src/base/math_util.hh +++ b/src/base/math_util.hh @@ -70,4 +70,70 @@ abs_diff(T a, T b) return a > b ? a - b : b - a; } +template +class clamped { +public: + static clamped from(T value, T min, T max) { return {value, min, max}; } + + clamped& operator+=(T rhs) + { + if (rhs < 0) { + return this->operator-=(-rhs); + } + + if (this->c_value + rhs < this->c_max) { + this->c_value += rhs; + } else { + this->c_value = this->c_max; + } + + return *this; + } + + clamped& operator-=(T rhs) + { + if (rhs < 0) { + return this->operator+=(-rhs); + } + + if (this->c_value - rhs > this->c_min) { + this->c_value -= rhs; + } else { + this->c_value = this->c_min; + } + + return *this; + } + + bool available_to_consume(T rhs) const + { + return (this->c_value - rhs > this->c_min); + } + + bool try_consume(T rhs) + { + if (this->c_value - rhs > this->c_min) { + this->c_value -= rhs; + return true; + } + + return false; + } + + operator T() const { return this->c_value; } + + bool is_min() const { return this->c_value == this->c_min; } + + T get_min() const { return this->c_min; } + + T get_max() const { return this->c_max; } + +private: + clamped(T value, T min, T max) : c_value(value), c_min(min), c_max(max) {} + + T c_value; + T c_min; + T c_max; +}; + #endif diff --git a/src/files_sub_source.cc b/src/files_sub_source.cc index dd5e2ea9..cf5c5366 100644 --- a/src/files_sub_source.cc +++ b/src/files_sub_source.cc @@ -138,6 +138,10 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch) auto tss = top_view->get_sub_source(); if (tss != nullptr) { + if (tss != &lss) { + lss.text_filters_changed(); + lnav_data.ld_views[LNV_LOG].reload_data(); + } tss->text_filters_changed(); top_view->reload_data(); } diff --git a/src/filter_sub_source.cc b/src/filter_sub_source.cc index 10e53a14..982c7d2a 100644 --- a/src/filter_sub_source.cc +++ b/src/filter_sub_source.cc @@ -275,12 +275,15 @@ 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()); - }) + [](auto tc) -> nonstd::optional { + text_sub_source* tss = tc->get_sub_source(); + + if (tss == nullptr) { + return nonstd::nullopt; + } + auto& fs = tss->get_filters(); + return nonstd::make_optional(fs.size()); + }) .value_or(0); } diff --git a/src/gantt_source.cc b/src/gantt_source.cc index b9c8435e..a0902ec2 100644 --- a/src/gantt_source.cc +++ b/src/gantt_source.cc @@ -145,10 +145,11 @@ gantt_header_overlay::list_static_overlay(const listview_curses& lv, gantt_source::gantt_source(textview_curses& log_view, logfile_sub_source& lss, plain_text_source& preview_source, - preview_status_source& preview_status_source) + gantt_status_source& preview_status_source) : gs_log_view(log_view), gs_lss(lss), gs_preview_source(preview_source), gs_preview_status_source(preview_status_source) { + this->tss_supports_filtering = true; } std::pair @@ -312,6 +313,9 @@ gantt_source::rebuild_indexes() this->gs_upper_bound = {}; this->gs_opid_width = 0; this->gs_total_width = 0; + this->gs_filtered_count = 0; + this->gs_preview_source.clear(); + this->gs_preview_status_source.get_description().clear(); auto max_desc_width = size_t{0}; @@ -320,6 +324,9 @@ gantt_source::rebuild_indexes() if (ld->get_file_ptr() == nullptr) { continue; } + if (!ld->is_visible()) { + continue; + } safe::ReadAccess r_opid_map( ld->get_file_ptr()->get_opids()); @@ -361,7 +368,56 @@ gantt_source::rebuild_indexes() opid_row{pair.first, pair.second}); } this->gs_time_order.clear(); + size_t filtered_in_count = 0; + for (const auto& filt : this->tss_filters) { + if (!filt->is_enabled()) { + continue; + } + if (filt->get_type() == text_filter::INCLUDE) { + filtered_in_count += 1; + } + } + this->gs_filter_hits = {}; for (const auto& pair : time_order_map) { + std::string full_desc; + for (const auto& desc : pair.second.or_value.otr_description) { + full_desc.append(" "); + full_desc.append(desc.second); + } + + shared_buffer sb; + shared_buffer_ref sbr; + sbr.share(sb, full_desc.c_str(), full_desc.length()); + if (this->tss_apply_filters) { + auto filtered_in = false; + auto filtered_out = false; + for (const auto& filt : this->tss_filters) { + if (!filt->is_enabled()) { + continue; + } + if (filt->matches(nonstd::nullopt, sbr)) { + this->gs_filter_hits[filt->get_index()] += 1; + switch (filt->get_type()) { + case text_filter::INCLUDE: + filtered_in = true; + break; + case text_filter::EXCLUDE: + filtered_out = true; + break; + default: + break; + } + } + } + if ((filtered_in_count > 0 && !filtered_in) || filtered_out) { + this->gs_filtered_count += 1; + continue; + } + } + + if (full_desc.size() > max_desc_width) { + max_desc_width = full_desc.size(); + } if (pair.second.or_value.get_error_count() > 0) { bm_errs.insert_once(vis_line_t(this->gs_time_order.size())); } else if (pair.second.or_value @@ -370,16 +426,11 @@ gantt_source::rebuild_indexes() { bm_warns.insert_once(vis_line_t(this->gs_time_order.size())); } - auto total_desc_width = size_t{0}; - for (const auto& desc : pair.second.or_value.otr_description) { - total_desc_width += 1 + desc.second.length(); - } - if (total_desc_width > max_desc_width) { - max_desc_width = total_desc_width; - } this->gs_time_order.emplace_back(pair.second); } - this->gs_total_width = 8 + this->gs_opid_width + max_desc_width; + this->gs_total_width = 22 + this->gs_opid_width + max_desc_width; + + this->tss_view->set_needs_update(); } nonstd::optional @@ -475,5 +526,40 @@ gantt_source::text_selection_changed(textview_curses& tc) this->gs_preview_source.replace_with(preview_content); this->gs_preview_status_source.get_description().set_value( - "Messages with opid: %.*s", row.or_name.length(), row.or_name.data()); + " OPID %.*s", row.or_name.length(), row.or_name.data()); + auto err_count = row.or_value.get_error_count(); + if (err_count == 0) { + this->gs_preview_status_source + .statusview_value_for_field(gantt_status_source::TSF_ERRORS) + .set_value(""); + } else if (err_count > 1) { + this->gs_preview_status_source + .statusview_value_for_field(gantt_status_source::TSF_ERRORS) + .set_value("%'d errors", err_count); + } else { + this->gs_preview_status_source + .statusview_value_for_field(gantt_status_source::TSF_ERRORS) + .set_value("%'d error", err_count); + } + this->gs_preview_status_source + .statusview_value_for_field(gantt_status_source::TSF_TOTAL) + .set_value("%'d messages ", row.or_value.get_total_msgs()); +} + +void +gantt_source::text_filters_changed() +{ + this->rebuild_indexes(); +} + +int +gantt_source::get_filtered_count() const +{ + return this->gs_filtered_count; +} + +int +gantt_source::get_filtered_count_for(size_t filter_index) const +{ + return this->gs_filter_hits[filter_index]; } diff --git a/src/gantt_source.hh b/src/gantt_source.hh index 0c370a3a..c3f64f1c 100644 --- a/src/gantt_source.hh +++ b/src/gantt_source.hh @@ -30,9 +30,9 @@ #ifndef lnav_gantt_source_hh #define lnav_gantt_source_hh +#include "gantt_status_source.hh" #include "logfile_sub_source.hh" #include "plain_text_source.hh" -#include "preview_status_source.hh" #include "textview_curses.hh" class gantt_source @@ -42,7 +42,7 @@ public: explicit gantt_source(textview_curses& log_view, logfile_sub_source& lss, plain_text_source& preview_source, - preview_status_source& preview_status_source); + gantt_status_source& preview_status_source); size_t text_line_count() override; @@ -63,6 +63,10 @@ public: void text_selection_changed(textview_curses& tc) override; + void text_filters_changed() override; + int get_filtered_count() const override; + int get_filtered_count_for(size_t filter_index) const override; + nonstd::optional row_for_time( struct timeval time_bucket) override; nonstd::optional time_for_row(vis_line_t row) override; @@ -74,7 +78,7 @@ public: textview_curses& gs_log_view; logfile_sub_source& gs_lss; plain_text_source& gs_preview_source; - preview_status_source& gs_preview_status_source; + gantt_status_source& gs_preview_status_source; ArenaAlloc::Alloc gs_allocator{64 * 1024}; struct opid_description_defs {}; @@ -90,6 +94,7 @@ public: struct opid_row { string_fragment or_name; opid_time_range or_value; + std::string or_description; }; attr_line_t gs_rendered_line; @@ -98,6 +103,8 @@ public: std::vector gs_time_order; struct timeval gs_lower_bound {}; struct timeval gs_upper_bound {}; + size_t gs_filtered_count{0}; + std::array gs_filter_hits{}; }; class gantt_header_overlay : public list_overlay_source { diff --git a/src/gantt_status_source.hh b/src/gantt_status_source.hh new file mode 100644 index 00000000..ef948458 --- /dev/null +++ b/src/gantt_status_source.hh @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2023, 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 lnav_gantt_status_source_hh +#define lnav_gantt_status_source_hh + +#include + +#include "statusview_curses.hh" + +class gantt_status_source : public status_data_source { +public: + typedef enum { + TSF_TITLE, + TSF_STITCH_TITLE, + TSF_DESCRIPTION, + TSF_TOTAL, + TSF_ERRORS, + + TSF__MAX + } field_t; + + gantt_status_source() + { + this->tss_fields[TSF_TITLE].set_width(16); + this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE); + this->tss_fields[TSF_TITLE].set_value(" Operation Logs "); + this->tss_fields[TSF_STITCH_TITLE].set_width(2); + this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); + this->tss_fields[TSF_DESCRIPTION].set_share(1); + this->tss_fields[TSF_ERRORS].right_justify(true); + this->tss_fields[TSF_ERRORS].set_role(role_t::VCR_ALERT_STATUS); + this->tss_fields[TSF_ERRORS].set_width(16); + this->tss_fields[TSF_TOTAL].right_justify(true); + this->tss_fields[TSF_TOTAL].set_width(20); + } + + size_t statusview_fields() override { return TSF__MAX; } + + status_field& statusview_value_for_field(int field) override + { + return this->tss_fields[field]; + } + + status_field& get_description() + { + return this->tss_fields[TSF_DESCRIPTION]; + } + +private: + status_field tss_fields[TSF__MAX]; +}; + +#endif diff --git a/src/help.md b/src/help.md index 8a8e8fa9..d3abca2c 100644 --- a/src/help.md +++ b/src/help.md @@ -49,12 +49,7 @@ not have to manually specify the log file format. The currently supported formats are: syslog, apache, strace, tcsh history, and generic log files with timestamps. -Lnav will also display data piped in on the standard input. The -following options are available when doing so: - -* `-t` Prepend timestamps to the lines of data being read in - on the standard input. -* `-w file` Write the contents of the standard input to this file. +Lnav will also display data piped in on the standard input. To automatically execute queries or lnav commands after the files have been loaded, you can use the following options: diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 0f7f6b26..ac5dc707 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -283,7 +283,8 @@ listview_curses::get_overlay_top(vis_line_t row, size_t count, size_t total) void listview_curses::do_update() { - if (this->lv_window == nullptr || this->lv_height == 0) { + if (this->lv_window == nullptr || this->lv_height == 0 || !this->vc_visible) + { view_curses::do_update(); return; } diff --git a/src/lnav.cc b/src/lnav.cc index 4b512748..1600bcd1 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -1399,6 +1399,7 @@ looper() lnav_data.ld_user_message_view.set_window(lnav_data.ld_window); + lnav_data.ld_spectro_details_view.set_title("spectro-details"); lnav_data.ld_spectro_details_view.set_window(lnav_data.ld_window); lnav_data.ld_spectro_details_view.set_show_scrollbar(true); lnav_data.ld_spectro_details_view.set_height(5_vl); @@ -1417,6 +1418,13 @@ looper() lnav_data.ld_spectro_source->ss_exec_context = &lnav_data.ld_exec_context; + lnav_data.ld_gantt_details_view.set_title("gantt-details"); + lnav_data.ld_gantt_details_view.set_window(lnav_data.ld_window); + lnav_data.ld_gantt_details_view.set_show_scrollbar(false); + lnav_data.ld_gantt_details_view.set_height(5_vl); + lnav_data.ld_gantt_details_view.set_sub_source( + &lnav_data.ld_gantt_details_source); + auto top_status_lifetime = injector::bind::to_scoped_singleton(); @@ -1444,6 +1452,8 @@ looper() = std::make_unique(); lnav_data.ld_status[LNS_SPECTRO].set_data_source( lnav_data.ld_spectro_status_source.get()); + lnav_data.ld_status[LNS_GANTT].set_data_source( + &lnav_data.ld_gantt_status_source); lnav_data.ld_match_view.set_show_bottom_border(true); lnav_data.ld_user_message_view.set_show_bottom_border(true); @@ -1649,6 +1659,7 @@ looper() lnav_data.ld_match_view.do_update(); lnav_data.ld_preview_view.do_update(); lnav_data.ld_spectro_details_view.do_update(); + lnav_data.ld_gantt_details_view.do_update(); lnav_data.ld_user_message_view.do_update(); if (ui_clock::now() >= next_status_update_time) { echo_views_stmt.execute(); @@ -2748,8 +2759,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' auto gantt_view_source = std::make_shared(lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source, - lnav_data.ld_preview_source, - lnav_data.ld_preview_status_source); + lnav_data.ld_gantt_details_source, + lnav_data.ld_gantt_status_source); auto gantt_header_source = std::make_shared(gantt_view_source); lnav_data.ld_views[LNV_GANTT] diff --git a/src/lnav.hh b/src/lnav.hh index 38d949c7..081a6689 100644 --- a/src/lnav.hh +++ b/src/lnav.hh @@ -55,6 +55,7 @@ #include "file_collection.hh" #include "files_sub_source.hh" #include "filter_status_source.hh" +#include "gantt_status_source.hh" #include "grep_highlighter.hh" #include "hist_source.hh" #include "input_dispatcher.hh" @@ -86,6 +87,7 @@ typedef enum { LNS_DOC, LNS_PREVIEW, LNS_SPECTRO, + LNS_GANTT, LNS__MAX } lnav_status_t; @@ -189,6 +191,7 @@ struct lnav_data_t { doc_status_source ld_doc_status_source; preview_status_source ld_preview_status_source; std::unique_ptr ld_spectro_status_source; + gantt_status_source ld_gantt_status_source; bool ld_preview_hidden; int64_t ld_preview_generation{0}; action_broadcaster ld_scroll_broadcaster; @@ -214,6 +217,8 @@ struct lnav_data_t { ld_user_message_expiration; textview_curses ld_spectro_details_view; plain_text_source ld_spectro_no_details_source; + textview_curses ld_gantt_details_view; + plain_text_source ld_gantt_details_source; view_stack ld_view_stack; textview_curses* ld_last_view; diff --git a/src/lnav.indexing.cc b/src/lnav.indexing.cc index 0c905c88..69a80219 100644 --- a/src/lnav.indexing.cc +++ b/src/lnav.indexing.cc @@ -278,6 +278,11 @@ rebuild_indexes(nonstd::optional deadline) } if (!closed_files.empty()) { lnav_data.ld_active_files.close_files(closed_files); + + auto* gantt_source = lnav_data.ld_views[LNV_GANTT].get_sub_source(); + if (gantt_source != nullptr) { + gantt_source->text_filters_changed(); + } } auto result = lss.rebuild_index(deadline); @@ -352,7 +357,8 @@ rebuild_indexes(nonstd::optional deadline) } lnav_data.ld_view_stack.top() | [](auto tc) { - lnav_data.ld_filter_status_source.update_filtered(tc->get_sub_source()); + auto* tss = tc->get_sub_source(); + lnav_data.ld_filter_status_source.update_filtered(tss); lnav_data.ld_scroll_broadcaster(tc); }; diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 1fcb41c8..bb1c47cc 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -586,7 +586,8 @@ com_relative_goto(exec_context& ec, retval = "info: shifting top by " + std::to_string(line_offset) + " lines"; } else { - tc->shift_top(vis_line_t(line_offset), true); + tc->set_selection(tc->get_selection() + + vis_line_t(line_offset)); retval = ""; } diff --git a/src/log_format.cc b/src/log_format.cc index ef442d83..d7e05219 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -106,6 +106,9 @@ opid_time_range::operator|=(const opid_time_range& rhs) if (this->otr_description.size() < rhs.otr_description.size()) { this->otr_description = rhs.otr_description; } + for (size_t lpc = 0; lpc < this->otr_level_counts.size(); lpc++) { + this->otr_level_counts[lpc] += rhs.otr_level_counts[lpc]; + } return *this; } diff --git a/src/logfile.cc b/src/logfile.cc index ad9a0bf9..21819db8 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -778,10 +778,10 @@ logfile::rebuild_index(nonstd::optional deadline) .ignore_error() .has_value()) { - curr_ll->set_mark(true); while (curr_ll->is_continued()) { --curr_ll; } + curr_ll->set_mark(true); auto line_number = static_cast( std::distance(this->begin(), curr_ll)); diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index 6b6571e4..8fcb5041 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -1959,25 +1959,30 @@ log_location_history::loc_history_forward(vis_line_t current_top) } bool -sql_filter::matches(const logfile& lf, - logfile::const_iterator ll, +sql_filter::matches(nonstd::optional ls_opt, const shared_buffer_ref& line) { - if (!ll->is_message()) { + if (!ls_opt) { + return false; + } + + auto ls = ls_opt; + + if (!ls->ls_line->is_message()) { return false; } if (this->sf_filter_stmt == nullptr) { return false; } - auto lfp = lf.shared_from_this(); + auto lfp = ls->ls_file.shared_from_this(); auto ld = this->sf_log_source.find_data_i(lfp); if (ld == this->sf_log_source.end()) { return false; } - auto eval_res - = this->sf_log_source.eval_sql_filter(this->sf_filter_stmt, ld, ll); + auto eval_res = this->sf_log_source.eval_sql_filter( + this->sf_filter_stmt, ld, ls->ls_line); if (eval_res.unwrapOr(true)) { return false; } @@ -2041,8 +2046,8 @@ vis_line_t logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start, vis_line_t highest) { - vis_bookmarks& bm = this->lmg_source.tss_view->get_bookmarks(); - bookmark_vector& bv = bm[&textview_curses::BM_META]; + auto& bm = this->lmg_source.tss_view->get_bookmarks(); + auto& bv = bm[&textview_curses::BM_META]; if (bv.empty()) { return -1_vl; @@ -2053,8 +2058,8 @@ logfile_sub_source::meta_grepper::grep_initial_line(vis_line_t start, void logfile_sub_source::meta_grepper::grep_next_line(vis_line_t& line) { - vis_bookmarks& bm = this->lmg_source.tss_view->get_bookmarks(); - bookmark_vector& bv = bm[&textview_curses::BM_META]; + auto& bm = this->lmg_source.tss_view->get_bookmarks(); + auto& bv = bm[&textview_curses::BM_META]; auto line_opt = bv.next(vis_line_t(line)); if (!line_opt) { diff --git a/src/logfile_sub_source.hh b/src/logfile_sub_source.hh index 240549a2..864560f8 100644 --- a/src/logfile_sub_source.hh +++ b/src/logfile_sub_source.hh @@ -80,39 +80,6 @@ public: virtual void index_complete(logfile_sub_source& lss) {} }; -class pcre_filter : public text_filter { -public: - pcre_filter(type_t type, - const std::string& id, - size_t index, - std::shared_ptr code) - : text_filter(type, filter_lang_t::REGEX, id, index), - pf_pcre(std::move(code)) - { - } - - ~pcre_filter() override = default; - - bool matches(const logfile& lf, - logfile::const_iterator ll, - const shared_buffer_ref& line) override - { - return this->pf_pcre->find_in(line.to_string_fragment()) - .ignore_error() - .has_value(); - } - - std::string to_command() const override - { - return (this->lf_type == text_filter::INCLUDE ? "filter-in " - : "filter-out ") - + this->lf_id; - } - -protected: - std::shared_ptr pf_pcre; -}; - class sql_filter : public text_filter { public: sql_filter(logfile_sub_source& lss, @@ -124,8 +91,7 @@ public: this->sf_filter_stmt = stmt; } - bool matches(const logfile& lf, - logfile::const_iterator ll, + bool matches(nonstd::optional ls, const shared_buffer_ref& line) override; std::string to_command() const override; diff --git a/src/plain_text_source.cc b/src/plain_text_source.cc index 70b3d0e3..90245545 100644 --- a/src/plain_text_source.cc +++ b/src/plain_text_source.cc @@ -91,6 +91,9 @@ plain_text_source::replace_with(const attr_line_t& text_lines) off += line_len; } this->tds_longest_line = this->compute_longest_line(); + if (this->tss_view != nullptr) { + this->tss_view->set_needs_update(); + } return *this; } @@ -103,6 +106,9 @@ plain_text_source::replace_with(const std::vector& text_lines) off += str.length() + 1; } this->tds_longest_line = this->compute_longest_line(); + if (this->tss_view != nullptr) { + this->tss_view->set_needs_update(); + } return *this; } @@ -112,6 +118,9 @@ plain_text_source::clear() this->tds_lines.clear(); this->tds_longest_line = 0; this->tds_text_format = text_format_t::TF_UNKNOWN; + if (this->tss_view != nullptr) { + this->tss_view->set_needs_update(); + } } plain_text_source& @@ -120,6 +129,9 @@ plain_text_source::truncate_to(size_t max_lines) while (this->tds_lines.size() > max_lines) { this->tds_lines.pop_back(); } + if (this->tss_view != nullptr) { + this->tss_view->set_needs_update(); + } return *this; } diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index 10262f0d..4cb2a371 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -903,11 +903,11 @@ rl_focus(readline_curses* rc) void rl_blur(readline_curses* rc) { + auto* tc = *lnav_data.ld_view_stack.top(); auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG] .get_overlay_source(); fos->fos_contexts.pop(); - lnav_data.ld_views[LNV_LOG].set_sync_selection_and_top( - fos->fos_contexts.top().c_show); + tc->set_sync_selection_and_top(fos->fos_contexts.top().c_show); lnav_data.ld_preview_generation += 1; } diff --git a/src/spectro_source.cc b/src/spectro_source.cc index 9bb4a550..7c3127aa 100644 --- a/src/spectro_source.cc +++ b/src/spectro_source.cc @@ -403,6 +403,12 @@ spectrogram_source::text_attrs_for_line(textview_curses& tc, } value_out.emplace_back(line_range(lpc, lpc + 1), VC_ROLE.value(role)); } + + auto alt_row_index = row % 4; + if (alt_row_index == 2 || alt_row_index == 3) { + value_out.emplace_back(line_range{0, -1}, + VC_ROLE.value(role_t::VCR_ALT_ROW)); + } } void diff --git a/src/textview_curses.cc b/src/textview_curses.cc index 4cc661ec..5253ab45 100644 --- a/src/textview_curses.cc +++ b/src/textview_curses.cc @@ -82,7 +82,7 @@ text_filter::add_line(logfile_filter_state& lfs, logfile::const_iterator ll, const shared_buffer_ref& line) { - bool match_state = this->matches(*lfs.tfs_logfile, ll, line); + bool match_state = this->matches(line_source{*lfs.tfs_logfile, ll}, line); if (ll->is_message()) { this->end_of_message(lfs); @@ -904,8 +904,7 @@ text_time_translator::data_reloaded(textview_curses* tc) template class bookmark_vector; bool -empty_filter::matches(const logfile& lf, - logfile::const_iterator ll, +empty_filter::matches(nonstd::optional ls, const shared_buffer_ref& line) { return false; diff --git a/src/textview_curses.hh b/src/textview_curses.hh index 08ec2906..6156968c 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -89,13 +89,8 @@ enum class filter_lang_t : int { class text_filter { public: typedef enum { - MAYBE, INCLUDE, EXCLUDE, - - LFT__MAX, - - LFT__MASK = (MAYBE | INCLUDE | EXCLUDE) } type_t; text_filter(type_t type, filter_lang_t lang, std::string id, size_t index) @@ -124,8 +119,12 @@ public: void end_of_message(logfile_filter_state& lfs); - virtual bool matches(const logfile& lf, - logfile_const_iterator ll, + struct line_source { + const logfile& ls_file; + logfile_const_iterator ls_line; + }; + + virtual bool matches(nonstd::optional ls, const shared_buffer_ref& line) = 0; @@ -150,13 +149,44 @@ public: { } - bool matches(const logfile& lf, - logfile_const_iterator ll, + bool matches(nonstd::optional ls, const shared_buffer_ref& line) override; std::string to_command() const override; }; +class pcre_filter : public text_filter { +public: + pcre_filter(type_t type, + const std::string& id, + size_t index, + std::shared_ptr code) + : text_filter(type, filter_lang_t::REGEX, id, index), + pf_pcre(std::move(code)) + { + } + + ~pcre_filter() override = default; + + bool matches(nonstd::optional ls, + const shared_buffer_ref& line) override + { + return this->pf_pcre->find_in(line.to_string_fragment()) + .ignore_error() + .has_value(); + } + + std::string to_command() const override + { + return (this->lf_type == text_filter::INCLUDE ? "filter-in " + : "filter-out ") + + this->lf_id; + } + +protected: + std::shared_ptr pf_pcre; +}; + class filter_stack { public: using iterator = std::vector>::iterator; diff --git a/src/view_helpers.cc b/src/view_helpers.cc index 997fd2e1..c9c8e825 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -307,6 +307,11 @@ open_pretty_view() auto* pretty_tc = &lnav_data.ld_views[LNV_PRETTY]; auto* log_tc = &lnav_data.ld_views[LNV_LOG]; auto* text_tc = &lnav_data.ld_views[LNV_TEXT]; + + if (top_tc != log_tc && top_tc != text_tc) { + return; + } + attr_line_t full_text; delete pretty_tc->get_sub_source(); @@ -571,6 +576,7 @@ handle_winch() lnav_data.ld_filter_view.set_needs_update(); lnav_data.ld_files_view.set_needs_update(); lnav_data.ld_spectro_details_view.set_needs_update(); + lnav_data.ld_gantt_details_view.set_needs_update(); lnav_data.ld_user_message_view.set_needs_update(); return true; @@ -579,27 +585,29 @@ handle_winch() void layout_views() { - unsigned long width, height; - + int width, height; getmaxyx(lnav_data.ld_window, height, width); + int doc_height; bool doc_side_by_side = width > (90 + 60); - bool preview_status_open + bool preview_open = !lnav_data.ld_preview_status_source.get_description().empty(); - bool filter_status_open = false; + bool filters_supported = false; auto is_spectro = false; + auto is_gantt = false; lnav_data.ld_view_stack.top() | [&](auto tc) { is_spectro = (tc == &lnav_data.ld_views[LNV_SPECTRO]); + is_gantt = (tc == &lnav_data.ld_views[LNV_GANTT]); - text_sub_source* tss = tc->get_sub_source(); + auto* tss = tc->get_sub_source(); if (tss == nullptr) { return; } if (tss->tss_supports_filtering) { - filter_status_open = true; + filters_supported = true; } }; @@ -614,9 +622,9 @@ layout_views() int preview_height = lnav_data.ld_preview_hidden ? 0 : lnav_data.ld_preview_source.text_line_count(); - int match_rows = lnav_data.ld_match_source.text_line_count(); - int match_height = std::min((unsigned long) match_rows, (height - 4) / 2); + int match_rows = lnav_data.ld_match_source.text_line_count(); + int match_height = std::min(match_rows, (height - 4) / 2); lnav_data.ld_match_view.set_height(vis_line_t(match_height)); int um_rows = lnav_data.ld_user_message_source.text_line_count(); @@ -627,105 +635,127 @@ layout_views() lnav_data.ld_user_message_source.clear(); um_rows = 0; } - int um_height = std::min((unsigned long) um_rows, (height - 4) / 2); - + auto um_height = std::min(um_rows, (height - 4) / 2); lnav_data.ld_user_message_view.set_height(vis_line_t(um_height)); - if (doc_height + 14 - > ((int) height - match_height - um_height - preview_height - 2)) - { - preview_height = 0; - preview_status_open = false; - } - - if (doc_height + 14 > ((int) height - match_height - um_height - 2)) { - doc_height = lnav_data.ld_doc_source.text_line_count(); - if (doc_height + 14 > ((int) height - match_height - um_height - 2)) { - doc_height = 0; - } - } - - bool doc_open = doc_height > 0; - bool filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER + auto filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER || lnav_data.ld_mode == ln_mode_t::FILES || lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS - || lnav_data.ld_mode == ln_mode_t::SEARCH_FILES) - && !preview_status_open && !doc_open; - bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS); + || lnav_data.ld_mode == ln_mode_t::SEARCH_FILES); int filter_height = filters_open ? 5 : 0; - int bottom_height = (doc_open ? 1 : 0) + doc_height - + (preview_status_open ? 1 : 0) + preview_height + 1 // bottom status - + match_height + um_height + lnav_data.ld_rl_view->get_height() - + (is_spectro && !doc_open ? 5 : 0); + bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS); - for (auto& tc : lnav_data.ld_views) { - tc.set_height(vis_line_t(-(bottom_height + (filter_status_open ? 1 : 0) - + (filters_open ? 1 : 0) + filter_height))); - } - lnav_data.ld_status[LNS_FILTER].set_visible(filter_status_open); - lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open); - lnav_data.ld_status[LNS_FILTER].set_top( - -(bottom_height + filter_height + 1 + (filters_open ? 1 : 0))); - lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open); - lnav_data.ld_status[LNS_FILTER_HELP].set_top( - -(bottom_height + filter_height + 1)); - lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + um_height + 2)); + auto bottom_min = std::min(2 + 3, height); + auto bottom = clamped::from(height, bottom_min, height); + + bottom -= lnav_data.ld_rl_view->get_height(); + lnav_data.ld_rl_view->set_width(width); + + bool vis; + vis = bottom.try_consume(lnav_data.ld_match_view.get_height()); + lnav_data.ld_match_view.set_y(bottom); + lnav_data.ld_match_view.set_visible(vis); + + vis = bottom.try_consume(um_height); + lnav_data.ld_user_message_view.set_y(bottom); + lnav_data.ld_user_message_view.set_visible(vis); + + bottom -= 1; + lnav_data.ld_status[LNS_BOTTOM].set_top(bottom); lnav_data.ld_status[LNS_BOTTOM].set_enabled(!filters_open && !breadcrumb_open); - lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height); - lnav_data.ld_status[LNS_DOC].set_visible(doc_open); - lnav_data.ld_status[LNS_PREVIEW].set_top(height - bottom_height - + (doc_open ? 1 : 0) + doc_height); - lnav_data.ld_status[LNS_PREVIEW].set_visible(preview_status_open); - lnav_data.ld_status[LNS_SPECTRO].set_top(height - bottom_height - 1); - lnav_data.ld_status[LNS_SPECTRO].set_visible(is_spectro); - lnav_data.ld_status[LNS_SPECTRO].set_enabled(lnav_data.ld_mode - == ln_mode_t::SPECTRO_DETAILS); - if (!doc_open || doc_side_by_side) { - lnav_data.ld_doc_view.set_height(vis_line_t(doc_height)); + vis = preview_open && bottom.try_consume(preview_height + 1); + lnav_data.ld_preview_view.set_height(vis_line_t(preview_height)); + lnav_data.ld_preview_view.set_y(bottom + 1); + lnav_data.ld_preview_view.set_visible(vis); + + lnav_data.ld_status[LNS_PREVIEW].set_top(bottom); + lnav_data.ld_status[LNS_PREVIEW].set_visible(vis); + + if (doc_side_by_side && doc_height > 0) { + vis = bottom.try_consume(doc_height + 1); + lnav_data.ld_example_view.set_height(vis_line_t(doc_height)); + lnav_data.ld_example_view.set_x(90); + lnav_data.ld_example_view.set_y(bottom + 1); + } else if (doc_height > 0 && bottom.available_to_consume(doc_height + 1)) { + lnav_data.ld_example_view.set_height( + vis_line_t(lnav_data.ld_example_source.text_line_count())); + vis = bottom.try_consume(lnav_data.ld_example_view.get_height()); + lnav_data.ld_example_view.set_x(0); + lnav_data.ld_example_view.set_y(bottom); } else { + vis = false; + lnav_data.ld_example_view.set_height(0_vl); + } + lnav_data.ld_example_view.set_visible(vis); + + if (doc_side_by_side) { + lnav_data.ld_doc_view.set_height(vis_line_t(doc_height)); + lnav_data.ld_doc_view.set_y(bottom + 1); + } else if (doc_height > 0) { lnav_data.ld_doc_view.set_height( vis_line_t(lnav_data.ld_doc_source.text_line_count())); + vis = bottom.try_consume(lnav_data.ld_doc_view.get_height() + 1); + lnav_data.ld_doc_view.set_y(bottom + 1); + } else { + vis = false; } - lnav_data.ld_doc_view.set_y(height - bottom_height + 1); + lnav_data.ld_doc_view.set_visible(vis); - if (!doc_open || doc_side_by_side) { - lnav_data.ld_example_view.set_height(vis_line_t(doc_height)); - lnav_data.ld_example_view.set_x(doc_open ? 90 : 0); - lnav_data.ld_example_view.set_y(height - bottom_height + 1); + auto has_doc = lnav_data.ld_example_view.get_height() > 0_vl + || lnav_data.ld_doc_view.get_height() > 0_vl; + lnav_data.ld_status[LNS_DOC].set_top(bottom); + lnav_data.ld_status[LNS_DOC].set_visible(has_doc && vis); + + if (is_gantt) { + vis = bottom.try_consume(lnav_data.ld_gantt_details_view.get_height() + + 1); } else { - lnav_data.ld_example_view.set_height( - vis_line_t(lnav_data.ld_example_source.text_line_count())); - lnav_data.ld_example_view.set_x(0); - lnav_data.ld_example_view.set_y( - height - bottom_height + lnav_data.ld_doc_view.get_height() + 1); + vis = false; } + lnav_data.ld_gantt_details_view.set_y(bottom + 1); + lnav_data.ld_gantt_details_view.set_width(width); + lnav_data.ld_gantt_details_view.set_visible(vis); + lnav_data.ld_status[LNS_GANTT].set_top(bottom); + lnav_data.ld_status[LNS_GANTT].set_visible(vis); + + vis = bottom.try_consume(filter_height + (filters_open ? 1 : 0) + + (filters_supported ? 1 : 0)); lnav_data.ld_filter_view.set_height(vis_line_t(filter_height)); - lnav_data.ld_filter_view.set_y(height - bottom_height - filter_height); + lnav_data.ld_filter_view.set_y(bottom + 2); lnav_data.ld_filter_view.set_width(width); + lnav_data.ld_filter_view.set_visible(filters_open && vis); lnav_data.ld_files_view.set_height(vis_line_t(filter_height)); - lnav_data.ld_files_view.set_y(height - bottom_height - filter_height); + lnav_data.ld_files_view.set_y(bottom + 2); lnav_data.ld_files_view.set_width(width); + lnav_data.ld_files_view.set_visible(filters_open && vis); - lnav_data.ld_preview_view.set_height(vis_line_t(preview_height)); - lnav_data.ld_preview_view.set_y(height - bottom_height + 1 - + (doc_open ? 1 : 0) + doc_height); - lnav_data.ld_user_message_view.set_y( - height - lnav_data.ld_rl_view->get_height() - match_height - um_height); - - lnav_data.ld_spectro_details_view.set_y(height - bottom_height); - lnav_data.ld_spectro_details_view.set_height( - is_spectro && !doc_open ? 5_vl : 0_vl); + lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open && vis); + lnav_data.ld_status[LNS_FILTER_HELP].set_top(bottom + 1); + + lnav_data.ld_status[LNS_FILTER].set_visible(vis); + lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open); + lnav_data.ld_status[LNS_FILTER].set_top(bottom); + + vis = is_spectro && bottom.try_consume(5 + 1); + lnav_data.ld_spectro_details_view.set_y(bottom + 1); + lnav_data.ld_spectro_details_view.set_height(5_vl); lnav_data.ld_spectro_details_view.set_width(width); - lnav_data.ld_spectro_details_view.set_title("spectro-details"); + lnav_data.ld_spectro_details_view.set_visible(vis); - lnav_data.ld_match_view.set_y(height - lnav_data.ld_rl_view->get_height() - - match_height); - lnav_data.ld_rl_view->set_width(width); + lnav_data.ld_status[LNS_SPECTRO].set_top(bottom); + lnav_data.ld_status[LNS_SPECTRO].set_visible(vis); + lnav_data.ld_status[LNS_SPECTRO].set_enabled(lnav_data.ld_mode + == ln_mode_t::SPECTRO_DETAILS); + + auto bottom_used = bottom - height; + for (auto& tc : lnav_data.ld_views) { + tc.set_height(vis_line_t(bottom_used)); + } } void @@ -969,7 +999,7 @@ toggle_view(textview_curses* toggle_tc) bool ensure_view(textview_curses* expected_tc) { - textview_curses* tc = lnav_data.ld_view_stack.top().value_or(nullptr); + auto* tc = lnav_data.ld_view_stack.top().value_or(nullptr); bool retval = true; if (tc != expected_tc) { diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index 510e8a05..1e67260c 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -51,13 +51,7 @@ not have to manually specify the log file format. The currently supported formats are: syslog, apache, strace, tcsh history, and generic log files with timestamps. -Lnav will also display data piped in on the standard input. The -following options are available when doing so: - - •  -t  Prepend timestamps to the lines of data being read - in on the standard input. - •  -w file  Write the contents of the standard input to - this file. +Lnav will also display data piped in on the standard input. To automatically execute queries or lnav commands after the files have been loaded, you can use the following options: