diff --git a/src/help.txt b/src/help.txt index 76edc517..eadf6f2a 100644 --- a/src/help.txt +++ b/src/help.txt @@ -150,7 +150,8 @@ through the file. K Like 'J' except it toggles the mark on the previous line. - c Copy the marked text to the X selection buffer. + c Copy the marked text to the X11 selection buffer or OS X + clipboard. C Clear all marked lines. diff --git a/src/line_buffer.cc b/src/line_buffer.cc index 503e2b57..41140536 100644 --- a/src/line_buffer.cc +++ b/src/line_buffer.cc @@ -21,7 +21,7 @@ using namespace std; static const size_t DEFAULT_LINE_BUFFER_SIZE = 256 * 1024; -static const size_t MAX_LINE_BUFFER_SIZE = 2 * DEFAULT_LINE_BUFFER_SIZE; +static const size_t MAX_LINE_BUFFER_SIZE = 16 * DEFAULT_LINE_BUFFER_SIZE; static const size_t DEFAULT_INCREMENT = 1024; static const size_t MAX_COMPRESSED_BUFFER_SIZE = 32 * 1024 * 1024; diff --git a/src/listview_curses.hh b/src/listview_curses.hh index 913a47d6..f2190f72 100644 --- a/src/listview_curses.hh +++ b/src/listview_curses.hh @@ -129,6 +129,10 @@ public: return retval; }; + bool is_visible(vis_line_t line) { + return (this->get_top() <= line && line <= this->get_bottom()); + }; + /** * Shift the value of top by the given value. * diff --git a/src/lnav.cc b/src/lnav.cc index 81def542..c8e28830 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -110,6 +110,12 @@ bool check_experimental(const char *feature_name) return false; } +/** + * Compute the path to a file in the user's '.lnav' directory. + * + * @param sub The path to the file in the '.lnav' directory. + * @return The full path + */ string dotlnav_path(const char *sub) { string retval; @@ -122,6 +128,9 @@ string dotlnav_path(const char *sub) snprintf(hpath, sizeof(hpath), "%s/.lnav/%s", home, sub); retval = hpath; } + else { + retval = sub; + } return retval; } @@ -132,6 +141,9 @@ void sqlite_close_wrapper(void *mem) sqlite3_close((sqlite3*)mem); } +/** + * Observer for loading progress that updates the bottom status bar. + */ class loading_observer : public logfile_sub_source::observer { public: @@ -251,9 +263,13 @@ void rebuild_indexes(bool force) if ((*iter)->get_format() != NULL) { logfile *lf = *iter; - iter = tss->tss_files.erase(iter); - lnav_data.ld_log_source.insert_file(lf); - force = true; + if (lnav_data.ld_log_source.insert_file(lf)) { + iter = tss->tss_files.erase(iter); + force = true; + } + else { + ++iter; + } } else { ++iter; @@ -827,20 +843,24 @@ static void handle_paging_key(int ch) } break; case 'J': - // TODO: if they scroll up, we should start marking again from the top. - // We should also scroll down as the continue to mark stuff. If they - // move somewhere else in the file, we should also start marking from - // the top again. if (lss) { - if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end()) { + if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end() || + !tc->is_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) { lnav_data.ld_last_user_mark[tc] = tc->get_top(); } - else if (lnav_data.ld_last_user_mark[tc] + 1 > tc->get_bottom()) { - flash(); - break; // XXX - } else { - lnav_data.ld_last_user_mark[tc] += 1; + vis_line_t height; + unsigned long width; + + tc->get_dimensions(height, width); + if (lnav_data.ld_last_user_mark[tc] > tc->get_bottom() - 2 && + tc->get_top() + height < tc->get_inner_height()) { + tc->shift_top(vis_line_t(1)); + } + if (lnav_data.ld_last_user_mark[tc] + 1 >= tc->get_inner_height()) { + break; + } + lnav_data.ld_last_user_mark[tc] += 1; } lss->toggle_user_mark(&textview_curses::BM_USER, vis_line_t(lnav_data.ld_last_user_mark[tc])); @@ -848,20 +868,30 @@ static void handle_paging_key(int ch) } break; case 'K': - // TODO: scroll up with the selection if (lss) { - if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end()) { - lnav_data.ld_last_user_mark[tc] = tc->get_top(); + int new_mark; + + if (lnav_data.ld_last_user_mark.find(tc) == lnav_data.ld_last_user_mark.end() || + !tc->is_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) { + lnav_data.ld_last_user_mark[tc] = -1; + new_mark = tc->get_top(); } + else { + new_mark = lnav_data.ld_last_user_mark[tc]; + } lss->toggle_user_mark(&textview_curses::BM_USER, - vis_line_t(lnav_data.ld_last_user_mark[tc])); - if (lnav_data.ld_last_user_mark[tc] - 1 < 0) { - flash(); - } - else { - lnav_data.ld_last_user_mark[tc] -= 1; - } + vis_line_t(new_mark)); + if (new_mark == tc->get_top()) { + tc->shift_top(vis_line_t(-1)); + } + if (new_mark > 0) { + lnav_data.ld_last_user_mark[tc] = new_mark - 1; + } + else { + lnav_data.ld_last_user_mark[tc] = new_mark; + flash(); + } tc->reload_data(); } break; @@ -1453,6 +1483,13 @@ static pcre *xpcre_compile(const char *pattern, int options = 0) return retval; } +/** + * Callback used to keep track of the timestamps for the top and bottom lines + * in the log view. This function is intended to be used as the callback + * function in a view_action. + * + * @param lv The listview object that contains the log + */ static void update_times(void *, listview_curses *lv) { if (lv == &lnav_data.ld_views[LNV_LOG] && lv->get_inner_height() > 0) { @@ -2569,6 +2606,7 @@ int main(int argc, char *argv[]) DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/messages"))); DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/system.log"))); DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/syslog"))); + DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/syslog.log"))); init_lnav_commands(lnav_commands); @@ -2592,7 +2630,7 @@ int main(int argc, char *argv[]) lnav_data.ld_looping = true; lnav_data.ld_mode = LNM_PAGING; - lnav_data.ld_debug_log_name = "/dev/null"; // XXX change to /dev/null + lnav_data.ld_debug_log_name = "/dev/null"; while ((c = getopt(argc, argv, "harsd:V")) != -1) { switch (c) { case 'h': diff --git a/src/log_format_impls.cc b/src/log_format_impls.cc index e226e66e..cc47a23d 100644 --- a/src/log_format_impls.cc +++ b/src/log_format_impls.cc @@ -1,3 +1,6 @@ +/** + * @file log_format_impls.cc + */ #include @@ -108,6 +111,19 @@ class syslog_log_format : public log_format { return SCRUB_PATTERN; } + static pcrepp &error_pattern(void) { + static pcrepp ERROR_PATTERN("(?:failed|failure|error)", PCRE_CASELESS); + + return ERROR_PATTERN; + } + + static pcrepp &warning_pattern(void) { + static pcrepp WARNING_PATTERN( + "(?:warn|not responding|init: cannot execute)", PCRE_CASELESS); + + return WARNING_PATTERN; + } + string get_name() { return "syslog_log"; }; void scrub(string &line) { @@ -144,17 +160,15 @@ class syslog_log_format : public log_format { if ((rest = strptime(prefix, "%b %d %H:%M:%S", &log_time)) != NULL) { + pcre_context_static<20> context; + pcre_input pi(prefix, 0, len); logline::level_t ll = logline::LEVEL_UNKNOWN; time_t log_gmt; - if (strcasestr(prefix, "failed") != NULL || - strcasestr(prefix, "failure") != NULL || - strcasestr(prefix, "error") != NULL) { + if (error_pattern().match(context, pi)) { ll = logline::LEVEL_ERROR; } - else if (strcasestr(prefix, "warn") != NULL || - strcasestr(prefix, "not responding") != NULL || - strcasestr(prefix, "init: cannot execute") != NULL) { + else if (warning_pattern().match(context, pi)) { ll = logline::LEVEL_WARNING; } log_gmt = tm2sec(&log_time); diff --git a/src/logfile_sub_source.hh b/src/logfile_sub_source.hh index 90cc231e..a52fd33d 100644 --- a/src/logfile_sub_source.hh +++ b/src/logfile_sub_source.hh @@ -1,5 +1,11 @@ -#ifndef __logfile_controller_hh -#define __logfile_controller_hh +/** + * @file logfile_sub_source.hh + */ + +#ifndef __logfile_sub_source_hh +#define __logfile_sub_source_hh + +#include #include #include @@ -43,6 +49,10 @@ protected: std::string lf_id; }; +/** + * Delegate class that merges the contents of multiple log files into a single + * source of data for a text view. + */ class logfile_sub_source : public text_sub_source { public: @@ -124,7 +134,7 @@ public: this->lss_user_marks[bm].clear(); }; - void insert_file(logfile *lf) + bool insert_file(logfile *lf) { std::vector::iterator existing; @@ -134,12 +144,18 @@ public: this->lss_files.end(), logfile_data_eq(NULL)); if (existing == this->lss_files.end()) { + if (this->lss_files.size() >= MAX_FILES) { + return false; + } + this->lss_files.push_back(logfile_data(lf)); this->lss_index.clear(); } else { existing->ld_file = lf; } + + return true; }; void remove_file(logfile *lf) @@ -232,9 +248,10 @@ public: content_line_t at(vis_line_t vl) { return this->lss_index[vl]; }; -private: static const size_t MAX_LINES_PER_FILE = 4 * 1024 * 1024; + static const size_t MAX_FILES = INT_MAX / MAX_LINES_PER_FILE; +private: enum { B_SCRUB, B_TIME_OFFSET,