From c3d51190c8c582f41681260b752d3a5b883236ea Mon Sep 17 00:00:00 2001 From: Timothy Stack Date: Wed, 6 Nov 2013 07:29:20 -0800 Subject: [PATCH] [wrap] start support for word wrapping and fix a variety of glitches --- src/grep_proc.hh | 4 +++ src/help.txt | 5 ++- src/hist_source.hh | 4 +++ src/k_merge_tree.h | 12 +++++-- src/listview_curses.cc | 35 +++++++++--------- src/listview_curses.hh | 74 ++++++++++++++++++++++++++++++++++---- src/lnav.cc | 30 +++++++++------- src/lnav_commands.cc | 38 ++++++++++++++++++-- src/logfile.hh | 14 ++++++++ src/logfile_sub_source.cc | 6 ---- src/logfile_sub_source.hh | 8 +++++ src/session_data.cc | 12 ++++--- src/sql_util.cc | 2 +- src/textfile_sub_source.hh | 10 ++++++ src/textview_curses.hh | 6 ++++ src/view_curses.hh | 2 ++ test/drive_listview.cc | 4 +++ 17 files changed, 212 insertions(+), 54 deletions(-) diff --git a/src/grep_proc.hh b/src/grep_proc.hh index 2b80afe9..371842d2 100644 --- a/src/grep_proc.hh +++ b/src/grep_proc.hh @@ -189,6 +189,10 @@ public: } }; + void invalidate() { + this->cleanup(); + }; + /** @param gpd The sink to send results to. */ void set_control(grep_proc_control *gpc) { diff --git a/src/help.txt b/src/help.txt index a78ab454..c7135cc4 100644 --- a/src/help.txt +++ b/src/help.txt @@ -325,6 +325,9 @@ COMMANDS Enable a inactive 'filter-in' or 'filter-out' expression. + disable-word-wrap Disable word wrapping in the log and text file views. + enable-word-wrap Enable word wrapping in the log and text file views. + open [:] Open the given file within lnav and, if it is a text file, switch to the text view and jump to @@ -348,7 +351,7 @@ COMMANDS session Add the given command to the session file (~/.lnav/session). Any commands listed in the session file - are executed on startup. Only the highlight and + are executed on startup. Only the highlight, word-wrap, and filter-related commands can be added to the session file. create-logline-table diff --git a/src/hist_source.hh b/src/hist_source.hh index 6bae0b9c..8ca74223 100644 --- a/src/hist_source.hh +++ b/src/hist_source.hh @@ -148,6 +148,10 @@ public: int row, string_attrs_t &value_out); + size_t text_size_for_line(textview_curses &tc, int row, bool raw) { + return 0; + }; + int value_for_row(vis_line_t row) { int grow = row / (this->buckets_per_group() + 1); diff --git a/src/k_merge_tree.h b/src/k_merge_tree.h index c88479e5..2310628d 100644 --- a/src/k_merge_tree.h +++ b/src/k_merge_tree.h @@ -86,9 +86,14 @@ public: // if false is returned, the merge is complete bool get_top(owner_t *&owner, iterator_t& iterator) { - owner = top_node_ptr_mbr->owner_ptr; - iterator = top_node_ptr_mbr->current_iterator; - return iterator != top_node_ptr_mbr->end_iterator; + if (top_node_ptr_mbr->has_iterator) { + owner = top_node_ptr_mbr->owner_ptr; + iterator = top_node_ptr_mbr->current_iterator; + return iterator != top_node_ptr_mbr->end_iterator; + } + else { + return false; + } } private: @@ -422,6 +427,7 @@ void kmerge_tree_c::compare_nodes(kmerge_tre node_rec* parent_ptr = node_ptr->parent_ptr; parent_ptr->owner_ptr = winner_ptr->owner_ptr; + parent_ptr->has_iterator = winner_ptr->has_iterator; parent_ptr->current_iterator = winner_ptr->current_iterator; parent_ptr->end_iterator = winner_ptr->end_iterator; parent_ptr->source_node_ptr = winner_ptr; diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 63331846..202baf40 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -49,6 +49,7 @@ listview_curses::listview_curses() lv_height(0), lv_needs_update(true), lv_show_scrollbar(true), + lv_word_wrap(false), lv_mouse_y(-1), lv_mouse_mode(LV_MODE_NONE) { } @@ -102,12 +103,12 @@ bool listview_curses::handle_key(int ch) case 'b': case KEY_BACKSPACE: case KEY_PPAGE: - this->shift_top(-height); + this->shift_top(-(this->rows_available(this->lv_top, RD_UP) - vis_line_t(1))); break; case ' ': case KEY_NPAGE: - this->shift_top(height); + this->shift_top(this->rows_available(this->lv_top, RD_DOWN) - vis_line_t(1)); break; case KEY_HOME: @@ -115,10 +116,9 @@ bool listview_curses::handle_key(int ch) break; case KEY_END: { - vis_line_t tail_bottom(this->get_inner_height() - height + 1); vis_line_t last_line(this->get_inner_height() - 1); + vis_line_t tail_bottom(this->get_top_for_last_row()); - tail_bottom = max(vis_line_t(0), tail_bottom); if (this->get_top() == last_line) this->set_top(tail_bottom); else if (tail_bottom <= this->get_top()) @@ -155,7 +155,7 @@ bool listview_curses::handle_key(int ch) void listview_curses::do_update(void) { if (this->lv_window != NULL && this->lv_needs_update) { - vis_line_t y(this->lv_y), height, bottom, lines, row; + vis_line_t y(this->lv_y), height, bottom, row; attr_line_t overlay_line; vis_line_t overlay_height(0); struct line_range lr; @@ -168,19 +168,13 @@ void listview_curses::do_update(void) } this->get_dimensions(height, width); - lr.lr_start = this->lv_left; - lr.lr_end = this->lv_left + width; row_count = this->get_inner_height(); - if (this->lv_top >= (int)row_count) { - this->lv_top = max(vis_line_t(0), vis_line_t(row_count) - height); - } - row = this->lv_top; - lines = min(height - overlay_height, - vis_line_t(row_count) - this->lv_top); bottom = y + height; - for (; y < bottom; ++y) { + while (y < bottom) { + lr.lr_start = this->lv_left; + lr.lr_end = this->lv_left + width; if (this->lv_overlay_source != NULL && this->lv_overlay_source->list_value_for_overlay( *this, @@ -188,24 +182,31 @@ void listview_curses::do_update(void) overlay_line)) { this->mvwattrline(this->lv_window, y, 0, overlay_line, lr); overlay_line.clear(); + ++y; } - else if (lines > 0) { + else if (row < row_count) { attr_line_t al; this->lv_source->listview_value_for_row(*this, row, al); - this->mvwattrline(this->lv_window, y, 0, al, lr); - --lines; + do { + this->mvwattrline(this->lv_window, y, 0, al, lr); + lr.lr_start += width; + lr.lr_end += width; + ++y; + } while (this->lv_word_wrap && y < bottom && lr.lr_start < al.length()); ++row; } else { wmove(this->lv_window, y, 0); wclrtoeol(this->lv_window); + ++y; } } if (this->lv_show_scrollbar) { double progress = 1.0; double coverage = 1.0; + vis_line_t lines; if (this->get_inner_height() > 0) { progress = (double)this->lv_top / (double)row_count; diff --git a/src/listview_curses.hh b/src/listview_curses.hh index 1a076e40..207f32c6 100644 --- a/src/listview_curses.hh +++ b/src/listview_curses.hh @@ -66,6 +66,9 @@ public: vis_line_t row, attr_line_t &value_out) = 0; + virtual size_t listview_size_for_row(const listview_curses &lv, + vis_line_t row) = 0; + virtual std::string listview_source_name(const listview_curses &lv) { return ""; }; @@ -149,6 +152,49 @@ public: void set_show_scrollbar(bool ss) { this->lv_show_scrollbar = ss; }; bool get_show_scrollbar() const { return this->lv_show_scrollbar; }; + void set_word_wrap(bool ww) { this->lv_word_wrap = ww; }; + bool get_word_wrap() const { return this->lv_word_wrap; }; + + enum row_direction_t { + RD_UP = -1, + RD_DOWN = 1, + }; + + vis_line_t rows_available(vis_line_t line, row_direction_t dir) { + unsigned long width; + vis_line_t height; + vis_line_t retval(0); + + this->get_dimensions(height, width); + if (this->lv_word_wrap) { + size_t row_count = this->lv_source->listview_rows(*this); + + while ((height > 0) && (line >= 0) && (line < row_count)) { + size_t len = this->lv_source->listview_size_for_row(*this, line); + + do { + len -= std::min(width, len); + --height; + } while (len > 0); + line += vis_line_t(dir); + ++retval; + } + } + else { + switch (dir) { + case RD_UP: + retval = std::min(height, line + vis_line_t(1)); + break; + case RD_DOWN: + retval = std::min(height, + vis_line_t(this->lv_source->listview_rows(*this) - line)); + break; + } + } + + return retval; + }; + /** @param win The curses window this view is attached to. */ void set_window(WINDOW *win) { this->lv_window = win; }; @@ -192,12 +238,24 @@ public: /** @return The line number that is displayed at the bottom. */ vis_line_t get_bottom() { - vis_line_t retval, height; - unsigned long width; + vis_line_t retval = this->lv_top; - this->get_dimensions(height, width); - retval = std::min(this->lv_top + height - vis_line_t(1), - vis_line_t(this->get_inner_height() - 1)); + retval += vis_line_t(this->rows_available(retval, RD_DOWN) - 1); + + return retval; + }; + + vis_line_t get_top_for_last_row() { + vis_line_t retval(0); + + if (this->get_inner_height() > 0) { + vis_line_t last_line(this->get_inner_height() - 1); + + retval = last_line - vis_line_t(this->rows_available(last_line, RD_UP) - 1); + if ((retval + 1) < this->get_inner_height()) { + ++retval; + } + } return retval; }; @@ -259,7 +317,10 @@ public: */ unsigned int shift_left(int offset) { - if (offset < 0 && this->lv_left < (unsigned int)-offset) { + if (this->lv_word_wrap) { + alerter::singleton().chime(); + } + else if (offset < 0 && this->lv_left < (unsigned int)-offset) { this->set_left(0); } else { @@ -353,6 +414,7 @@ protected: * is needed. */ bool lv_show_scrollbar; /*< Draw the scrollbar in the view. */ + bool lv_word_wrap; struct timeval lv_mouse_time; int lv_scroll_accel; diff --git a/src/lnav.cc b/src/lnav.cc index c66badda..e7678454 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -529,10 +529,9 @@ void rebuild_indexes(bool force) logfile_sub_source &lss = lnav_data.ld_log_source; textview_curses & log_view = lnav_data.ld_views[LNV_LOG]; textview_curses & text_view = lnav_data.ld_views[LNV_TEXT]; - vis_line_t old_bottom(0), height(0); + vis_line_t old_bottom(0); content_line_t top_content = content_line_t(-1); - unsigned long width; bool scroll_down; size_t old_count; time_t old_time; @@ -552,9 +551,8 @@ void rebuild_indexes(bool force) bool new_data = false; size_t new_count; - text_view.get_dimensions(height, width); - old_bottom = text_view.get_top() + height; - scroll_down = (size_t)old_bottom > tss->text_line_count(); + old_bottom = text_view.get_top_for_last_row(); + scroll_down = text_view.get_top() >= old_bottom; for (iter = tss->tss_files.begin(); iter != tss->tss_files.end(); ) { @@ -619,15 +617,14 @@ void rebuild_indexes(bool force) text_view.reload_data(); new_count = tss->text_line_count(); - if (scroll_down && new_count >= (size_t)old_bottom) { - text_view.set_top(vis_line_t(new_count - height + 1)); + if (scroll_down && text_view.get_top_for_last_row() > text_view.get_top()) { + text_view.set_top(text_view.get_top_for_last_row()); } } old_time = lnav_data.ld_top_time; - log_view.get_dimensions(height, width); - old_bottom = log_view.get_top() + height; - scroll_down = (size_t)old_bottom > old_count; + old_bottom = log_view.get_top_for_last_row(); + scroll_down = log_view.get_top() >= old_bottom; if (force) { old_count = 0; } @@ -638,8 +635,8 @@ void rebuild_indexes(bool force) log_view.reload_data(); - if (scroll_down && new_count >= (size_t)old_bottom) { - log_view.set_top(vis_line_t(new_count - height + 1)); + if (scroll_down && log_view.get_top_for_last_row() > log_view.get_top()) { + log_view.set_top(log_view.get_top_for_last_row()); } else if (!scroll_down && force) { content_line_t new_top_content = content_line_t(-1); @@ -658,6 +655,9 @@ void rebuild_indexes(bool force) start_line = force ? grep_line_t(0) : grep_line_t(-1); if (force) { + if (lnav_data.ld_search_child[LNV_LOG].get() != NULL) { + lnav_data.ld_search_child[LNV_LOG]->get_grep_proc()->invalidate(); + } log_view.match_reset(); } @@ -707,6 +707,10 @@ public: value_out = this->tds_lines[row]; }; + size_t text_size_for_line(textview_curses &tc, int row, bool raw) { + return this->tds_lines[row].length(); + }; + private: vector tds_lines; }; @@ -1981,7 +1985,7 @@ void execute_search(lnav_view_t view, const std::string ®ex) } gc.reset(); - fprintf(stderr, "start search for: %s\n", regex.c_str()); + fprintf(stderr, "start search for: '%s'\n", regex.c_str()); if (regex.empty()) { lnav_data.ld_bottom_source.grep_error(""); diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 39e3c685..2c62a86e 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -592,6 +592,36 @@ static string com_disable_filter(string cmdline, vector &args) return retval; } +static string com_enable_word_wrap(string cmdline, vector &args) +{ + string retval = ""; + + if (args.size() == 0) { + + } + else { + lnav_data.ld_views[LNV_LOG].set_word_wrap(true); + lnav_data.ld_views[LNV_TEXT].set_word_wrap(true); + } + + return retval; +} + +static string com_disable_word_wrap(string cmdline, vector &args) +{ + string retval = ""; + + if (args.size() == 0) { + + } + else { + lnav_data.ld_views[LNV_LOG].set_word_wrap(false); + lnav_data.ld_views[LNV_TEXT].set_word_wrap(false); + } + + return retval; +} + static std::vector custom_logline_tables; static string com_create_logline_table(string cmdline, vector &args) @@ -656,14 +686,16 @@ static string com_session(string cmdline, vector &args) string retval = "error: expecting a command to save to the session file"; if (args.size() == 0) {} - else if (args.size() > 2) { + else if (args.size() >= 2) { /* XXX put these in a map */ if (args[1] != "highlight" && + args[1] != "enable-word-wrap" && + args[1] != "disable-word-wrap" && args[1] != "filter-in" && args[1] != "filter-out" && args[1] != "enable-filter" && args[1] != "disable-filter") { - retval = "error: only the highlight and filter commands are " + retval = "error: only the highlight, filter, and word-wrap commands are " "supported"; } else if (getenv("HOME") == NULL) { @@ -1130,6 +1162,8 @@ void init_lnav_commands(readline_context::command_map_t &cmd_map) cmd_map["write-csv-to"] = com_save_to; cmd_map["enable-filter"] = com_enable_filter; cmd_map["disable-filter"] = com_disable_filter; + cmd_map["enable-word-wrap"] = com_enable_word_wrap; + cmd_map["disable-word-wrap"] = com_disable_word_wrap; cmd_map["create-logline-table"] = com_create_logline_table; cmd_map["delete-logline-table"] = com_delete_logline_table; cmd_map["open"] = com_open; diff --git a/src/logfile.hh b/src/logfile.hh index 748ecd93..bc131de9 100644 --- a/src/logfile.hh +++ b/src/logfile.hh @@ -225,6 +225,20 @@ public: return retval; }; + size_t line_length(iterator ll) { + iterator next_line = ll + 1; + size_t retval; + + if (next_line == this->end()) { + retval = this->lf_index_size - ll->get_offset(); + } + else { + retval = next_line->get_offset() - ll->get_offset() - 1; + } + + return retval; + }; + void read_full_message(iterator ll, std::string &msg_out, int max_lines=50); /** diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index 16d86d3f..7415fab6 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -170,8 +170,6 @@ void logfile_sub_source::text_value_for_line(textview_curses &tc, { content_line_t line(0); - size_t tab; - assert(row >= 0); assert((size_t)row < this->lss_index.size()); @@ -189,10 +187,6 @@ void logfile_sub_source::text_value_for_line(textview_curses &tc, this->lss_token_value = this->lss_token_file->read_line(this->lss_token_line); - while ((tab = this->lss_token_value.find('\t')) != string::npos) { - this->lss_token_value = this->lss_token_value.replace(tab, 1, 8, ' '); - } - this->lss_token_date_end = 0; value_out = this->lss_token_value; if (this->lss_flags & F_SCRUB) { diff --git a/src/logfile_sub_source.hh b/src/logfile_sub_source.hh index 6af5c2e4..a0fb6ef8 100644 --- a/src/logfile_sub_source.hh +++ b/src/logfile_sub_source.hh @@ -130,6 +130,14 @@ public: int row, string_attrs_t &value_out); + size_t text_size_for_line(textview_curses &tc, int row, bool raw) { + content_line_t line = this->lss_index[row]; + logfile *lf = this->find(line); + logfile::iterator ll = lf->begin() + line; + + return lf->line_length(ll) + (this->lss_flags & F_TIME_OFFSET ? 13 : 0); + }; + void text_mark(bookmark_type_t *bm, int line, bool added) { content_line_t cl = this->lss_index[line]; diff --git a/src/session_data.cc b/src/session_data.cc index 0afa3459..014bb326 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -450,8 +450,9 @@ static void load_time_bookmarks(void) continue; } - if (part_name == NULL) + if (part_name == NULL) { continue; + } if (!dts.scan(log_time, NULL, &log_tm, log_tv)) { continue; @@ -530,7 +531,7 @@ static void load_time_bookmarks(void) lss.find(lf->get_filename().c_str(), base_content_line); - fprintf(stderr, "checking bookmarks for %s\n", lf->get_filename().c_str()); + fprintf(stderr, "checking time offsets for %s\n", lf->get_filename().c_str()); logfile::iterator line_iter = lf->begin(); @@ -687,7 +688,7 @@ static int read_last_search(yajlpp_parse_context *ypc, const unsigned char *str, ypc->get_path_fragment(-2)); view_index = view_name - lnav_view_strings; - if (view_index < LNV__MAX) { + if (view_index < LNV__MAX && !regex.empty()) { execute_search((lnav_view_t)view_index, regex); lnav_data.ld_views[view_index].set_follow_search(false); } @@ -786,6 +787,7 @@ static void save_time_bookmarks(void) auto_mem stmt(sqlite3_finalize); if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) { + fprintf(stderr, "error: unable to open bookmark DB -- %s\n", db_path.c_str()); return; } @@ -813,7 +815,7 @@ static void save_time_bookmarks(void) stmt.out(), NULL) != SQLITE_OK) { fprintf(stderr, - "error: could not prepare bookmark replace statemnt -- %s\n", + "error: could not prepare bookmark delete statemnt -- %s\n", sqlite3_errmsg(db)); return; } @@ -1065,7 +1067,7 @@ static void save_time_bookmarks(void) } if (sqlite3_bind_int64(stmt.in(), 6, offset.tv_usec) != SQLITE_OK) { - fprintf(stderr, "error: could not bind offset_sec -- %s\n", + fprintf(stderr, "error: could not bind offset_usec -- %s\n", sqlite3_errmsg(db.in())); return; } diff --git a/src/sql_util.cc b/src/sql_util.cc index 8050b1cd..5c351056 100644 --- a/src/sql_util.cc +++ b/src/sql_util.cc @@ -410,7 +410,7 @@ void sql_strftime(char *buffer, size_t buffer_size, time_t time, int millis) gmtime_r(&time, &gmtm); snprintf(buffer, buffer_size, - "% 4d-%02d-%02dT%02d:%02d:%02d.%03d", + "%4d-%02d-%02dT%02d:%02d:%02d.%03d", gmtm.tm_year + 1900, gmtm.tm_mon + 1, gmtm.tm_mday, diff --git a/src/textfile_sub_source.hh b/src/textfile_sub_source.hh index 81030bae..486c4983 100644 --- a/src/textfile_sub_source.hh +++ b/src/textfile_sub_source.hh @@ -81,6 +81,16 @@ public: value_out[lr].insert(make_string_attr("file", this->current_file())); }; + size_t text_size_for_line(textview_curses &tc, int line, bool raw) { + size_t retval = 0; + + if (!this->tss_files.empty()) { + retval = this->current_file()->line_length(this->current_file()->begin() + line); + } + + return retval; + }; + logfile *current_file(void) const { if (this->tss_files.empty()) { diff --git a/src/textview_curses.hh b/src/textview_curses.hh index 943a6ba5..6dac5457 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -69,6 +69,8 @@ public: std::string &value_out, bool raw = false) = 0; + virtual size_t text_size_for_line(textview_curses &tc, int line, bool raw = false) = 0; + /** * Inform the source that the given line has been marked/unmarked. This * callback function can be used to translate between between visible line @@ -350,6 +352,10 @@ public: vis_line_t line, attr_line_t &value_out); + 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 == NULL ? "" : this->tc_sub_source->text_source_name(*this); diff --git a/src/view_curses.hh b/src/view_curses.hh index d85d131c..408282b2 100644 --- a/src/view_curses.hh +++ b/src/view_curses.hh @@ -213,6 +213,8 @@ public: /** @return The attributes for the string. */ string_attrs_t &get_attrs() { return this->al_attrs; }; + size_t length() const { return this->al_string.length(); }; + void operator=(const std::string &rhs) { this->al_string = rhs; }; /** Clear the string and the attributes for the string. */ diff --git a/test/drive_listview.cc b/test/drive_listview.cc index bddee048..39dac00a 100644 --- a/test/drive_listview.cc +++ b/test/drive_listview.cc @@ -69,6 +69,10 @@ public: } }; + size_t listview_size_for_row(const listview_curses &lv, vis_line_t row) { + return 100; + }; + bool attrline_next_token(const view_curses &vc, int line, struct line_range &lr,