/** * Copyright (c) 2007-2012, 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. * * @file textview_curses.hh */ #ifndef textview_curses_hh #define textview_curses_hh #include #include #include "base/func_util.hh" #include "base/lnav_log.hh" #include "bookmarks.hh" #include "breadcrumb.hh" #include "grep_proc.hh" #include "highlighter.hh" #include "listview_curses.hh" #include "lnav_config_fwd.hh" #include "log_accel.hh" #include "logfile_fwd.hh" #include "ring_span.hh" #include "text_format.hh" #include "textview_curses_fwd.hh" class textview_curses; using vis_bookmarks = bookmarks::type; class logfile_filter_state { public: logfile_filter_state(std::shared_ptr lf = nullptr); void clear(); void clear_filter_state(size_t index); void clear_deleted_filter_state(uint32_t used_mask); void resize(size_t newsize); std::optional content_line_to_vis_line(uint32_t line); const static int MAX_FILTERS = 32; std::shared_ptr tfs_logfile; size_t tfs_filter_count[MAX_FILTERS]; int tfs_filter_hits[MAX_FILTERS]; bool tfs_message_matched[MAX_FILTERS]; size_t tfs_lines_for_message[MAX_FILTERS]; bool tfs_last_message_matched[MAX_FILTERS]; size_t tfs_last_lines_for_message[MAX_FILTERS]; std::vector tfs_mask; std::vector tfs_index; }; enum class filter_lang_t : int { NONE, REGEX, SQL, }; class text_filter { public: typedef enum { INCLUDE, EXCLUDE, } type_t; text_filter(type_t type, filter_lang_t lang, std::string id, size_t index) : lf_type(type), lf_lang(lang), lf_id(std::move(id)), lf_index(index) { } virtual ~text_filter() = default; type_t get_type() const { return this->lf_type; } filter_lang_t get_lang() const { return this->lf_lang; } void set_type(type_t t) { this->lf_type = t; } std::string get_id() const { return this->lf_id; } void set_id(std::string id) { this->lf_id = std::move(id); } size_t get_index() const { return this->lf_index; } bool is_enabled() const { return this->lf_enabled; } void enable() { this->lf_enabled = true; } void disable() { this->lf_enabled = false; } void set_enabled(bool value) { this->lf_enabled = value; } void revert_to_last(logfile_filter_state& lfs, size_t rollback_size); void add_line(logfile_filter_state& lfs, logfile_const_iterator ll, const shared_buffer_ref& line); void end_of_message(logfile_filter_state& lfs); struct line_source { const logfile& ls_file; logfile_const_iterator ls_line; }; virtual bool matches(std::optional ls, const shared_buffer_ref& line) = 0; virtual std::string to_command() const = 0; bool operator==(const std::string& rhs) const { return this->lf_id == rhs; } bool lf_deleted{false}; protected: bool lf_enabled{true}; type_t lf_type; filter_lang_t lf_lang; std::string lf_id; size_t lf_index; }; class empty_filter : public text_filter { public: empty_filter(type_t type, size_t index) : text_filter(type, filter_lang_t::REGEX, "", index) { } bool matches(std::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(std::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; using const_iterator = std::vector>::const_iterator; using value_type = std::shared_ptr; explicit filter_stack(size_t reserved = 0) : fs_reserved(reserved) {} iterator begin() { return this->fs_filters.begin(); } iterator end() { return this->fs_filters.end(); } const_iterator begin() const { return this->fs_filters.begin(); } const_iterator end() const { return this->fs_filters.end(); } size_t size() const { return this->fs_filters.size(); } bool empty() const { return this->fs_filters.empty(); }; bool full() const { return (this->fs_reserved + this->fs_filters.size()) == logfile_filter_state::MAX_FILTERS; } std::optional next_index(); void add_filter(const std::shared_ptr& filter); void clear_filters() { while (!this->fs_filters.empty()) { this->fs_filters.pop_back(); } } void set_filter_enabled(const std::shared_ptr& filter, bool enabled) { if (enabled) { filter->enable(); } else { filter->disable(); } } std::shared_ptr get_filter(const std::string& id); bool delete_filter(const std::string& id); void get_mask(uint32_t& filter_mask); void get_enabled_mask(uint32_t& filter_in_mask, uint32_t& filter_out_mask); private: const size_t fs_reserved; std::vector> fs_filters; }; class text_time_translator { public: struct row_info { row_info() = default; row_info(struct timeval tv, int64_t id) : ri_time(tv), ri_id(id) {} struct timeval ri_time { 0, 0 }; int64_t ri_id{-1}; }; virtual ~text_time_translator() = default; virtual std::optional row_for_time(struct timeval time_bucket) = 0; virtual std::optional row_for(const row_info& ri) { return this->row_for_time(ri.ri_time); } virtual std::optional time_for_row(vis_line_t row) = 0; void data_reloaded(textview_curses* tc); void ttt_scroll_invoked(textview_curses* tc); protected: std::optional ttt_top_row_info; }; class text_accel_source { public: virtual ~text_accel_source() = default; virtual log_accel::direction_t get_line_accel_direction(vis_line_t vl); void toggle_time_offset() { this->tas_display_time_offset = !this->tas_display_time_offset; this->text_accel_display_changed(); } void set_time_offset(bool enabled) { if (this->tas_display_time_offset != enabled) { this->tas_display_time_offset = enabled; this->text_accel_display_changed(); } } bool is_time_offset_enabled() const { return this->tas_display_time_offset; } virtual bool is_time_offset_supported() const { return true; } virtual logline* text_accel_get_line(vis_line_t vl) = 0; std::string get_time_offset_for_line(textview_curses& tc, vis_line_t vl); protected: virtual void text_accel_display_changed() {} bool tas_display_time_offset{false}; }; class text_anchors { public: virtual ~text_anchors() = default; static std::string to_anchor_string(const std::string& raw); virtual std::optional row_for_anchor(const std::string& id) = 0; enum class direction { prev, next, }; virtual std::optional adjacent_anchor(vis_line_t vl, direction dir) { return std::nullopt; } virtual std::optional anchor_for_row(vis_line_t vl) = 0; virtual std::unordered_set get_anchors() = 0; }; class location_history { public: virtual ~location_history() = default; virtual void loc_history_append(vis_line_t top) = 0; virtual std::optional loc_history_back(vis_line_t current_top) = 0; virtual std::optional loc_history_forward( vis_line_t current_top) = 0; const static int MAX_SIZE = 100; protected: size_t lh_history_position{0}; }; /** * Source for the text to be shown in a textview_curses view. */ class text_sub_source { public: virtual ~text_sub_source() = default; enum { RB_RAW, RB_FULL, RB_REWRITE, }; enum { RF_RAW = (1UL << RB_RAW), RF_FULL = (1UL << RB_FULL), RF_REWRITE = (1UL << RB_REWRITE), }; typedef long line_flags_t; text_sub_source(size_t reserved_filters = 0) : tss_filters(reserved_filters) { } virtual void register_view(textview_curses* tc) { this->tss_view = tc; } /** * @return The total number of lines available from the source. */ virtual size_t text_line_count() = 0; virtual size_t text_line_width(textview_curses& curses) { return INT_MAX; } virtual bool text_is_row_selectable(textview_curses& tc, vis_line_t row) { return true; } virtual void text_selection_changed(textview_curses& tc) {} /** * Get the value for a line. * * @param tc The textview_curses object that is delegating control. * @param line The line number to retrieve. * @param value_out The string object that should be set to the line * contents. * @param raw Indicates that the raw contents of the line should be returned * without any post processing. */ virtual void text_value_for_line(textview_curses& tc, int line, std::string& value_out, line_flags_t flags = 0) = 0; virtual size_t text_size_for_line(textview_curses& tc, int line, line_flags_t raw = 0) = 0; /** * Inform the source that the given line has been marked/unmarked. This * callback function can be used to translate between between visible line * numbers and content line numbers. For example, when viewing a log file * with filters being applied, we want the bookmarked lines to be stable * across changes in the filters. * * @param bm The type of bookmark. * @param line The line that has been marked/unmarked. * @param added True if the line was bookmarked and false if it was * unmarked. */ virtual void text_mark(const bookmark_type_t* bm, vis_line_t line, bool added) { } /** * Clear the bookmarks for a particular type in the text source. * * @param bm The type of bookmarks to clear. */ virtual void text_clear_marks(const bookmark_type_t* bm) {} /** * Get the attributes for a line of text. * * @param tc The textview_curses object that is delegating control. * @param line The line number to retrieve. * @param value_out A string_attrs_t object that should be updated with the * attributes for the line. */ virtual void text_attrs_for_line(textview_curses& tc, int line, string_attrs_t& value_out) { } /** * Update the bookmarks used by the text view based on the bookmarks * maintained by the text source. * * @param bm The bookmarks data structure used by the text view. */ virtual void text_update_marks(vis_bookmarks& bm) {} virtual std::string text_source_name(const textview_curses& tv) { return ""; } filter_stack& get_filters() { return this->tss_filters; } virtual void text_filters_changed() {} virtual int get_filtered_count() const { return 0; } virtual int get_filtered_count_for(size_t filter_index) const { return 0; } virtual text_format_t get_text_format() const { return text_format_t::TF_UNKNOWN; } virtual std::optional< std::pair*, grep_proc_sink*>> get_grepper() { return std::nullopt; } virtual std::optional get_location_history() { return std::nullopt; } void toggle_apply_filters(); virtual void text_crumbs_for_line(int line, std::vector& crumbs); virtual void quiesce() {} virtual void scroll_invoked(textview_curses* tc); virtual void text_open_href(const std::string& href) {} bool tss_supports_filtering{false}; bool tss_apply_filters{true}; protected: textview_curses* tss_view{nullptr}; filter_stack tss_filters; }; class vis_location_history : public location_history { public: vis_location_history() : vlh_history(std::begin(this->vlh_backing), std::end(this->vlh_backing)) { } void loc_history_append(vis_line_t top) override; std::optional loc_history_back(vis_line_t current_top) override; std::optional loc_history_forward( vis_line_t current_top) override; nonstd::ring_span vlh_history; private: vis_line_t current_position() { auto iter = this->vlh_history.rbegin(); iter += this->lh_history_position; return *iter; } vis_line_t vlh_backing[MAX_SIZE]; }; class text_delegate { public: virtual ~text_delegate() = default; virtual bool text_handle_mouse( textview_curses& tc, const listview_curses::display_line_content_t&, mouse_event& me) { return false; } }; /** * The textview_curses class adds user bookmarks and searching to the standard * list view interface. */ class textview_curses : public listview_curses , public list_data_source , public grep_proc_source , public grep_proc_sink , public lnav_config_listener { public: using action = std::function; const static bookmark_type_t BM_USER; const static bookmark_type_t BM_USER_EXPR; const static bookmark_type_t BM_SEARCH; const static bookmark_type_t BM_META; const static bookmark_type_t BM_PARTITION; textview_curses(); ~textview_curses(); void reload_config(error_reporter& reporter); void set_paused(bool paused) { this->tc_paused = paused; if (this->tc_state_event_handler) { this->tc_state_event_handler(*this); } } bool is_paused() const { return this->tc_paused; } vis_bookmarks& get_bookmarks() { return this->tc_bookmarks; } const vis_bookmarks& get_bookmarks() const { return this->tc_bookmarks; } void toggle_user_mark(const bookmark_type_t* bm, vis_line_t start_line, vis_line_t end_line = vis_line_t(-1)); void set_user_mark(const bookmark_type_t* bm, vis_line_t vl, bool marked); textview_curses& set_sub_source(text_sub_source* src); text_sub_source* get_sub_source() const { return this->tc_sub_source; } textview_curses& set_supports_marks(bool m) { this->tc_supports_marks = m; return *this; } textview_curses& set_delegate(std::shared_ptr del) { this->tc_delegate = del; return *this; } std::shared_ptr get_delegate() const { return this->tc_delegate; } std::optional> horiz_shift(vis_line_t start, vis_line_t end, int off_start); void set_search_action(action sa) { this->tc_search_action = std::move(sa); } void grep_end_batch(grep_proc& gp); void grep_end(grep_proc& gp); size_t listview_rows(const listview_curses& lv) { return this->tc_sub_source == nullptr ? 0 : this->tc_sub_source->text_line_count(); } size_t listview_width(const listview_curses& lv) { return this->tc_sub_source == nullptr ? 0 : this->tc_sub_source->text_line_width(*this); } void listview_value_for_rows(const listview_curses& lv, vis_line_t line, std::vector& rows_out); void textview_value_for_row(vis_line_t line, attr_line_t& value_out); bool listview_is_row_selectable(const listview_curses& lv, vis_line_t row); void listview_selection_changed(const listview_curses& lv); size_t listview_size_for_row(const listview_curses& lv, vis_line_t row) { return this->tc_sub_source->text_size_for_line(*this, row); } std::string listview_source_name(const listview_curses& lv) { return this->tc_sub_source == nullptr ? "" : this->tc_sub_source->text_source_name(*this); } bool grep_value_for_line(vis_line_t line, std::string& value_out); void grep_quiesce() { if (this->tc_sub_source != nullptr) { this->tc_sub_source->quiesce(); } } void grep_begin(grep_proc& gp, vis_line_t start, vis_line_t stop); void grep_match(grep_proc& gp, vis_line_t line, int start, int end); bool is_searching() const { return this->tc_searching > 0; } void set_follow_search_for(int64_t ms_to_deadline, std::function func) { struct timeval now, tv; tv.tv_sec = ms_to_deadline / 1000; tv.tv_usec = (ms_to_deadline % 1000) * 1000; gettimeofday(&now, nullptr); timeradd(&now, &tv, &this->tc_follow_deadline); this->tc_follow_selection = this->get_selection(); this->tc_follow_func = func; } size_t get_match_count() { return this->tc_bookmarks[&BM_SEARCH].size(); } void match_reset() { this->tc_bookmarks[&BM_SEARCH].clear(); if (this->tc_sub_source != nullptr) { this->tc_sub_source->text_clear_marks(&BM_SEARCH); } } highlight_map_t& get_highlights() { return this->tc_highlights; } const highlight_map_t& get_highlights() const { return this->tc_highlights; } std::set& get_disabled_highlights() { return this->tc_disabled_highlights; } bool handle_mouse(mouse_event& me); void reload_data(); bool toggle_hide_fields() { bool retval = this->tc_hide_fields; this->tc_hide_fields = !this->tc_hide_fields; return retval; } bool get_hide_fields() const { return this->tc_hide_fields; } void set_hide_fields(bool val) { if (this->tc_hide_fields != val) { this->tc_hide_fields = val; this->set_needs_update(); } } void execute_search(const std::string& regex_orig); void redo_search(); void search_range(vis_line_t start, vis_line_t stop = -1_vl) { if (this->tc_search_child) { this->tc_search_child->get_grep_proc()->queue_request(start, stop); } if (this->tc_source_search_child) { this->tc_source_search_child->queue_request(start, stop); } } void search_new_data(vis_line_t start = -1_vl) { this->search_range(start); if (this->tc_search_child) { this->tc_search_child->get_grep_proc()->start(); } if (this->tc_source_search_child) { this->tc_source_search_child->start(); } } std::string get_current_search() const { return this->tc_current_search; } void save_current_search() { this->tc_previous_search = this->tc_current_search; } void revert_search() { this->execute_search(this->tc_previous_search); } void invoke_scroll(); textview_curses& set_reload_config_delegate( std::function func) { this->tc_reload_config_delegate = std::move(func); if (this->tc_reload_config_delegate) { this->tc_reload_config_delegate(*this); } return *this; } std::function tc_state_event_handler; std::optional tc_cursor_role; std::optional tc_disabled_cursor_role; struct selected_text_info { int sti_x; int64_t sti_line; line_range sti_range; std::string sti_value; std::string sti_href; }; std::optional tc_selected_text; bool tc_text_selection_active{false}; display_line_content_t tc_press_line; int tc_press_left{0}; protected: class grep_highlighter { public: grep_highlighter(std::shared_ptr>& gp, highlight_source_t source, std::string hl_name, highlight_map_t& hl_map) : gh_grep_proc(std::move(gp)), gh_hl_source(source), gh_hl_name(std::move(hl_name)), gh_hl_map(hl_map) { } ~grep_highlighter() { this->gh_hl_map.erase( this->gh_hl_map.find({this->gh_hl_source, this->gh_hl_name})); } grep_proc* get_grep_proc() { return this->gh_grep_proc.get(); } private: std::shared_ptr> gh_grep_proc; highlight_source_t gh_hl_source; std::string gh_hl_name; highlight_map_t& gh_hl_map; }; text_sub_source* tc_sub_source{nullptr}; std::shared_ptr tc_delegate; vis_bookmarks tc_bookmarks; int tc_searching{0}; struct timeval tc_follow_deadline { 0, 0 }; vis_line_t tc_follow_selection{-1_vl}; std::function tc_follow_func; action tc_search_action; highlight_map_t tc_highlights; std::set tc_disabled_highlights; std::optional tc_selection_start; mouse_event tc_press_event; bool tc_hide_fields{true}; bool tc_paused{false}; bool tc_supports_marks{false}; std::string tc_current_search; std::string tc_previous_search; std::shared_ptr tc_search_child; std::shared_ptr> tc_source_search_child; std::function tc_reload_config_delegate; }; #endif