From f5cc4b298fb4a6022dc6309ca87924de344781ab Mon Sep 17 00:00:00 2001 From: Timothy Stack Date: Thu, 14 Jul 2022 22:32:38 -0700 Subject: [PATCH] [ui] add back top status bar --- NEWS | 9 +- docs/schemas/config-v1.schema.json | 5 + src/base/string_attr_type.hh | 1 + src/command_executor.cc | 4 + src/help.md | 68 ++++++--- src/init.sql | 80 ++++++---- src/listview_curses.cc | 12 +- src/lnav.cc | 16 +- src/lnav.hh | 3 + src/lnav.indexing.cc | 1 + src/lnav_config.cc | 4 + src/root-config.json | 2 +- src/styling.hh | 1 + src/themes/monocai.json | 4 + src/top_status_source.cc | 142 +++++++++--------- src/top_status_source.hh | 11 +- src/view_curses.cc | 6 + ...449c0a43e895e85c8b1c9547f32d7b5b4f84f6.out | 2 +- ...b0dd8a030396742bc5acfde7715fb19f312f29.out | 2 +- ...a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out | 77 +++++++--- ...777849c39a6c34dea5b0279cd7400692f1ab5f.out | 2 +- ...2c0a90ce333365ff7354375f2c609bc27135c8.err | 2 +- ...670dfa1ae7ac5a074baa642068c6d26ac8e096.err | 2 +- ...2feef079a51410e1f8661bfe92da1c3277f665.err | 2 +- ...c1fb9affbfac609ebf1cc5556aafb1ecd223c1.err | 2 +- ...f0fc1a154b0d79b4f6e846f283426be498040f.err | 2 +- ...862ec9c8f261a8507d237eb673c7ddfaafd898.err | 2 +- test/test_sql.sh | 2 +- 28 files changed, 296 insertions(+), 170 deletions(-) diff --git a/NEWS b/NEWS index 781d74c8..c76b54ec 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,12 @@ lnav v0.10.2: Features: - * Redesigned the top status bar to make it display breadcrumbs that - are interactive. Pressing ENTER will activate the breadcrumb bar + * Redesigned the top status area to allow for user-specified + messages and added a second line that displays an interactive + breadcrumb bar. The top status line now shows the clock and + the remaining area displays whatever messages are inserted + into the lnav_user_notifications table. The information that + was originally on top is now in a second line and organized + as breadcrumbs. Pressing ENTER will activate the breadcrumb bar and the left/right cursor keys can be used to select a particular crumb while the up/down keys can select a value to switch to. While a crumb is selected, you can also type in some text to do diff --git a/docs/schemas/config-v1.schema.json b/docs/schemas/config-v1.schema.json index a7e5a51e..4e5c2977 100644 --- a/docs/schemas/config-v1.schema.json +++ b/docs/schemas/config-v1.schema.json @@ -560,6 +560,11 @@ "title": "/ui/theme-defs//status-styles/subtitle", "$ref": "#/definitions/style" }, + "info": { + "description": "Styling for informational messages in status bars", + "title": "/ui/theme-defs//status-styles/info", + "$ref": "#/definitions/style" + }, "hotkey": { "description": "Styling for hotkey highlights of status bars", "title": "/ui/theme-defs//status-styles/hotkey", diff --git a/src/base/string_attr_type.hh b/src/base/string_attr_type.hh index 6ec7c7b6..a4ef93df 100644 --- a/src/base/string_attr_type.hh +++ b/src/base/string_attr_type.hh @@ -64,6 +64,7 @@ enum class role_t : int32_t { VCR_ACTIVE_STATUS2, /*< */ VCR_STATUS_TITLE, VCR_STATUS_SUBTITLE, + VCR_STATUS_INFO, VCR_STATUS_STITCH_TITLE_TO_SUB, VCR_STATUS_STITCH_SUB_TO_TITLE, VCR_STATUS_STITCH_SUB_TO_NORMAL, diff --git a/src/command_executor.cc b/src/command_executor.cc index c53edcfb..cb5d6da4 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -83,6 +83,8 @@ sql_progress(const struct log_cursor& lc) if (ui_periodic_timer::singleton().time_to_update(sql_counter)) { lnav_data.ld_bottom_source.update_loading(off, total); + lnav_data.ld_top_source.update_time(); + lnav_data.ld_status[LNS_TOP].do_update(); lnav_data.ld_status[LNS_BOTTOM].do_update(); refresh(); } @@ -98,6 +100,8 @@ sql_progress_finished() } lnav_data.ld_bottom_source.update_loading(0, 0); + lnav_data.ld_top_source.update_time(); + lnav_data.ld_status[LNS_TOP].do_update(); lnav_data.ld_status[LNS_BOTTOM].do_update(); lnav_data.ld_views[LNV_DB].redo_search(); } diff --git a/src/help.md b/src/help.md index bff9d8e5..1a7e644b 100644 --- a/src/help.md +++ b/src/help.md @@ -15,12 +15,12 @@ efficiently zero in on problems. ## Opening Paths/URLs -The main arguments to lnav are the files, directories, glob patterns, -or URLs to be viewed. If no arguments are given, the default syslog -file for your system will be opened. These arguments will be polled -periodically so that any new data or files will be automatically -loaded. If a previously loaded file is removed or replaced, it will -be closed and the replacement opened. +The main arguments to lnav are the local/remote files, directories, +glob patterns, or URLs to be viewed. If no arguments are given, the +default syslog file for your system will be opened. These arguments +will be polled periodically so that any new data or files will be +automatically loaded. If a previously loaded file is removed or +replaced, it will be closed and the replacement opened. Note: When opening SFTP URLs, if the password is not provided for the host, the SSH agent can be used to do authentication. @@ -118,32 +118,59 @@ display has a proportionally sized 'scroll bar' that indicates your current position in the files. The scroll bar will also show areas of the file where warnings or errors are detected by coloring the bar yellow or red, respectively. Tick marks will also be added to the -left and right hand side of the bar, for search hits and bookmarks. +left and right-hand side of the bar, for search hits and bookmarks. -A bar on the left side is color coded and broken up to indicate which -messages are from the same file. Pressing the left-arrow or `h` will -reveal the source file names for each message and pressing again will -show the full paths. +The bar on the left side indicates the file the log message is from. A +break in the bar means that the next log message comes from a different +file. The color of the bar is derived from the file name. Pressing the +left-arrow or `h` will reveal the source file names for each message and +pressing again will show the full paths. -Above and below the main body are status lines that display: +Above and below the main body are status lines that display a variety +of information. The top line displays: -* the current time; -* the name of the file the top line was pulled from; -* the log format for the top line; -* the current view; -* the line number for the top line in the display; -* the current search hit, the total number of hits, and the search term; +* The current time, configurable by the `/ui/clock-format` property. +* The highest priority message from the `lnav_user_notifications` table. + You can insert rows into this table to display your own status messages. + The default message displayed on startup explains how to focus on the + next status line at the top, which is an interactive breadcrumb bar. + +The second status line at the top display breadcrumbs for the top line +in the main view. Pressing `ENTER` will focus input on the breadcrumb +bar, the cursor keys can be used to select a breadcrumb. The common +breadcrumbs are: + +* The name of the current view. +* In the log view, the timestamp of the top log message. +* In the log view, the format of the log file the top log message is from. +* The name of the file the top line was pulled from. +* If the top line is within a larger chunk of structured data, the path to + the value in the top line will be shown. + +Notes: + +1. Pressing `CTRL-A`/`CTRL-E` will select the first/last breadcrumb. +1. Typing text while a breadcrumb is selected will perform a fuzzy + search on the possibilities. + +The bottom status bar displays: + +* The line number for the top line in the display. +* The current search hit, the total number of hits, and the search term. If the view supports filtering, there will be a status line showing the following: -* the number of enabled filters and the total number of filters; -* the number of lines not displayed because of filtering. +* The number of enabled filters and the total number of filters. +* The number of lines not displayed because of filtering. To edit the filters, you can press TAB to change the focus from the main view to the filter editor. The editor allows you to create, enable/disable, and delete filters easily. +Along with filters, a "Files" panel will also be available for viewing +and controlling the files that lnav is currently monitoring. + Finally, the last line on the display is where you can enter search patterns and execute internal commands, such as converting a unix-timestamp into a human-readable date. The command-line is @@ -169,6 +196,7 @@ that you can always use `q` to pop the top view off of the stack. | **q** | Leave the current view or quit the program when in the log file view. | | Q | Similar to `q`, except it will try to sync the top time between the current and former views. For example, when leaving the spectrogram view with `Q`, the top time in that view will be matched to the top time in the log view. | | TAB | Toggle focusing on the filter editor or the main view. | +| ENTER | Focus on the breadcrumb bar. | | a/A | Restore the view that was previously popped with `q`/`Q`. The `A` hotkey will try to match the top times between the two views. | | X | Close the current text file or log file. | diff --git a/src/init.sql b/src/init.sql index 6495e16f..8ba02262 100644 --- a/src/init.sql +++ b/src/init.sql @@ -1,8 +1,9 @@ -CREATE TABLE IF NOT EXISTS http_status_codes ( - status integer PRIMARY KEY, - message text, +CREATE TABLE IF NOT EXISTS http_status_codes +( + status INTEGER PRIMARY KEY, + message TEXT, - FOREIGN KEY(status) REFERENCES access_log(sc_status) + FOREIGN KEY (status) REFERENCES access_log (sc_status) ); INSERT INTO http_status_codes VALUES (100, 'Continue'); @@ -65,33 +66,56 @@ INSERT INTO http_status_codes VALUES (508, 'Loop Detected'); INSERT INTO http_status_codes VALUES (510, 'Not Extended'); INSERT INTO http_status_codes VALUES (511, 'Network Authentication Required'); -CREATE TABLE lnav_example_log ( - log_line INTEGER PRIMARY KEY, - log_part TEXT collate naturalnocase, - log_time datetime, - log_actual_time datetime hidden, - log_idle_msecs int, - log_level TEXT collate loglevel, - log_mark boolean, - log_comment TEXT, - log_tags TEXT, - log_filters TEXT, +CREATE TABLE lnav_example_log +( + log_line INTEGER PRIMARY KEY, + log_part TEXT collate naturalnocase, + log_time datetime, + log_actual_time datetime hidden, + log_idle_msecs int, + log_level TEXT collate loglevel, + log_mark boolean, + log_comment TEXT, + log_tags TEXT, + log_filters TEXT, - ex_procname TEXT collate 'BINARY', - ex_duration INTEGER, + ex_procname TEXT collate 'BINARY', + ex_duration INTEGER, - log_time_msecs int hidden, - log_path TEXT hidden collate naturalnocase, - log_text TEXT hidden, - log_body TEXT hidden + log_time_msecs int hidden, + log_path TEXT hidden collate naturalnocase, + log_text TEXT hidden, + log_body TEXT hidden ); CREATE VIEW lnav_top_view AS - SELECT * FROM lnav_views WHERE name = ( - SELECT name FROM lnav_view_stack ORDER BY rowid DESC LIMIT 1); +SELECT * +FROM lnav_views +WHERE name = (SELECT name FROM lnav_view_stack ORDER BY rowid DESC LIMIT 1); -INSERT INTO lnav_example_log VALUES - (0, null, '2017-02-03T04:05:06.100', '2017-02-03T04:05:06.100', 0, 'info', 0, null, null, null, 'hw', 2, 1486094706000, '/tmp/log', '2017-02-03T04:05:06.100 hw(2): Hello, World!', 'Hello, World!'), - (1, null, '2017-02-03T04:05:06.200', '2017-02-03T04:05:06.200', 100, 'error', 0, null, null, null, 'gw', 4, 1486094706000, '/tmp/log', '2017-02-03T04:05:06.200 gw(4): Goodbye, World!', 'Goodbye, World!'), - (2, 'new', '2017-02-03T04:25:06.200', '2017-02-03T04:25:06.200', 1200000, 'warn', 0, null, null, null, 'gw', 1, 1486095906000, '/tmp/log', '2017-02-03T04:25:06.200 gw(1): Goodbye, World!', 'Goodbye, World!'), - (3, 'new', '2017-02-03T04:55:06.200', '2017-02-03T04:55:06.200', 1800000, 'debug', 0, null, null, null, 'gw', 10, 1486097706000, '/tmp/log', '2017-02-03T04:55:06.200 gw(10): Goodbye, World!', 'Goodbye, World!'); +INSERT INTO lnav_example_log +VALUES (0, null, '2017-02-03T04:05:06.100', '2017-02-03T04:05:06.100', 0, + 'info', 0, null, null, null, 'hw', 2, 1486094706000, '/tmp/log', + '2017-02-03T04:05:06.100 hw(2): Hello, World!', 'Hello, World!'), + (1, null, '2017-02-03T04:05:06.200', '2017-02-03T04:05:06.200', 100, + 'error', 0, null, null, null, 'gw', 4, 1486094706000, '/tmp/log', + '2017-02-03T04:05:06.200 gw(4): Goodbye, World!', 'Goodbye, World!'), + (2, 'new', '2017-02-03T04:25:06.200', '2017-02-03T04:25:06.200', 1200000, + 'warn', 0, null, null, null, 'gw', 1, 1486095906000, '/tmp/log', + '2017-02-03T04:25:06.200 gw(1): Goodbye, World!', 'Goodbye, World!'), + (3, 'new', '2017-02-03T04:55:06.200', '2017-02-03T04:55:06.200', 1800000, + 'debug', 0, null, null, null, 'gw', 10, 1486097706000, '/tmp/log', + '2017-02-03T04:55:06.200 gw(10): Goodbye, World!', 'Goodbye, World!'); + +CREATE TABLE lnav_user_notifications +( + id TEXT NOT NULL DEFAULT 'org.lnav.unknown', + priority INTEGER NOT NULL DEFAULT 0, + created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + expiration DATETIME DEFAULT NULL, + message TEXT +); + +INSERT INTO lnav_user_notifications (id, priority, expiration, message) +VALUES ('org.lnav.breadcrumb.help.focus', -1, datetime('now', '+1 minute'), + 'Press ENTER to focus on the breadcrumb bar'); diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 28942444..823499fb 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -583,8 +583,18 @@ void listview_curses::set_selection(vis_line_t sel) { if (this->lv_selectable) { + if (this->lv_selection == sel) { + return; + } + if (sel == -1_vl) { + this->lv_selection = sel; + this->lv_source->listview_selection_changed(*this); + this->set_needs_update(); + return; + } + auto inner_height = this->get_inner_height(); - if (this->lv_selection != sel && sel >= 0_vl && sel < inner_height) { + if (sel >= 0_vl && sel < inner_height) { auto found = false; vis_line_t step; diff --git a/src/lnav.cc b/src/lnav.cc index 97c8b2df..a70a7963 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -1170,7 +1170,7 @@ looper() vsb.push_back(sb); - breadcrumb_view.set_y(0); + breadcrumb_view.set_y(1); breadcrumb_view.set_window(lnav_data.ld_window); breadcrumb_view.set_line_source(lnav_crumb_source); auto event_handler = [](auto&& tc) { @@ -1182,7 +1182,7 @@ looper() }; for (lpc = 0; lpc < LNV__MAX; lpc++) { lnav_data.ld_views[lpc].set_window(lnav_data.ld_window); - lnav_data.ld_views[lpc].set_y(1); + lnav_data.ld_views[lpc].set_y(2); lnav_data.ld_views[lpc].set_height( vis_line_t(-(rlc->get_height() + 1))); lnav_data.ld_views[lpc].set_scroll_action(sb); @@ -1232,6 +1232,8 @@ looper() lnav_data.ld_spectro_source->ss_exec_context = &lnav_data.ld_exec_context; + lnav_data.ld_status[LNS_TOP].set_top(0); + lnav_data.ld_status[LNS_TOP].set_data_source(&lnav_data.ld_top_source); lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc->get_height() + 1)); for (auto& sc : lnav_data.ld_status) { sc.set_window(lnav_data.ld_window); @@ -1338,6 +1340,7 @@ looper() gettimeofday(¤t_time, nullptr); + lnav_data.ld_top_source.update_time(current_time); lnav_data.ld_preview_view.set_needs_update(); layout_views(); @@ -1444,6 +1447,7 @@ looper() lnav_data.ld_spectro_details_view.do_update(); lnav_data.ld_user_message_view.do_update(); if (ui_clock::now() >= next_status_update_time) { + lnav_data.ld_top_source.update_user_msg(); for (auto& sc : lnav_data.ld_status) { sc.do_update(); } @@ -1594,14 +1598,14 @@ looper() next_rebuild_time = next_rescan_time; } - ps->check_poll_set(pollfds); - lnav_data.ld_view_stack.top() | - [](auto tc) { lnav_data.ld_bottom_source.update_hits(tc); }; - auto old_mode = lnav_data.ld_mode; auto old_file_names_size = lnav_data.ld_active_files.fc_file_names.size(); + ps->check_poll_set(pollfds); + lnav_data.ld_view_stack.top() | + [](auto tc) { lnav_data.ld_bottom_source.update_hits(tc); }; + if (lnav_data.ld_mode != old_mode) { switch (lnav_data.ld_mode) { case ln_mode_t::PAGING: diff --git a/src/lnav.hh b/src/lnav.hh index a7cf29fd..b0045c78 100644 --- a/src/lnav.hh +++ b/src/lnav.hh @@ -70,6 +70,7 @@ #include "sql_util.hh" #include "statusview_curses.hh" #include "textfile_sub_source.hh" +#include "top_status_source.hh" #include "view_helpers.hh" class spectrogram_source; @@ -92,6 +93,7 @@ extern const std::vector lnav_zoom_strings; /** The status bars. */ typedef enum { + LNS_TOP, LNS_BOTTOM, LNS_FILTER, LNS_FILTER_HELP, @@ -195,6 +197,7 @@ struct lnav_data_t { ln_mode_t ld_last_config_mode{ln_mode_t::FILTER}; statusview_curses ld_status[LNS__MAX]; + top_status_source ld_top_source; bottom_status_source ld_bottom_source; filter_status_source ld_filter_status_source; filter_help_status_source ld_filter_help_status_source; diff --git a/src/lnav.indexing.cc b/src/lnav.indexing.cc index 4a965552..5ebab861 100644 --- a/src/lnav.indexing.cc +++ b/src/lnav.indexing.cc @@ -81,6 +81,7 @@ do_observer_update(const std::shared_ptr& lf) if (isendwin()) { return; } + lnav_data.ld_top_source.update_time(); for (auto& sc : lnav_data.ld_status) { sc.do_update(); } diff --git a/src/lnav_config.cc b/src/lnav_config.cc index 2c513236..5886b716 100644 --- a/src/lnav_config.cc +++ b/src/lnav_config.cc @@ -768,6 +768,10 @@ static const struct json_path_container theme_status_styles_handlers = { .with_description("Styling for subtitle sections of status bars") .for_child(&lnav_theme::lt_style_status_subtitle) .with_children(style_config_handlers), + yajlpp::property_handler("info") + .with_description("Styling for informational messages in status bars") + .for_child(&lnav_theme::lt_style_status_info) + .with_children(style_config_handlers), yajlpp::property_handler("hotkey") .with_description("Styling for hotkey highlights of status bars") .for_child(&lnav_theme::lt_style_status_hotkey) diff --git a/src/root-config.json b/src/root-config.json index fd302f0e..076717e3 100644 --- a/src/root-config.json +++ b/src/root-config.json @@ -1,7 +1,7 @@ { "$schema": "https://lnav.org/schemas/config-v1.schema.json", "ui" : { - "clock-format": "%a %b %d %H:%M:%S %Z", + "clock-format": "%Y-%m-%dT%H:%M:%S %Z", "dim-text": false, "default-colors": true, "keymap": "default", diff --git a/src/styling.hh b/src/styling.hh index 13d7125e..610ca13d 100644 --- a/src/styling.hh +++ b/src/styling.hh @@ -183,6 +183,7 @@ struct lnav_theme { style_config lt_style_status_title_hotkey; style_config lt_style_status_disabled_title; style_config lt_style_status_subtitle; + style_config lt_style_status_info; style_config lt_style_status_hotkey; style_config lt_style_quoted_code; style_config lt_style_code_border; diff --git a/src/themes/monocai.json b/src/themes/monocai.json index ed0793ec..cc563004 100644 --- a/src/themes/monocai.json +++ b/src/themes/monocai.json @@ -204,6 +204,10 @@ "background-color": "#66d9ee", "bold": true }, + "info": { + "color": "#aaa", + "background-color": "#353535" + }, "title-hotkey": { "color": "$black", "background-color": "#5394ec", diff --git a/src/top_status_source.cc b/src/top_status_source.cc index 22bb0281..eb5a2fde 100644 --- a/src/top_status_source.cc +++ b/src/top_status_source.cc @@ -29,40 +29,28 @@ #include "top_status_source.hh" +#include + +#include "base/injector.hh" +#include "bound_tags.hh" #include "config.h" #include "lnav_config.hh" #include "logfile_sub_source.hh" +#include "sql_util.hh" top_status_source::top_status_source() { this->tss_fields[TSF_TIME].set_width(28); - this->tss_fields[TSF_PARTITION_NAME].set_width(34); - this->tss_fields[TSF_PARTITION_NAME].set_left_pad(1); - this->tss_fields[TSF_VIEW_NAME].set_width(8); - this->tss_fields[TSF_VIEW_NAME].set_role(role_t::VCR_STATUS_TITLE); - this->tss_fields[TSF_VIEW_NAME].right_justify(true); - this->tss_fields[TSF_STITCH_VIEW_FORMAT].set_width(2); - this->tss_fields[TSF_STITCH_VIEW_FORMAT].set_stitch_value( - role_t::VCR_STATUS_STITCH_SUB_TO_TITLE, - role_t::VCR_STATUS_STITCH_TITLE_TO_SUB); - this->tss_fields[TSF_STITCH_VIEW_FORMAT].right_justify(true); - this->tss_fields[TSF_FORMAT].set_width(20); - this->tss_fields[TSF_FORMAT].set_role(role_t::VCR_STATUS_SUBTITLE); - this->tss_fields[TSF_FORMAT].right_justify(true); - this->tss_fields[TSF_STITCH_FORMAT_FILENAME].set_width(2); - this->tss_fields[TSF_STITCH_FORMAT_FILENAME].set_stitch_value( - role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB, - role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL); - this->tss_fields[TSF_STITCH_FORMAT_FILENAME].right_justify(true); - this->tss_fields[TSF_FILENAME].set_min_width(35); /* XXX */ - this->tss_fields[TSF_FILENAME].set_share(1); - this->tss_fields[TSF_FILENAME].right_justify(true); + this->tss_fields[TSF_TIME].set_role(role_t::VCR_STATUS_INFO); + this->tss_fields[TSF_USER_MSG].set_share(1); + this->tss_fields[TSF_USER_MSG].right_justify(true); + this->tss_fields[TSF_USER_MSG].set_role(role_t::VCR_STATUS_INFO); } void top_status_source::update_time(const timeval& current_time) { - status_field& sf = this->tss_fields[TSF_TIME]; + auto& sf = this->tss_fields[TSF_TIME]; char buffer[32]; buffer[0] = ' '; @@ -82,64 +70,76 @@ top_status_source::update_time() this->update_time(tv); } +struct user_msg_stmt { + user_msg_stmt() + { + static const char* MSG_QUERY = R"( +SELECT message FROM lnav_user_notifications + WHERE expiration IS NULL OR expiration > datetime('now') + ORDER BY priority DESC + LIMIT 1 +)"; + + auto& lnav_db = injector::get&, + sqlite_db_tag>(); + + auto retcode = sqlite3_prepare_v2( + lnav_db, MSG_QUERY, -1, this->ums_stmt.out(), nullptr); + + ensure(retcode == SQLITE_OK); + } + + auto_mem ums_stmt{sqlite3_finalize}; +}; + void -top_status_source::update_filename(listview_curses* lc) +top_status_source::update_user_msg() { - auto& sf_partition = this->tss_fields[TSF_PARTITION_NAME]; - auto& sf_format = this->tss_fields[TSF_FORMAT]; - auto& sf_filename = this->tss_fields[TSF_FILENAME]; + static user_msg_stmt um_stmt; + static auto& lnav_db + = injector::get&, + sqlite_db_tag>(); - if (lc->get_inner_height() > 0) { - std::vector rows(1); + auto& al = this->tss_fields[TSF_USER_MSG].get_value(); + al.clear(); - lc->get_data_source()->listview_value_for_rows( - *lc, lc->get_top(), rows); - auto& sa = rows[0].get_attrs(); - auto file_attr_opt = get_string_attr(sa, logline::L_FILE); - if (file_attr_opt) { - auto lf = file_attr_opt.value().get(); + auto* stmt = um_stmt.ums_stmt.in(); + sqlite3_reset(stmt); - if (lf->get_format()) { - sf_format.set_value("% 13s", - lf->get_format()->get_name().get()); - } else if (!lf->get_filename().empty()) { - sf_format.set_value("% 13s", "plain text"); - } else { - sf_format.clear(); + auto count = sqlite3_bind_parameter_count(stmt); + for (int lpc = 0; lpc < count; lpc++) { + const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1); + + if (name[0] == '$') { + const char* env_value; + + if ((env_value = getenv(&name[1])) != nullptr) { + sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC); } + continue; + } + } - if (sf_filename.get_width() > (ssize_t) lf->get_filename().length()) - { - sf_filename.set_value(lf->get_filename()); - } else { - sf_filename.set_value(lf->get_unique_path()); + auto step_res = sqlite3_step(stmt); + + switch (step_res) { + case SQLITE_OK: + case SQLITE_DONE: + break; + case SQLITE_ROW: { + int ncols = sqlite3_column_count(stmt); + for (int lpc = 0; lpc < ncols; lpc++) { + const auto* text = (const char*) sqlite3_column_text(stmt, lpc); + + al.with_ansi_string(text); + al.append(" "); } - } else { - sf_format.clear(); - sf_filename.clear(); + break; } - - auto part_attr_opt = get_string_attr(sa, logline::L_PARTITION); - if (part_attr_opt) { - auto bm = part_attr_opt.value().get(); - - sf_partition.set_value(bm->bm_name.c_str()); - } else { - sf_partition.clear(); - } - } else { - sf_format.clear(); - if (lc->get_data_source() != nullptr) { - sf_filename.set_value( - lc->get_data_source()->listview_source_name(*lc)); + default: { + log_error("failed to execute user-message expression: %s", + sqlite3_errmsg(lnav_db)); + break; } } } - -void -top_status_source::update_view_name(listview_curses* lc) -{ - status_field& sf_view_name = this->tss_fields[TSF_VIEW_NAME]; - - sf_view_name.set_value("%s ", lc->get_title().c_str()); -} diff --git a/src/top_status_source.hh b/src/top_status_source.hh index 8975f4e5..58668ac6 100644 --- a/src/top_status_source.hh +++ b/src/top_status_source.hh @@ -39,12 +39,7 @@ class top_status_source : public status_data_source { public: typedef enum { TSF_TIME, - TSF_PARTITION_NAME, - TSF_VIEW_NAME, - TSF_STITCH_VIEW_FORMAT, - TSF_FORMAT, - TSF_STITCH_FORMAT_FILENAME, - TSF_FILENAME, + TSF_USER_MSG, TSF__MAX } field_t; @@ -62,9 +57,7 @@ public: void update_time(); - void update_filename(listview_curses* lc); - - void update_view_name(listview_curses* lc); + void update_user_msg(); private: status_field tss_fields[TSF__MAX]; diff --git a/src/view_curses.cc b/src/view_curses.cc index 13fbf020..4f36d56b 100644 --- a/src/view_curses.cc +++ b/src/view_curses.cc @@ -879,6 +879,12 @@ view_colors::init_roles(const lnav_theme& lt, lt.lt_style_status_subtitle, lt.lt_style_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_STATUS_INFO)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_status_info, + lt.lt_style_status, + reporter); this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_STATUS_HOTKEY)] = this->to_attrs(color_pair_base, diff --git a/test/expected/test_cmds.sh_2a449c0a43e895e85c8b1c9547f32d7b5b4f84f6.out b/test/expected/test_cmds.sh_2a449c0a43e895e85c8b1c9547f32d7b5b4f84f6.out index 8241dd29..b693f512 100644 --- a/test/expected/test_cmds.sh_2a449c0a43e895e85c8b1c9547f32d7b5b4f84f6.out +++ b/test/expected/test_cmds.sh_2a449c0a43e895e85c8b1c9547f32d7b5b4f84f6.out @@ -1 +1 @@ -/ui/clock-format = "%a %b %d %H:%M:%S %Z" +/ui/clock-format = "%Y-%m-%dT%H:%M:%S %Z" diff --git a/test/expected/test_cmds.sh_35b0dd8a030396742bc5acfde7715fb19f312f29.out b/test/expected/test_cmds.sh_35b0dd8a030396742bc5acfde7715fb19f312f29.out index c5fa5ddb..fc2b6b98 100644 --- a/test/expected/test_cmds.sh_35b0dd8a030396742bc5acfde7715fb19f312f29.out +++ b/test/expected/test_cmds.sh_35b0dd8a030396742bc5acfde7715fb19f312f29.out @@ -1,3 +1,3 @@ -/ui/clock-format = "%a %b %d %H:%M:%S %Z" +/ui/clock-format = "%Y-%m-%dT%H:%M:%S %Z" info: changed config option -- /ui/clock-format /ui/clock-format = "abc" diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index cb4778a3..637b3092 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -16,12 +16,12 @@ efficiently zero in on problems. Opening Paths/URLs -The main arguments to lnav are the files, directories, glob patterns, -or URLs to be viewed. If no arguments are given, the default syslog -file for your system will be opened. These arguments will be polled -periodically so that any new data or files will be automatically -loaded. If a previously loaded file is removed or replaced, it will be -closed and the replacement opened. +The main arguments to lnav are the local/remote files, directories, +glob patterns, or URLs to be viewed. If no arguments are given, the +default syslog file for your system will be opened. These arguments +will be polled periodically so that any new data or files will be +automatically loaded. If a previously loaded file is removed or +replaced, it will be closed and the replacement opened. Note: When opening SFTP URLs, if the password is not provided for the host, the SSH agent can be used to do authentication. @@ -121,34 +121,66 @@ display has a proportionally sized 'scroll bar' that indicates your current position in the files. The scroll bar will also show areas of the file where warnings or errors are detected by coloring the bar yellow or red, respectively. Tick marks will also be added to the left -and right hand side of the bar, for search hits and bookmarks. +and right-hand side of the bar, for search hits and bookmarks. -A bar on the left side is color coded and broken up to indicate which -messages are from the same file. Pressing the left-arrow or  h  will -reveal the source file names for each message and pressing again will -show the full paths. +The bar on the left side indicates the file the log message is from. A +break in the bar means that the next log message comes from a +different file. The color of the bar is derived from the file name. +Pressing the left-arrow or  h  will reveal the source file names for +each message and pressing again will show the full paths. -Above and below the main body are status lines that display: +Above and below the main body are status lines that display a variety +of information. The top line displays: - • the current time; - • the name of the file the top line was pulled from; - • the log format for the top line; - • the current view; - • the line number for the top line in the display; - • the current search hit, the total number of hits, and - the search term; + • The current time, configurable by the  /ui/clock-format  + property. + • The highest priority message from the  lnav_user_notifications  + table. You can insert rows into this table to display + your own status messages. The default message displayed + on startup explains how to focus on the next status line + at the top, which is an interactive breadcrumb bar. + +The second status line at the top display breadcrumbs for the top line +in the main view. Pressing  ENTER  will focus input on the breadcrumb +bar, the cursor keys can be used to select a breadcrumb. The common +breadcrumbs are: + + • The name of the current view. + • In the log view, the timestamp of the top log message. + • In the log view, the format of the log file the top log + message is from. + • The name of the file the top line was pulled from. + • If the top line is within a larger chunk of structured + data, the path to the value in the top line will be + shown. + +Notes: + + 1. Pressing  CTRL-A / CTRL-E  will select the first/last + breadcrumb. + 2. Typing text while a breadcrumb is selected will + perform a fuzzy search on the possibilities. + +The bottom status bar displays: + + • The line number for the top line in the display. + • The current search hit, the total number of hits, and + the search term. If the view supports filtering, there will be a status line showing the following: - • the number of enabled filters and the total number of - filters; - • the number of lines not displayed because of filtering. + • The number of enabled filters and the total number of + filters. + • The number of lines not displayed because of filtering. To edit the filters, you can press TAB to change the focus from the main view to the filter editor. The editor allows you to create, enable/disable, and delete filters easily. +Along with filters, a "Files" panel will also be available for viewing +and controlling the files that lnav is currently monitoring. + Finally, the last line on the display is where you can enter search patterns and execute internal commands, such as converting a unix-timestamp into a human-readable date. The command-line is @@ -180,6 +212,7 @@ can always use  q  to pop the top view off of the stack. top time in the log view. TAB Toggle focusing on the filter editor or the main view. + ENTER Focus on the breadcrumb bar. a/A Restore the view that was previously popped with  q / Q . The  A  hotkey will try to match the top times between the two views. diff --git a/test/expected/test_cmds.sh_c4777849c39a6c34dea5b0279cd7400692f1ab5f.out b/test/expected/test_cmds.sh_c4777849c39a6c34dea5b0279cd7400692f1ab5f.out index 2b19cdca..f2fef20b 100644 --- a/test/expected/test_cmds.sh_c4777849c39a6c34dea5b0279cd7400692f1ab5f.out +++ b/test/expected/test_cmds.sh_c4777849c39a6c34dea5b0279cd7400692f1ab5f.out @@ -1,3 +1,3 @@ info: changed config option -- /ui/clock-format info: reset option -- /ui/clock-format -/ui/clock-format = "%a %b %d %H:%M:%S %Z" +/ui/clock-format = "%Y-%m-%dT%H:%M:%S %Z" diff --git a/test/expected/test_sql_fs_func.sh_9e2c0a90ce333365ff7354375f2c609bc27135c8.err b/test/expected/test_sql_fs_func.sh_9e2c0a90ce333365ff7354375f2c609bc27135c8.err index 5f69a7fe..18cae472 100644 --- a/test/expected/test_sql_fs_func.sh_9e2c0a90ce333365ff7354375f2c609bc27135c8.err +++ b/test/expected/test_sql_fs_func.sh_9e2c0a90ce333365ff7354375f2c609bc27135c8.err @@ -1 +1 @@ -error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to readlink(path) failed","attrs":[{"start":8,"end":16,"type":"role","value":44},{"start":17,"end":21,"type":"role","value":43},{"start":8,"end":22,"type":"role","value":57}]},"reason":{"str":"unable to stat path: non-existent-link -- No such file or directory","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to readlink(path) failed","attrs":[{"start":8,"end":16,"type":"role","value":45},{"start":17,"end":21,"type":"role","value":44},{"start":8,"end":22,"type":"role","value":58}]},"reason":{"str":"unable to stat path: non-existent-link -- No such file or directory","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} diff --git a/test/expected/test_sql_fs_func.sh_cf670dfa1ae7ac5a074baa642068c6d26ac8e096.err b/test/expected/test_sql_fs_func.sh_cf670dfa1ae7ac5a074baa642068c6d26ac8e096.err index 1e0660b3..68ee6417 100644 --- a/test/expected/test_sql_fs_func.sh_cf670dfa1ae7ac5a074baa642068c6d26ac8e096.err +++ b/test/expected/test_sql_fs_func.sh_cf670dfa1ae7ac5a074baa642068c6d26ac8e096.err @@ -1 +1 @@ -error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to realpath(path) failed","attrs":[{"start":8,"end":16,"type":"role","value":44},{"start":17,"end":21,"type":"role","value":43},{"start":8,"end":22,"type":"role","value":57}]},"reason":{"str":"Could not get real path for non-existent-path -- No such file or directory","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to realpath(path) failed","attrs":[{"start":8,"end":16,"type":"role","value":45},{"start":17,"end":21,"type":"role","value":44},{"start":8,"end":22,"type":"role","value":58}]},"reason":{"str":"Could not get real path for non-existent-path -- No such file or directory","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} diff --git a/test/expected/test_sql_json_func.sh_5f2feef079a51410e1f8661bfe92da1c3277f665.err b/test/expected/test_sql_json_func.sh_5f2feef079a51410e1f8661bfe92da1c3277f665.err index 10da9f78..aa519f16 100644 --- a/test/expected/test_sql_json_func.sh_5f2feef079a51410e1f8661bfe92da1c3277f665.err +++ b/test/expected/test_sql_json_func.sh_5f2feef079a51410e1f8661bfe92da1c3277f665.err @@ -1 +1 @@ -error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to json_contains(json, value) failed","attrs":[{"start":8,"end":21,"type":"role","value":44},{"start":22,"end":26,"type":"role","value":43},{"start":28,"end":33,"type":"role","value":43},{"start":8,"end":34,"type":"role","value":57}]},"reason":{"str":"parse error: premature EOF\n [\"hi\", \"bye\", \"solong]\n (right here) ------^","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to json_contains(json, value) failed","attrs":[{"start":8,"end":21,"type":"role","value":45},{"start":22,"end":26,"type":"role","value":44},{"start":28,"end":33,"type":"role","value":44},{"start":8,"end":34,"type":"role","value":58}]},"reason":{"str":"parse error: premature EOF\n [\"hi\", \"bye\", \"solong]\n (right here) ------^","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} diff --git a/test/expected/test_sql_str_func.sh_80c1fb9affbfac609ebf1cc5556aafb1ecd223c1.err b/test/expected/test_sql_str_func.sh_80c1fb9affbfac609ebf1cc5556aafb1ecd223c1.err index 800480f0..db216654 100644 --- a/test/expected/test_sql_str_func.sh_80c1fb9affbfac609ebf1cc5556aafb1ecd223c1.err +++ b/test/expected/test_sql_str_func.sh_80c1fb9affbfac609ebf1cc5556aafb1ecd223c1.err @@ -1 +1 @@ -error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to regexp_match(re, str) failed","attrs":[{"start":8,"end":20,"type":"role","value":44},{"start":21,"end":23,"type":"role","value":43},{"start":25,"end":28,"type":"role","value":43},{"start":8,"end":29,"type":"role","value":57}]},"reason":{"str":"regular expression does not have any captures","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to regexp_match(re, str) failed","attrs":[{"start":8,"end":20,"type":"role","value":45},{"start":21,"end":23,"type":"role","value":44},{"start":25,"end":28,"type":"role","value":44},{"start":8,"end":29,"type":"role","value":58}]},"reason":{"str":"regular expression does not have any captures","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} diff --git a/test/expected/test_sql_time_func.sh_42f0fc1a154b0d79b4f6e846f283426be498040f.err b/test/expected/test_sql_time_func.sh_42f0fc1a154b0d79b4f6e846f283426be498040f.err index da349a48..fc569fe2 100644 --- a/test/expected/test_sql_time_func.sh_42f0fc1a154b0d79b4f6e846f283426be498040f.err +++ b/test/expected/test_sql_time_func.sh_42f0fc1a154b0d79b4f6e846f283426be498040f.err @@ -1 +1 @@ -error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to timeslice(time, slice) failed","attrs":[{"start":8,"end":17,"type":"role","value":44},{"start":18,"end":22,"type":"role","value":43},{"start":24,"end":29,"type":"role","value":43},{"start":8,"end":30,"type":"role","value":57}]},"reason":{"str":"unable to parse time slice value: blah -- Unrecognized input","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to timeslice(time, slice) failed","attrs":[{"start":8,"end":17,"type":"role","value":45},{"start":18,"end":22,"type":"role","value":44},{"start":24,"end":29,"type":"role","value":44},{"start":8,"end":30,"type":"role","value":58}]},"reason":{"str":"unable to parse time slice value: blah -- Unrecognized input","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} diff --git a/test/expected/test_sql_time_func.sh_72862ec9c8f261a8507d237eb673c7ddfaafd898.err b/test/expected/test_sql_time_func.sh_72862ec9c8f261a8507d237eb673c7ddfaafd898.err index 0ddbc0e5..d672b575 100644 --- a/test/expected/test_sql_time_func.sh_72862ec9c8f261a8507d237eb673c7ddfaafd898.err +++ b/test/expected/test_sql_time_func.sh_72862ec9c8f261a8507d237eb673c7ddfaafd898.err @@ -1 +1 @@ -error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to timeslice(time, slice) failed","attrs":[{"start":8,"end":17,"type":"role","value":44},{"start":18,"end":22,"type":"role","value":43},{"start":24,"end":29,"type":"role","value":43},{"start":8,"end":30,"type":"role","value":57}]},"reason":{"str":"no time slice value given","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"call to timeslice(time, slice) failed","attrs":[{"start":8,"end":17,"type":"role","value":45},{"start":18,"end":22,"type":"role","value":44},{"start":24,"end":29,"type":"role","value":44},{"start":8,"end":30,"type":"role","value":58}]},"reason":{"str":"no time slice value given","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}} diff --git a/test/test_sql.sh b/test/test_sql.sh index f82cd37e..ae1c00d3 100644 --- a/test/test_sql.sh +++ b/test/test_sql.sh @@ -888,7 +888,7 @@ CREATE TABLE lnav_events ( ts TEXT NOT NULL DEFAULT(strftime('%Y-%m-%dT%H:%M:%f', 'now')), content TEXT ); -CREATE TABLE http_status_codes ( +CREATE TABLE http_status_codes EOF