diff options
Diffstat (limited to '')
-rw-r--r-- | src/textview_curses.hh | 763 |
1 files changed, 763 insertions, 0 deletions
diff --git a/src/textview_curses.hh b/src/textview_curses.hh new file mode 100644 index 0000000..51bb4fe --- /dev/null +++ b/src/textview_curses.hh @@ -0,0 +1,763 @@ +/** + * 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 <utility> +#include <vector> + +#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 "logfile_fwd.hh" +#include "ring_span.hh" +#include "text_format.hh" +#include "textview_curses_fwd.hh" + +class textview_curses; + +using vis_bookmarks = bookmarks<vis_line_t>::type; + +class logfile_filter_state { +public: + logfile_filter_state(std::shared_ptr<logfile> 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); + + nonstd::optional<size_t> content_line_to_vis_line(uint32_t line); + + const static int MAX_FILTERS = 32; + + std::shared_ptr<logfile> 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<uint32_t> tfs_mask; + std::vector<uint32_t> tfs_index; +}; + +enum class filter_lang_t : int { + NONE, + REGEX, + SQL, +}; + +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) + : 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, + shared_buffer_ref& line); + + void end_of_message(logfile_filter_state& lfs); + + virtual bool matches(const logfile& lf, + logfile_const_iterator ll, + 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(const logfile& lf, + logfile_const_iterator ll, + shared_buffer_ref& line) override; + + std::string to_command() const override; +}; + +class filter_stack { +public: + using iterator = std::vector<std::shared_ptr<text_filter>>::iterator; + using const_iterator + = std::vector<std::shared_ptr<text_filter>>::const_iterator; + using value_type = std::shared_ptr<text_filter>; + + 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; + } + + nonstd::optional<size_t> next_index(); + + void add_filter(const std::shared_ptr<text_filter>& filter); + + void clear_filters() + { + while (!this->fs_filters.empty()) { + this->fs_filters.pop_back(); + } + } + + void set_filter_enabled(const std::shared_ptr<text_filter>& filter, + bool enabled) + { + if (enabled) { + filter->enable(); + } else { + filter->disable(); + } + } + + std::shared_ptr<text_filter> 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<std::shared_ptr<text_filter>> fs_filters; +}; + +class text_time_translator { +public: + virtual ~text_time_translator() = default; + + virtual nonstd::optional<vis_line_t> row_for_time( + struct timeval time_bucket) + = 0; + + virtual nonstd::optional<struct timeval> time_for_row(vis_line_t row) = 0; + + void scroll_invoked(textview_curses* tc); + + void data_reloaded(textview_curses* tc); + +protected: + struct timeval ttt_top_time { + 0, 0 + }; +}; + +class text_anchors { +public: + virtual ~text_anchors() = default; + + static std::string to_anchor_string(const std::string& raw); + + virtual nonstd::optional<vis_line_t> row_for_anchor(const std::string& id) + = 0; + + virtual nonstd::optional<std::string> anchor_for_row(vis_line_t vl) = 0; + + virtual std::unordered_set<std::string> get_anchors() = 0; +}; + +class location_history { +public: + virtual ~location_history() = default; + + virtual void loc_history_append(vis_line_t top) = 0; + + virtual nonstd::optional<vis_line_t> loc_history_back( + vis_line_t current_top) + = 0; + + virtual nonstd::optional<vis_line_t> 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) + { + } + + 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 nonstd::optional< + std::pair<grep_proc_source<vis_line_t>*, grep_proc_sink<vis_line_t>*>> + get_grepper() + { + return nonstd::nullopt; + } + + virtual nonstd::optional<location_history*> get_location_history() + { + return nonstd::nullopt; + } + + void toggle_apply_filters(); + + virtual void text_crumbs_for_line(int line, + std::vector<breadcrumb::crumb>& crumbs); + + virtual void quiesce() {} + + 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; + + nonstd::optional<vis_line_t> loc_history_back( + vis_line_t current_top) override; + + nonstd::optional<vis_line_t> loc_history_forward( + vis_line_t current_top) override; + + nonstd::ring_span<vis_line_t> 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 void text_overlay(textview_curses& tc) {} + + virtual bool text_handle_mouse(textview_curses& tc, 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<vis_line_t> + , public grep_proc_sink<vis_line_t> + , public lnav_config_listener { +public: + using action = std::function<void(textview_curses*)>; + + 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; + + 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_delegate(std::shared_ptr<text_delegate> del) + { + this->tc_delegate = del; + + return *this; + } + + std::shared_ptr<text_delegate> get_delegate() const + { + return this->tc_delegate; + } + + nonstd::optional<std::pair<int, int>> 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<vis_line_t>& gp); + void grep_end(grep_proc<vis_line_t>& 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<attr_line_t>& 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<vis_line_t>& gp, + vis_line_t start, + vis_line_t stop); + void grep_match(grep_proc<vis_line_t>& 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<bool()> 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_top = this->get_top(); + 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<highlight_source_t>& get_disabled_highlights() + { + return this->tc_disabled_highlights; + } + + bool handle_mouse(mouse_event& me); + + void reload_data(); + + void do_update() + { + this->listview_curses::do_update(); + if (this->tc_delegate != nullptr) { + this->tc_delegate->text_overlay(*this); + } + } + + 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 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() + { + if (this->tc_sub_source != nullptr) { + auto ttt = dynamic_cast<text_time_translator*>(this->tc_sub_source); + + if (ttt != nullptr) { + ttt->scroll_invoked(this); + } + } + + listview_curses::invoke_scroll(); + } + + std::function<void(textview_curses&)> tc_state_event_handler; + +protected: + class grep_highlighter { + public: + grep_highlighter(std::shared_ptr<grep_proc<vis_line_t>>& 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<vis_line_t>* get_grep_proc() + { + return this->gh_grep_proc.get(); + } + + private: + std::shared_ptr<grep_proc<vis_line_t>> 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<text_delegate> tc_delegate; + + vis_bookmarks tc_bookmarks; + + int tc_searching{0}; + struct timeval tc_follow_deadline { + 0, 0 + }; + vis_line_t tc_follow_top{-1_vl}; + std::function<bool()> tc_follow_func; + action tc_search_action; + + highlight_map_t tc_highlights; + std::set<highlight_source_t> tc_disabled_highlights; + + vis_line_t tc_selection_start{-1_vl}; + vis_line_t tc_selection_last{-1_vl}; + bool tc_selection_cleared{false}; + bool tc_hide_fields{true}; + bool tc_paused{false}; + + std::string tc_current_search; + std::string tc_previous_search; + std::shared_ptr<grep_highlighter> tc_search_child; + std::shared_ptr<grep_proc<vis_line_t>> tc_source_search_child; +}; + +#endif |