From ee345321f7781592ffb5d4818626afbdfa33c21f Mon Sep 17 00:00:00 2001 From: Tim Stack Date: Sat, 17 Feb 2024 07:16:14 -0800 Subject: [PATCH] [hotkeys] change curly braces to move to the next/prev section and... a bunch of other stuff --- NEWS.md | 8 + src/base/ansi_scrubber.cc | 23 +- src/base/lnav.console.cc | 29 +- src/base/string_util.tests.cc | 10 + src/base/strnatcmp.c | 4 +- src/data_scanner.hh | 17 ++ src/document.sections.cc | 253 +++++++++++++++--- src/document.sections.hh | 15 ++ src/field_overlay_source.cc | 47 ++-- src/field_overlay_source.hh | 9 +- src/formats/vmw_py_log.json | 3 + src/internals/cmd-ref.rst | 38 ++- src/keymaps/default-keymap.json | 8 +- src/listview_curses.cc | 3 + src/lnav.cc | 5 +- src/lnav_commands.cc | 81 +++++- src/log_format_impls.cc | 3 +- src/logfile.cc | 9 +- src/logfile_sub_source.cc | 9 +- src/plain_text_source.cc | 194 +++++++++++++- src/plain_text_source.hh | 5 + src/pretty_printer.cc | 51 ++-- src/pretty_printer.hh | 5 +- src/readline_callbacks.cc | 2 +- src/textfile_sub_source.cc | 213 ++++++++++++--- src/textfile_sub_source.hh | 3 + src/textview_curses.hh | 11 + src/time-extension-functions.cc | 1 - src/time_formats.am | 1 + src/url_handler.cc | 2 +- src/view_curses.cc | 6 + src/view_helpers.cc | 39 +-- test/Makefile.am | 1 + test/books.json | 14 + test/expected/expected.am | 4 + ...3639753916f71254e8c9cce4ebb8bfd9978d3e.out | 8 +- ...06341dd560f927512e92c7c0985ed8b25827ae.out | 4 +- ...a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out | 33 ++- ...55a8b48c0f602f0270500b0117b76e11db546e.out | 5 +- ...8cac40c27f547044c89d39eb0ff2ef81da4b21.out | 4 +- ...9f005b0708d629bc95f0c48a5e390f440c1fef.out | 4 +- ...6b3cdd46b387e72d6faa4cce648b8b11ae870b.out | 4 +- ...1ded92531350668301431db64df2d2f4a2e9ee.out | 2 - ...3a40164c93c7ec44a66e7940b92b128a421147.err | 0 ...3a40164c93c7ec44a66e7940b92b128a421147.out | 5 + ...d174410d702a7b4be794fb6fa2c8889bd768d6.err | 0 ...d174410d702a7b4be794fb6fa2c8889bd768d6.out | 6 + ...6b120fbea638472a27964444e262b4572afacc.err | 0 ...6b120fbea638472a27964444e262b4572afacc.out | 5 + ...4954af3e536b3789b1fd5b33519e9d444cc933.err | 0 ...4954af3e536b3789b1fd5b33519e9d444cc933.out | 6 + test/logfile_json.json | 4 +- test/test_text_file.sh | 18 ++ 53 files changed, 1016 insertions(+), 218 deletions(-) create mode 100644 test/books.json create mode 100644 test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.err create mode 100644 test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out create mode 100644 test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.err create mode 100644 test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.out create mode 100644 test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.err create mode 100644 test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.out create mode 100644 test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.err create mode 100644 test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.out diff --git a/NEWS.md b/NEWS.md index 913fc6bb..e240b116 100644 --- a/NEWS.md +++ b/NEWS.md @@ -122,6 +122,11 @@ Features: * Added a `log_msg_values` column to the `all_logs` SQL table that contains a JSON object with the top 5 values for the fields extracted from the log message. +* Added `:next-section` and `:prev-section` commands for + moving to the next and previous section of a document. + For example, the next section in a man page or JSON + array. The default keymap has been changed to bind + the curly brace keys to these commands. * Added Nextcloud log format from Adam Monsen. * Added GitHub Event Log format for files from gharchive.org. It makes a good example of a JSON-Lines format. @@ -158,6 +163,9 @@ Interface changes: used to draw the overlay contents now as well. (The overlay is used to display the parser details, comments, and annotations.) +* The `{` and `}` keys have been changed from moving + through the "location history" to moving to the previous + and next section in a document. * Added indent guidelines when structured data is detected. Breaking changes: diff --git a/src/base/ansi_scrubber.cc b/src/base/ansi_scrubber.cc index 7fe4099d..e83853aa 100644 --- a/src/base/ansi_scrubber.cc +++ b/src/base/ansi_scrubber.cc @@ -174,6 +174,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa) if (lhs_pair.first == '_' || rhs_pair.first == '_') { if (sa != nullptr && bold_range.is_valid()) { + shift_string_attrs( + *sa, bold_range.lr_start, -bold_range.length() * 2); sa->emplace_back(bold_range, VC_STYLE.value(text_attrs{A_BOLD})); bold_range.clear(); @@ -191,6 +193,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa) }); } else { if (sa != nullptr && ul_range.is_valid()) { + shift_string_attrs( + *sa, ul_range.lr_start, -ul_range.length() * 2); sa->emplace_back( ul_range, VC_STYLE.value(text_attrs{A_UNDERLINE})); ul_range.clear(); @@ -216,26 +220,25 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa) auto output_size = fill_index - sf.sf_begin; auto erased_size = sf.length() - output_size; - if (sa != nullptr) { -#if 0 - shift_string_attrs( - *sa, caps->c_begin + sf.length() / 3, -erased_size); -#endif - sa->emplace_back(line_range{last_origin_offset_end, - sf.sf_begin + (int) output_size}, - SA_ORIGIN_OFFSET.value(origin_offset)); - } - if (sa != nullptr && ul_range.is_valid()) { + shift_string_attrs( + *sa, ul_range.lr_start, -ul_range.length() * 2); sa->emplace_back(ul_range, VC_STYLE.value(text_attrs{A_UNDERLINE})); ul_range.clear(); } if (sa != nullptr && bold_range.is_valid()) { + shift_string_attrs( + *sa, bold_range.lr_start, -bold_range.length() * 2); sa->emplace_back(bold_range, VC_STYLE.value(text_attrs{A_BOLD})); bold_range.clear(); } + if (sa != nullptr) { + sa->emplace_back(line_range{last_origin_offset_end, + sf.sf_begin + (int) output_size}, + SA_ORIGIN_OFFSET.value(origin_offset)); + } str.erase(str.begin() + fill_index, str.begin() + sf.sf_end); last_origin_offset_end = sf.sf_begin + output_size; diff --git a/src/base/lnav.console.cc b/src/base/lnav.console.cc index 0a23e8a6..9635e5ab 100644 --- a/src/base/lnav.console.cc +++ b/src/base/lnav.console.cc @@ -277,13 +277,38 @@ curses_color_to_terminal_color(int curses_color) } } +static bool +get_no_color() +{ + return getenv("NO_COLOR") != nullptr; +} + +static bool +get_yes_color() +{ + return getenv("YES_COLOR") != nullptr; +} + +static bool +get_fd_tty(int fd) +{ + return isatty(fd); +} + void println(FILE* file, const attr_line_t& al) { + static const auto IS_NO_COLOR = get_no_color(); + static const auto IS_YES_COLOR = get_yes_color(); + static const auto IS_STDOUT_TTY = get_fd_tty(STDOUT_FILENO); + static const auto IS_STDERR_TTY = get_fd_tty(STDERR_FILENO); + const auto& str = al.get_string(); - if (getenv("NO_COLOR") != nullptr - || (!isatty(fileno(file)) && getenv("YES_COLOR") == nullptr)) + if (IS_NO_COLOR || (file != stdout && file != stderr) + || (((file == stdout && !IS_STDOUT_TTY) + || (file == stderr && !IS_STDERR_TTY)) + && !IS_YES_COLOR)) { fmt::print(file, "{}\n", str); return; diff --git a/src/base/string_util.tests.cc b/src/base/string_util.tests.cc index 8dbd97b4..8b9a9014 100644 --- a/src/base/string_util.tests.cc +++ b/src/base/string_util.tests.cc @@ -103,4 +103,14 @@ TEST_CASE("strnatcmp") CHECK(strnatcasecmp(lhs.length(), lhs.data(), rhs.length(), rhs.data()) < 0); } + + { + const std::string a = "10.112.81.15"; + const std::string b = "192.168.202.254"; + + int ipcmp = 0; + auto rc = ipv4cmp(a.length(), a.c_str(), b.length(), b.c_str(), &ipcmp); + CHECK(rc == 1); + CHECK(ipcmp == -1); + } } diff --git a/src/base/strnatcmp.c b/src/base/strnatcmp.c index 67731641..0ea332d2 100644 --- a/src/base/strnatcmp.c +++ b/src/base/strnatcmp.c @@ -275,13 +275,13 @@ int ipv4cmp(int a_len, nat_char const *a, } for (; ai < a_len; ai++) { - if (!isdigit((unsigned char)a[ai]) || a[ai] != '.') { + if (!isdigit((unsigned char)a[ai]) && a[ai] != '.') { return 0; } } for (; bi < b_len; bi++) { - if (!isdigit((unsigned char)b[bi]) || b[bi] != '.') { + if (!isdigit((unsigned char)b[bi]) && b[bi] != '.') { return 0; } } diff --git a/src/data_scanner.hh b/src/data_scanner.hh index 5fa554ff..947a43ec 100644 --- a/src/data_scanner.hh +++ b/src/data_scanner.hh @@ -224,4 +224,21 @@ private: bool ds_units{false}; }; +inline data_token_t +to_closer(data_token_t dt) +{ + switch (dt) { + case DT_XML_OPEN_TAG: + return DT_XML_CLOSE_TAG; + case DT_LCURLY: + return DT_RCURLY; + case DT_LSQUARE: + return DT_RSQUARE; + case DT_LPAREN: + return DT_RPAREN; + default: + ensure(0); + } +} + #endif diff --git a/src/document.sections.cc b/src/document.sections.cc index 09ba9015..47cd9666 100644 --- a/src/document.sections.cc +++ b/src/document.sections.cc @@ -27,6 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include @@ -60,6 +61,117 @@ hier_node::lookup_child(section_key_t key) const })); } +nonstd::optional +hier_node::child_index(const hier_node* hn) const +{ + size_t retval = 0; + + for (const auto& child : this->hn_children) { + if (child.get() == hn) { + return retval; + } + retval += 1; + } + + return nonstd::nullopt; +} + +nonstd::optional +hier_node::child_neighbors(const lnav::document::hier_node* hn, + file_off_t offset) const +{ + auto index_opt = this->child_index(hn); + if (!index_opt) { + return nonstd::nullopt; + } + + hier_node::child_neighbors_result retval; + + if (index_opt.value() == 0) { + if (this->hn_parent != nullptr) { + auto parent_neighbors_opt + = this->hn_parent->child_neighbors(this, offset); + + if (parent_neighbors_opt) { + retval.cnr_previous = parent_neighbors_opt->cnr_previous; + } + } else { + retval.cnr_previous = hn; + } + } else { + const auto* prev_hn = this->hn_children[index_opt.value() - 1].get(); + + if (hn->hn_line_number == 0 + || (hn->hn_line_number - prev_hn->hn_line_number) > 1) + { + retval.cnr_previous = prev_hn; + } else if (this->hn_parent != nullptr) { + auto parent_neighbors_opt + = this->hn_parent->child_neighbors(this, offset); + + if (parent_neighbors_opt) { + retval.cnr_previous = parent_neighbors_opt->cnr_previous; + } + } + } + + if (index_opt.value() == this->hn_children.size() - 1) { + if (this->hn_parent != nullptr) { + auto parent_neighbors_opt + = this->hn_parent->child_neighbors(this, offset); + + if (parent_neighbors_opt) { + retval.cnr_next = parent_neighbors_opt->cnr_next; + } + } else if (!hn->hn_children.empty()) { + for (const auto& child : hn->hn_children) { + if (child->hn_start > offset) { + retval.cnr_next = child.get(); + break; + } + } + } + } else { + const auto* next_hn = this->hn_children[index_opt.value() + 1].get(); + + if (next_hn->hn_start > offset + && (hn->hn_line_number == 0 + || (next_hn->hn_line_number - hn->hn_line_number) > 1)) + { + retval.cnr_next = next_hn; + } else if (this->hn_parent != nullptr) { + auto parent_neighbors_opt + = this->hn_parent->child_neighbors(this, offset); + + if (parent_neighbors_opt) { + retval.cnr_next = parent_neighbors_opt->cnr_next; + } + } + } + + return retval; +} + +nonstd::optional +hier_node::line_neighbors(size_t ln) const +{ + if (this->hn_children.empty()) { + return nonstd::nullopt; + } + + hier_node::child_neighbors_result retval; + + for (const auto& child : this->hn_children) { + if (child->hn_line_number > ln) { + retval.cnr_next = child.get(); + break; + } + retval.cnr_previous = child.get(); + } + + return retval; +} + nonstd::optional hier_node::lookup_path(const hier_node* root, const std::vector& path) @@ -81,11 +193,24 @@ hier_node::lookup_path(const hier_node* root, return retval; } +std::vector +metadata::path_for_range(size_t start, size_t stop) +{ + std::vector retval; + + this->m_sections_tree.visit_overlapping( + start, stop, [&retval](const lnav::document::section_interval_t& iv) { + retval.emplace_back(iv.value); + }); + return retval; +} + struct metadata_builder { std::vector mb_intervals; std::vector mb_type_intervals; std::unique_ptr mb_root_node; std::set mb_indents; + text_format_t mb_text_format{text_format_t::TF_UNKNOWN}; metadata to_metadata() && { @@ -94,6 +219,7 @@ struct metadata_builder { std::move(this->mb_root_node), std::move(this->mb_type_intervals), std::move(this->mb_indents), + this->mb_text_format, }; } }; @@ -238,6 +364,16 @@ discover_metadata_int(const attr_line_t& al, metadata_builder& mb) } }); + hier_node::depth_first( + mb.mb_root_node.get(), [&orig_attrs](hier_node* node) { + auto off_opt = get_string_attr( + orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start); + + if (off_opt) { + node->hn_start += off_opt.value()->sa_value.get(); + } + }); + if (!root_node->hn_children.empty() || !root_node->hn_named_children.empty()) { @@ -284,6 +420,7 @@ public: { metadata_builder mb; + mb.mb_text_format = this->sw_text_format; while (true) { auto tokenize_res = this->sw_scanner.tokenize2(this->sw_text_format); @@ -334,17 +471,26 @@ public: this->sw_interval_state.resize(this->sw_depth + 1); this->sw_hier_nodes.push_back( std::make_unique()); + this->sw_container_tokens.push_back(to_closer(dt)); break; case DT_XML_CLOSE_TAG: { auto term = this->flush_values(); if (this->sw_depth > 0) { - if (term) { - this->append_child_node(term); - } - this->sw_interval_state.pop_back(); - this->sw_hier_stage - = std::move(this->sw_hier_nodes.back()); - this->sw_hier_nodes.pop_back(); + auto found = false; + do { + if (this->sw_container_tokens.back() == dt) { + found = true; + } + if (term) { + this->append_child_node(term); + term = nonstd::nullopt; + } + this->sw_interval_state.pop_back(); + this->sw_hier_stage + = std::move(this->sw_hier_nodes.back()); + this->sw_hier_nodes.pop_back(); + this->sw_container_tokens.pop_back(); + } while (!found); } this->append_child_node(el.e_capture); if (this->sw_depth > 0) { @@ -418,6 +564,7 @@ public: this->sw_interval_state.resize(this->sw_depth + 1); this->sw_hier_nodes.push_back( std::make_unique()); + this->sw_container_tokens.push_back(to_closer(dt)); } else { this->sw_values.emplace_back(el); } @@ -426,37 +573,54 @@ public: case DT_RCURLY: case DT_RSQUARE: case DT_RPAREN: - if (this->is_structured_text()) { + if (this->is_structured_text() + && !this->sw_container_tokens.empty() + && std::find(this->sw_container_tokens.begin(), + this->sw_container_tokens.end(), + dt) + != this->sw_container_tokens.end()) + { auto term = this->flush_values(); if (this->sw_depth > 0) { - this->append_child_node(term); - this->sw_depth -= 1; - this->sw_interval_state.pop_back(); - this->sw_hier_stage - = std::move(this->sw_hier_nodes.back()); - this->sw_hier_nodes.pop_back(); - if (this->sw_interval_state.back().is_start) { - data_scanner::capture_t obj_cap = { - static_cast( - this->sw_interval_state.back() - .is_start.value()), - el.e_capture.c_end, - }; - - auto sf = this->sw_scanner.to_string_fragment( - obj_cap); - if (!sf.find('\n')) { - this->sw_hier_stage->hn_named_children - .clear(); - this->sw_hier_stage->hn_children.clear(); - while (!this->sw_intervals.empty() - && this->sw_intervals.back().start - > obj_cap.c_begin) - { - this->sw_intervals.pop_back(); + auto found = false; + do { + if (this->sw_container_tokens.back() == dt) { + found = true; + } + this->append_child_node(term); + term = nonstd::nullopt; + this->sw_depth -= 1; + this->sw_interval_state.pop_back(); + this->sw_hier_stage + = std::move(this->sw_hier_nodes.back()); + this->sw_hier_nodes.pop_back(); + if (this->sw_interval_state.back().is_start) { + data_scanner::capture_t obj_cap = { + static_cast( + this->sw_interval_state.back() + .is_start.value()), + el.e_capture.c_end, + }; + + auto sf + = this->sw_scanner.to_string_fragment( + obj_cap); + if (!sf.find('\n')) { + this->sw_hier_stage->hn_named_children + .clear(); + this->sw_hier_stage->hn_children + .clear(); + while ( + !this->sw_intervals.empty() + && this->sw_intervals.back().start + > obj_cap.c_begin) + { + this->sw_intervals.pop_back(); + } } } - } + this->sw_container_tokens.pop_back(); + } while (!found); } } this->sw_values.emplace_back(el); @@ -650,18 +814,22 @@ private: auto new_key = ivstate.is_name.empty() ? lnav::document::section_key_t{top_node->hn_children.size()} : lnav::document::section_key_t{ivstate.is_name}; - this->sw_intervals.emplace_back(iv_start, iv_stop, new_key); auto* retval = new_node.get(); new_node->hn_parent = top_node; - new_node->hn_start = this->sw_intervals.back().start; + new_node->hn_start = iv_start; new_node->hn_line_number = ivstate.is_line_number; - if (!ivstate.is_name.empty()) { - top_node->hn_named_children.insert({ - ivstate.is_name, - retval, - }); + if (this->sw_depth == 1 + || new_node->hn_line_number != top_node->hn_line_number) + { + this->sw_intervals.emplace_back(iv_start, iv_stop, new_key); + if (!ivstate.is_name.empty()) { + top_node->hn_named_children.insert({ + ivstate.is_name, + retval, + }); + } + top_node->hn_children.emplace_back(std::move(new_node)); } - top_node->hn_children.emplace_back(std::move(new_node)); ivstate.is_start = nonstd::nullopt; ivstate.is_line_number = 0; ivstate.is_name.clear(); @@ -676,6 +844,7 @@ private: bool sw_at_start{true}; std::set sw_indents; std::vector sw_values{}; + std::vector sw_container_tokens; std::vector sw_interval_state; std::vector sw_intervals; std::vector sw_type_intervals; diff --git a/src/document.sections.hh b/src/document.sections.hh index a96c4248..944dbb4f 100644 --- a/src/document.sections.hh +++ b/src/document.sections.hh @@ -69,6 +69,18 @@ struct hier_node { nonstd::optional lookup_child(section_key_t key) const; + nonstd::optional child_index(const hier_node* hn) const; + + struct child_neighbors_result { + nonstd::optional cnr_previous; + nonstd::optional cnr_next; + }; + + nonstd::optional child_neighbors( + const hier_node* hn, file_off_t offset) const; + + nonstd::optional line_neighbors(size_t ln) const; + nonstd::optional find_line_number(const std::string& str) const { auto iter = this->hn_named_children.find(str); @@ -115,6 +127,9 @@ struct metadata { std::unique_ptr m_sections_root; section_types_tree_t m_section_types_tree; std::set m_indents; + text_format_t m_text_format{text_format_t::TF_UNKNOWN}; + + std::vector path_for_range(size_t start, size_t stop); std::vector possibility_provider( const std::vector& path); diff --git a/src/field_overlay_source.cc b/src/field_overlay_source.cc index a209c71c..43717867 100644 --- a/src/field_overlay_source.cc +++ b/src/field_overlay_source.cc @@ -476,26 +476,33 @@ field_overlay_source::build_meta_line(const listview_curses& lv, { auto line_meta_opt = this->fos_lss.find_bookmark_metadata(row); - auto file_and_line = this->fos_lss.find_line_with_file(row); - if (file_and_line && !file_and_line->second->is_continued()) { - auto applicable_anno = lnav::log::annotate::applicable(row); - if (!applicable_anno.empty() - && (!line_meta_opt - || line_meta_opt.value()->bm_annotations.la_pairs.empty())) - { - auto anno_msg - = attr_line_t(" ") - .append(":memo:"_emoji) - .append(" Annotations available, ") - .append(lv.get_selection() == row - ? "use " - : "focus on this line and use ") - .append(":annotate"_quoted_code) - .append(" to apply them") - .append(lv.get_selection() == row ? " to this line" : "") - .with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT)); - - dst.emplace_back(anno_msg); + if (!this->fos_contexts.empty() + && this->fos_contexts.top().c_show_applicable_annotations) + { + auto file_and_line = this->fos_lss.find_line_with_file(row); + + if (file_and_line && !file_and_line->second->is_continued()) { + auto applicable_anno = lnav::log::annotate::applicable(row); + if (!applicable_anno.empty() + && (!line_meta_opt + || line_meta_opt.value()->bm_annotations.la_pairs.empty())) + { + auto anno_msg + = attr_line_t(" ") + .append(":memo:"_emoji) + .append(" Annotations available, ") + .append(lv.get_selection() == row + ? "use " + : "focus on this line and use ") + .append(":annotate"_quoted_code) + .append(" to apply them") + .append(lv.get_selection() == row ? " to this line" + : "") + .with_attr_for_all( + VC_ROLE.value(role_t::VCR_COMMENT)); + + dst.emplace_back(anno_msg); + } } } diff --git a/src/field_overlay_source.hh b/src/field_overlay_source.hh index 8a560af9..5458ad5c 100644 --- a/src/field_overlay_source.hh +++ b/src/field_overlay_source.hh @@ -78,15 +78,20 @@ public: } struct context { - context(std::string prefix, bool show, bool show_discovered) + context(std::string prefix, + bool show, + bool show_discovered, + bool show_applicable_annotations) : c_prefix(std::move(prefix)), c_show(show), - c_show_discovered(show_discovered) + c_show_discovered(show_discovered), + c_show_applicable_annotations(show_applicable_annotations) { } std::string c_prefix; bool c_show{false}; bool c_show_discovered{true}; + bool c_show_applicable_annotations{true}; }; std::stack fos_contexts; diff --git a/src/formats/vmw_py_log.json b/src/formats/vmw_py_log.json index 59454e89..d4cfe5d1 100644 --- a/src/formats/vmw_py_log.json +++ b/src/formats/vmw_py_log.json @@ -39,6 +39,9 @@ }, { "line": "2022-06-01T13:23:25.310 [2376]DEBUG:com.vmware.vherd.base.detwist:method = com.vmware.appliance.version1.system.version.get, args = ()" + }, + { + "line": "2023-07-19T02:47:11 AM UTC [1670]DEBUG:firewall-reload:Processing system service 'sshd' firewall rules." } ] } diff --git a/src/internals/cmd-ref.rst b/src/internals/cmd-ref.rst index e9a87e69..ffe9186a 100644 --- a/src/internals/cmd-ref.rst +++ b/src/internals/cmd-ref.rst @@ -665,7 +665,7 @@ :goto #screenshots **See Also** - :ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto` + :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto` ---- @@ -873,7 +873,7 @@ Move to the next position in the location history **See Also** - :ref:`goto`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto` + :ref:`goto`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto` ---- @@ -896,7 +896,20 @@ :next-mark error **See Also** - :ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_mark`, :ref:`relative_goto` + :ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto` + +---- + + +.. _next_section: + +:next-section +^^^^^^^^^^^^^ + + Move to the next section in the document + + **See Also** + :ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto` ---- @@ -1003,7 +1016,7 @@ Move to the previous position in the location history **See Also** - :ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`prev_mark`, :ref:`relative_goto` + :ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_mark`, :ref:`prev_section`, :ref:`relative_goto` ---- @@ -1026,7 +1039,20 @@ :prev-mark error **See Also** - :ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_mark`, :ref:`prev_location`, :ref:`relative_goto` + :ref:`goto`, :ref:`hide_unmarked_lines`, :ref:`mark`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_section`, :ref:`relative_goto` + +---- + + +.. _prev_section: + +:prev-section +^^^^^^^^^^^^^ + + Move to the previous section in the document + + **See Also** + :ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto` ---- @@ -1143,7 +1169,7 @@ :relative-goto -10% **See Also** - :ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark` + :ref:`goto`, :ref:`next_location`, :ref:`next_mark`, :ref:`next_section`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`prev_section` ---- diff --git a/src/keymaps/default-keymap.json b/src/keymaps/default-keymap.json index 2fac4b1d..709ccc64 100644 --- a/src/keymaps/default-keymap.json +++ b/src/keymaps/default-keymap.json @@ -11,8 +11,8 @@ "keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view", "keymap_def_zoom": "Press ${ansi_bold}z${ansi_norm}/${ansi_bold}Z${ansi_norm} to zoom in/out", "keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages", - "keymap_def_prev_location": "Press ${ansi_bold}{${ansi_norm} to move to the previous location in history", - "keymap_def_next_location": "Press ${ansi_bold}}${ansi_norm} to move to the next location in history", + "keymap_def_prev_section": "Press ${ansi_bold}{${ansi_norm} to move to the previous section in history", + "keymap_def_next_section": "Press ${ansi_bold}}${ansi_norm} to move to the next section in history", "keymap_def_next_mark": "Press ${ansi_bold}c${ansi_norm} to copy marked lines to the clipboard; press ${ansi_bold}C${ansi_norm} to clear marked lines", "keymap_def_time_offset": "Press ${ansi_bold}s${ansi_norm}/${ansi_bold}S${ansi_norm} to move forward/backward through slow downs" }, @@ -153,11 +153,11 @@ "command": ":prev-mark" }, "x7d": { - "command": ":next-location", + "command": ":next-section", "alt-msg": "${keymap_def_prev_location}" }, "x7b": { - "command": ":prev-location", + "command": ":prev-section", "alt-msg": "${keymap_def_next_location}" }, "x3f": { diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 2ae06250..2932a8ae 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -719,6 +719,9 @@ listview_curses::shift_selection(shift_amount_t sa) break; } if (this->is_selectable()) { + if (this->lv_selection == -1_vl) { + this->lv_selection = this->lv_top; + } auto new_selection = this->lv_selection + vis_line_t(offset); if (new_selection < 0_vl) { diff --git a/src/lnav.cc b/src/lnav.cc index 1eaf7dc8..9ef2d708 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -2826,7 +2826,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' .set_word_wrap(false); auto log_fos = new field_overlay_source(lnav_data.ld_log_source, lnav_data.ld_text_source); - log_fos->fos_contexts.emplace("", false, true); + log_fos->fos_contexts.emplace("", false, true, true); lnav_data.ld_views[LNV_LOG] .set_sub_source(&lnav_data.ld_log_source) #if 0 @@ -3357,6 +3357,9 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' textview_curses *log_tc, *text_tc, *tc; bool output_view = true; + log_fos->fos_contexts.top().c_show_applicable_annotations + = false; + view_colors::init(true); rescan_files(true); wait_for_pipers(); diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 8e5c7556..8bb8f5db 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -1144,6 +1144,62 @@ com_goto_location(exec_context& ec, return Ok(retval); } +static Result +com_next_section(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + } else if (!ec.ec_dry_run) { + auto* tc = *lnav_data.ld_view_stack.top(); + auto* ta = dynamic_cast(tc->get_sub_source()); + + if (ta == nullptr) { + return ec.make_error("view does not support sections"); + } + + auto adj_opt = ta->adjacent_anchor(tc->get_selection(), + text_anchors::direction::next); + if (!adj_opt) { + return ec.make_error("no next section found"); + } + + tc->set_selection(adj_opt.value()); + } + + return Ok(retval); +} + +static Result +com_prev_section(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + } else if (!ec.ec_dry_run) { + auto* tc = *lnav_data.ld_view_stack.top(); + auto* ta = dynamic_cast(tc->get_sub_source()); + + if (ta == nullptr) { + return ec.make_error("view does not support sections"); + } + + auto adj_opt = ta->adjacent_anchor(tc->get_selection(), + text_anchors::direction::prev); + if (!adj_opt) { + return ec.make_error("no previous section found"); + } + + tc->set_selection(adj_opt.value()); + } + + return Ok(retval); +} + static bool csv_needs_quoting(const std::string& str) { @@ -1738,11 +1794,8 @@ com_save_to(exec_context& ec, size_t count = 0; if (fos != nullptr) { - fos->fos_contexts.push(field_overlay_source::context{ - "", - false, - false, - }); + fos->fos_contexts.push( + field_overlay_source::context{"", false, false, false}); } auto y = 0_vl; @@ -5820,6 +5873,24 @@ readline_context::command_t STD_COMMANDS[] = { help_text(":prev-location") .with_summary("Move to the previous position in the location history") .with_tags({"navigation"})}, + + { + "next-section", + com_next_section, + + help_text(":next-section") + .with_summary("Move to the next section in the document") + .with_tags({"navigation"}), + }, + { + "prev-section", + com_prev_section, + + help_text(":prev-section") + .with_summary("Move to the previous section in the document") + .with_tags({"navigation"}), + }, + {"help", com_help, diff --git a/src/log_format_impls.cc b/src/log_format_impls.cc index f17529f1..be3d5036 100644 --- a/src/log_format_impls.cc +++ b/src/log_format_impls.cc @@ -402,6 +402,7 @@ public: bro_log_format() { + this->lf_structured = true; this->lf_is_self_describing = true; this->lf_time_ordered = false; @@ -703,7 +704,6 @@ public: if (!this->blf_format_name.empty() && !this->blf_separator.empty() && !this->blf_field_defs.empty()) { - dst.clear(); return this->scan_int(dst, li, sbr, sbc); } @@ -1059,6 +1059,7 @@ public: { this->lf_is_self_describing = true; this->lf_time_ordered = false; + this->lf_structured = true; } const intern_string_t get_name() const override diff --git a/src/logfile.cc b/src/logfile.cc index 4521ed3f..e6aade6c 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -310,6 +310,7 @@ logfile::process_prefix(shared_buffer_ref& sbr, this->lf_index.size(), li.li_file_range.fr_offset, li.li_file_range.fr_size); + auto starting_index_size = this->lf_index.size(); size_t prev_index_size = this->lf_index.size(); for (const auto& curr : root_formats) { if (this->lf_index.size() @@ -437,13 +438,13 @@ logfile::process_prefix(shared_buffer_ref& sbr, */ const auto& last_line = this->lf_index.back(); - for (size_t lpc = 0; lpc < this->lf_index.size() - 1; lpc++) { + require_lt(starting_index_size, this->lf_index.size()); + for (size_t lpc = 0; lpc < starting_index_size; lpc++) { if (this->lf_format->lf_multiline) { + this->lf_index[lpc].set_time(last_line.get_time()); + this->lf_index[lpc].set_millis(last_line.get_millis()); if (this->lf_format->lf_structured) { this->lf_index[lpc].set_ignore(true); - } else { - this->lf_index[lpc].set_time(last_line.get_time()); - this->lf_index[lpc].set_millis(last_line.get_millis()); } } else { this->lf_index[lpc].set_time(last_line.get_time()); diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index 7b5f3cdc..c34204c5 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -311,6 +311,11 @@ logfile_sub_source::text_value_for_line(textview_curses& tc, value_out = this->lss_token_value; } + { + auto lr = line_range{0, (int) this->lss_token_value.length()}; + this->lss_token_attrs.emplace_back(lr, SA_ORIGINAL_LINE.value()); + } + if (!this->lss_token_line->is_continued() && (this->lss_token_file->is_time_adjusted() || ((format->lf_timestamp_flags & ETF_ZONE_SET @@ -425,6 +430,7 @@ logfile_sub_source::text_value_for_line(textview_curses& tc, auto relstr = this->get_time_offset_for_line(tc, row_vl); value_out = fmt::format(FMT_STRING("{: >12}|{}"), relstr, value_out); } + this->lss_in_value_for_line = false; } @@ -459,8 +465,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, const auto& line_values = this->lss_token_values; lr.lr_start = 0; - lr.lr_end = this->lss_token_value.length(); - value_out.emplace_back(lr, SA_ORIGINAL_LINE.value()); + lr.lr_end = -1; value_out.emplace_back( lr, SA_LEVEL.value(this->lss_token_line->get_msg_level())); diff --git a/src/plain_text_source.cc b/src/plain_text_source.cc index 94d856b2..22fd834e 100644 --- a/src/plain_text_source.cc +++ b/src/plain_text_source.cc @@ -31,6 +31,7 @@ #include "base/itertools.hh" #include "config.h" +#include "scn/scn.h" static std::vector to_text_line(const std::vector& lines) @@ -79,7 +80,30 @@ plain_text_source::replace_with(const attr_line_t& text_lines) { this->tds_lines.clear(); this->tds_doc_sections = lnav::document::discover_metadata(text_lines); + file_off_t off = 0; + auto lines = text_lines.split_lines(); + while (!lines.empty() && lines.back().empty()) { + lines.pop_back(); + } + for (auto& line : lines) { + auto line_len = line.length() + 1; + this->tds_lines.emplace_back(off, std::move(line)); + off += line_len; + } + this->tds_longest_line = this->compute_longest_line(); + if (this->tss_view != nullptr) { + this->tss_view->set_needs_update(); + } + return *this; +} +plain_text_source& +plain_text_source::replace_with_mutable(attr_line_t& text_lines, + text_format_t tf) +{ + this->tds_lines.clear(); + this->tds_doc_sections + = lnav::document::discover_structure(text_lines, line_range{0, -1}, tf); file_off_t off = 0; auto lines = text_lines.split_lines(); while (!lines.empty() && lines.back().empty()) { @@ -368,16 +392,47 @@ plain_text_source::row_for_anchor(const std::string& id) return retval; } + const auto& meta = this->tds_doc_sections; + + auto is_ptr = startswith(id, "#/"); + if (is_ptr) { + auto hier_sf = string_fragment::from_str(id).consume_n(2).value(); + std::vector path; + + while (!hier_sf.empty()) { + auto comp_pair = hier_sf.split_when(string_fragment::tag1{'/'}); + auto scan_res + = scn::scan_value(comp_pair.first.to_string_view()); + if (scan_res && scan_res.empty()) { + path.emplace_back(scan_res.value()); + } else { + path.emplace_back(json_ptr::decode(comp_pair.first)); + } + hier_sf = comp_pair.second; + } + + auto lookup_res = lnav::document::hier_node::lookup_path( + meta.m_sections_root.get(), path); + if (lookup_res) { + retval = this->line_for_offset(lookup_res.value()->hn_start); + } + + return retval; + } + lnav::document::hier_node::depth_first( - this->tds_doc_sections.m_sections_root.get(), + meta.m_sections_root.get(), [this, &id, &retval](const lnav::document::hier_node* node) { for (const auto& child_pair : node->hn_named_children) { - auto child_anchor + const auto& child_anchor = text_anchors::to_anchor_string(child_pair.first); - if (child_anchor == id) { - retval = this->line_for_offset(child_pair.second->hn_start); + if (child_anchor != id) { + continue; } + + retval = this->line_for_offset(child_pair.second->hn_start); + break; } }); @@ -413,16 +468,127 @@ plain_text_source::anchor_for_row(vis_line_t vl) } const auto& tl = this->tds_lines[vl]; + auto& md = this->tds_doc_sections; + auto path_for_line = md.path_for_range( + tl.tl_offset, tl.tl_offset + tl.tl_value.al_string.length()); - this->tds_doc_sections.m_sections_tree.visit_overlapping( - tl.tl_offset, [&retval](const lnav::document::section_interval_t& iv) { - retval = iv.value.match( - [](const std::string& str) { - return nonstd::make_optional( - text_anchors::to_anchor_string(str)); - }, - [](size_t) { return nonstd::nullopt; }); - }); + if (path_for_line.empty()) { + return nonstd::nullopt; + } - return retval; + if ((path_for_line.size() == 1 + || this->tds_text_format == text_format_t::TF_MARKDOWN) + && path_for_line.back().is()) + { + return text_anchors::to_anchor_string( + path_for_line.back().get()); + } + + auto comps = path_for_line | lnav::itertools::map([](const auto& elem) { + return elem.match( + [](const std::string& str) { + return json_ptr::encode_str(str); + }, + [](size_t index) { return fmt::to_string(index); }); + }); + + return fmt::format(FMT_STRING("#/{}"), + fmt::join(comps.begin(), comps.end(), "/")); +} + +nonstd::optional +plain_text_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir) +{ + if (vl > this->tds_lines.size() + || this->tds_doc_sections.m_sections_root == nullptr) + { + return nonstd::nullopt; + } + + const auto& tl = this->tds_lines[vl]; + auto path_for_line = this->tds_doc_sections.path_for_range( + tl.tl_offset, tl.tl_offset + tl.tl_value.al_string.length()); + + auto& md = this->tds_doc_sections; + if (path_for_line.empty()) { + auto neighbors_res = md.m_sections_root->line_neighbors(vl); + if (!neighbors_res) { + return nonstd::nullopt; + } + + switch (dir) { + case text_anchors::direction::prev: { + if (neighbors_res->cnr_previous) { + return this->line_for_offset( + neighbors_res->cnr_previous.value()->hn_start); + } + break; + } + case text_anchors::direction::next: { + if (neighbors_res->cnr_next) { + return this->line_for_offset( + neighbors_res->cnr_next.value()->hn_start); + } + break; + } + } + return nonstd::nullopt; + } + + auto last_key = path_for_line.back(); + path_for_line.pop_back(); + + auto parent_opt = lnav::document::hier_node::lookup_path( + md.m_sections_root.get(), path_for_line); + if (!parent_opt) { + return nonstd::nullopt; + } + auto parent = parent_opt.value(); + + auto child_hn = parent->lookup_child(last_key); + if (!child_hn) { + // XXX "should not happen" + return nonstd::nullopt; + } + + auto neighbors_res = parent->child_neighbors( + child_hn.value(), tl.tl_offset + tl.tl_value.al_string.length() + 1); + if (!neighbors_res) { + return nonstd::nullopt; + } + + if (neighbors_res->cnr_previous && last_key.is()) { + auto neighbor_sub + = neighbors_res->cnr_previous.value()->lookup_child(last_key); + if (neighbor_sub) { + neighbors_res->cnr_previous = neighbor_sub; + } + } + + if (neighbors_res->cnr_next && last_key.is()) { + auto neighbor_sub + = neighbors_res->cnr_next.value()->lookup_child(last_key); + if (neighbor_sub) { + neighbors_res->cnr_next = neighbor_sub; + } + } + + switch (dir) { + case text_anchors::direction::prev: { + if (neighbors_res->cnr_previous) { + return this->line_for_offset( + neighbors_res->cnr_previous.value()->hn_start); + } + break; + } + case text_anchors::direction::next: { + if (neighbors_res->cnr_next) { + return this->line_for_offset( + neighbors_res->cnr_next.value()->hn_start); + } + break; + } + } + + return nonstd::nullopt; } diff --git a/src/plain_text_source.hh b/src/plain_text_source.hh index 8794dc17..5f395252 100644 --- a/src/plain_text_source.hh +++ b/src/plain_text_source.hh @@ -73,6 +73,9 @@ public: return *this; } + plain_text_source& replace_with_mutable(attr_line_t& text_lines, + text_format_t tf); + plain_text_source& replace_with(const attr_line_t& text_lines); plain_text_source& replace_with(const std::vector& text_lines); @@ -123,6 +126,8 @@ public: nonstd::optional row_for_anchor(const std::string& id) override; nonstd::optional anchor_for_row(vis_line_t vl) override; std::unordered_set get_anchors() override; + nonstd::optional adjacent_anchor(vis_line_t vl, + direction dir) override; protected: size_t compute_longest_line(); diff --git a/src/pretty_printer.cc b/src/pretty_printer.cc index 6b04af16..0e9ec0e6 100644 --- a/src/pretty_printer.cc +++ b/src/pretty_printer.cc @@ -77,14 +77,14 @@ pretty_printer::append_to(attr_line_t& al) = this->pp_stream.tellp(); this->pp_interval_state.back().is_name = tok_res->to_string(); - this->descend(); + this->descend(DT_XML_CLOSE_TAG); } else { this->pp_values.emplace_back(el); } continue; case DT_XML_CLOSE_TAG: this->flush_values(); - this->ascend(); + this->ascend(el.e_token); this->append_child_node(); this->write_element(el); this->start_new_line(); @@ -94,7 +94,7 @@ pretty_printer::append_to(attr_line_t& al) case DT_LPAREN: this->flush_values(true); this->pp_values.emplace_back(el); - this->descend(); + this->descend(to_closer(el.e_token)); this->pp_interval_state.back().is_start = this->pp_stream.tellp(); continue; @@ -105,7 +105,7 @@ pretty_printer::append_to(attr_line_t& al) if (this->pp_body_lines.top()) { this->start_new_line(); } - this->ascend(); + this->ascend(el.e_token); this->write_element(el); continue; case DT_COMMA: @@ -135,7 +135,7 @@ pretty_printer::append_to(attr_line_t& al) this->pp_values.emplace_back(el); } while (this->pp_depth > 0) { - this->ascend(); + this->ascend(this->pp_container_tokens.back()); } this->flush_values(); @@ -341,30 +341,47 @@ pretty_printer::start_new_line() } void -pretty_printer::ascend() +pretty_printer::ascend(data_token_t dt) { if (this->pp_depth > 0) { - int lines = this->pp_body_lines.top(); - this->pp_depth -= 1; - this->pp_body_lines.pop(); - this->pp_body_lines.top() += lines; - - if (!this->pp_is_xml) { - this->append_child_node(); + if (this->pp_container_tokens.back() != dt + && std::find(this->pp_container_tokens.begin(), + this->pp_container_tokens.end(), + dt) + == this->pp_container_tokens.end()) + { + return; } - this->pp_interval_state.pop_back(); - this->pp_hier_stage = std::move(this->pp_hier_nodes.back()); - this->pp_hier_nodes.pop_back(); + + auto found = false; + do { + if (this->pp_container_tokens.back() == dt) { + found = true; + } + int lines = this->pp_body_lines.top(); + this->pp_depth -= 1; + this->pp_body_lines.pop(); + this->pp_body_lines.top() += lines; + + if (!this->pp_is_xml) { + this->append_child_node(); + } + this->pp_interval_state.pop_back(); + this->pp_hier_stage = std::move(this->pp_hier_nodes.back()); + this->pp_hier_nodes.pop_back(); + this->pp_container_tokens.pop_back(); + } while (!found); } else { this->pp_body_lines.top() = 0; } } void -pretty_printer::descend() +pretty_printer::descend(data_token_t dt) { this->pp_depth += 1; this->pp_body_lines.push(0); + this->pp_container_tokens.push_back(dt); this->pp_interval_state.resize(this->pp_depth + 1); this->pp_hier_nodes.push_back( std::make_unique()); diff --git a/src/pretty_printer.hh b/src/pretty_printer.hh index 92c1022c..495eb4eb 100644 --- a/src/pretty_printer.hh +++ b/src/pretty_printer.hh @@ -97,9 +97,9 @@ public: std::set take_indents() { return std::move(this->pp_indents); } private: - void descend(); + void descend(data_token_t dt); - void ascend(); + void ascend(data_token_t dt); void start_new_line(); @@ -120,6 +120,7 @@ private: int pp_depth{0}; int pp_line_length{0}; int pp_soft_indent{0}; + std::vector pp_container_tokens{}; std::stack pp_body_lines{}; data_scanner* pp_scanner; string_attrs_t pp_attrs; diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index ff902da3..0bb2b1e0 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -904,7 +904,7 @@ rl_focus(readline_curses* rc) auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG] .get_overlay_source(); - fos->fos_contexts.emplace("", false, true); + fos->fos_contexts.emplace("", false, true, true); get_textview_for_mode(lnav_data.ld_mode)->save_current_search(); } diff --git a/src/textfile_sub_source.cc b/src/textfile_sub_source.cc index 94332b16..aec34063 100644 --- a/src/textfile_sub_source.cc +++ b/src/textfile_sub_source.cc @@ -560,26 +560,26 @@ textfile_sub_source::text_crumbs_for_line( rend_iter->second.rf_text_source->text_crumbs_for_line(line, crumbs); } + if (lf->has_line_metadata()) { + auto* lfo + = dynamic_cast(lf->get_logline_observer()); + if (line < 0 || line >= lfo->lfo_filter_state.tfs_index.size()) { + return; + } + auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[line]; + char ts[64]; + + sql_strftime(ts, sizeof(ts), ll_iter->get_timeval(), 'T'); + + crumbs.emplace_back( + std::string(ts), + []() -> std::vector { return {}; }, + [](const auto& key) {}); + } auto meta_iter = this->tss_doc_metadata.find(lf->get_filename()); if (meta_iter == this->tss_doc_metadata.end() || meta_iter->second.ms_metadata.m_sections_tree.empty()) { - if (lf->has_line_metadata()) { - auto* lfo = dynamic_cast( - lf->get_logline_observer()); - if (line < 0 || line >= lfo->lfo_filter_state.tfs_index.size()) { - return; - } - auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[line]; - char ts[64]; - - sql_strftime(ts, sizeof(ts), ll_iter->get_timeval(), 'T'); - - crumbs.emplace_back( - std::string(ts), - []() -> std::vector { return {}; }, - [](const auto& key) {}); - } } else { auto* lfo = dynamic_cast(lf->get_logline_observer()); @@ -849,6 +849,7 @@ textfile_sub_source::rescan_files( rf.rf_mtime = st.st_mtime; rf.rf_file_size = st.st_size; rf.rf_text_source = std::make_unique(); + rf.rf_text_source->set_text_format(lf->get_text_format()); rf.rf_text_source->register_view(this->tss_view); if (parse_res.isOk()) { auto& lf_meta = lf->get_embedded_metadata(); @@ -931,7 +932,7 @@ textfile_sub_source::rescan_files( this->tss_doc_metadata[lf->get_filename()] = metadata_state{ st.st_mtime, - static_cast(content.length()), + static_cast(lf->get_index_size()), lnav::document::discover_structure( content, line_range{0, -1}, @@ -945,7 +946,7 @@ textfile_sub_source::rescan_files( this->tss_doc_metadata[lf->get_filename()] = metadata_state{ st.st_mtime, - static_cast(st.st_size), + static_cast(lf->get_index_size()), {}, }; } @@ -1159,6 +1160,142 @@ textfile_sub_source::get_anchors() return retval; } +static nonstd::optional +to_vis_line(const std::shared_ptr& lf, file_off_t off) +{ + auto ll_opt = lf->line_for_offset(off); + if (ll_opt != lf->end()) { + return vis_line_t(std::distance(lf->cbegin(), ll_opt.value())); + } + + return nonstd::nullopt; +} + +nonstd::optional +textfile_sub_source::adjacent_anchor(vis_line_t vl, text_anchors::direction dir) +{ + auto lf = this->current_file(); + if (!lf) { + return nonstd::nullopt; + } + + log_debug("adjacent_anchor: %s:L%d:%s", + lf->get_filename().c_str(), + vl, + dir == text_anchors::direction::prev ? "prev" : "next"); + auto rend_iter = this->tss_rendered_files.find(lf->get_filename()); + if (rend_iter != this->tss_rendered_files.end()) { + return rend_iter->second.rf_text_source->adjacent_anchor(vl, dir); + } + + auto iter = this->tss_doc_metadata.find(lf->get_filename()); + if (iter == this->tss_doc_metadata.end()) { + log_debug(" no metadata available"); + return nonstd::nullopt; + } + + auto& md = iter->second.ms_metadata; + auto* lfo = dynamic_cast(lf->get_logline_observer()); + if (vl >= lfo->lfo_filter_state.tfs_index.size() + || md.m_sections_root == nullptr) + { + return nonstd::nullopt; + } + auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[vl]; + auto line_offsets = lf->get_file_range(ll_iter, false); + log_debug( + " range %d:%d", line_offsets.fr_offset, line_offsets.next_offset()); + auto path_for_line + = md.path_for_range(line_offsets.fr_offset, line_offsets.next_offset()); + + if (path_for_line.empty()) { + log_debug(" no path found"); + auto neighbors_res = md.m_sections_root->line_neighbors(vl); + if (!neighbors_res) { + return nonstd::nullopt; + } + + switch (dir) { + case text_anchors::direction::prev: { + if (neighbors_res->cnr_previous) { + return to_vis_line( + lf, neighbors_res->cnr_previous.value()->hn_start); + } + break; + } + case text_anchors::direction::next: { + if (neighbors_res->cnr_next) { + return to_vis_line( + lf, neighbors_res->cnr_next.value()->hn_start); + } + break; + } + } + return nonstd::nullopt; + } + + auto last_key = path_for_line.back(); + path_for_line.pop_back(); + + auto parent_opt = lnav::document::hier_node::lookup_path( + md.m_sections_root.get(), path_for_line); + if (!parent_opt) { + return nonstd::nullopt; + } + auto parent = parent_opt.value(); + + auto child_hn = parent->lookup_child(last_key); + if (!child_hn) { + // XXX "should not happen" + return nonstd::nullopt; + } + + auto neighbors_res = parent->child_neighbors( + child_hn.value(), line_offsets.next_offset() + 1); + if (!neighbors_res) { + log_debug(" no neighbors found"); + return nonstd::nullopt; + } + + log_debug(" neighbors p:%d n:%d", + neighbors_res->cnr_previous.has_value(), + neighbors_res->cnr_next.has_value()); + if (neighbors_res->cnr_previous && last_key.is()) { + auto neighbor_sub + = neighbors_res->cnr_previous.value()->lookup_child(last_key); + if (neighbor_sub) { + neighbors_res->cnr_previous = neighbor_sub; + } + } + + if (neighbors_res->cnr_next && last_key.is()) { + auto neighbor_sub + = neighbors_res->cnr_next.value()->lookup_child(last_key); + if (neighbor_sub) { + neighbors_res->cnr_next = neighbor_sub; + } + } + + switch (dir) { + case text_anchors::direction::prev: { + if (neighbors_res->cnr_previous) { + return to_vis_line( + lf, neighbors_res->cnr_previous.value()->hn_start); + } + break; + } + case text_anchors::direction::next: { + if (neighbors_res->cnr_next) { + return to_vis_line(lf, + neighbors_res->cnr_next.value()->hn_start); + } + break; + } + } + + return nonstd::nullopt; +} + nonstd::optional textfile_sub_source::anchor_for_row(vis_line_t vl) { @@ -1181,36 +1318,34 @@ textfile_sub_source::anchor_for_row(vis_line_t vl) if (vl >= lfo->lfo_filter_state.tfs_index.size()) { return nonstd::nullopt; } + auto& md = iter->second.ms_metadata; auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[vl]; - auto ll_next_iter = ll_iter + 1; - auto end_offset = (ll_next_iter == lf->end()) - ? lf->get_index_size() - 1 - : ll_next_iter->get_offset() - 1; - - std::vector collector; - iter->second.ms_metadata.m_sections_tree.visit_overlapping( - ll_iter->get_offset(), - end_offset, - [&collector](const lnav::document::section_interval_t& iv) { - collector.emplace_back(iv.value.match( - [](const std::string& str) { return str; }, - [](size_t index) { return fmt::to_string(index); })); - }); + auto line_offsets = lf->get_file_range(ll_iter, false); + auto path_for_line + = md.path_for_range(line_offsets.fr_offset, line_offsets.next_offset()); - if (collector.empty()) { + if (path_for_line.empty()) { return nonstd::nullopt; } - if (collector.size() == 1) { - return text_anchors::to_anchor_string(collector.front()); + if ((path_for_line.size() == 1 + || md.m_text_format == text_format_t::TF_MARKDOWN) + && path_for_line.back().is()) + { + return text_anchors::to_anchor_string( + path_for_line.back().get()); } - for (auto& elem : collector) { - elem = json_ptr::encode_str(elem); - } + auto comps = path_for_line | lnav::itertools::map([](const auto& elem) { + return elem.match( + [](const std::string& str) { + return json_ptr::encode_str(str); + }, + [](size_t index) { return fmt::to_string(index); }); + }); return fmt::format(FMT_STRING("#/{}"), - fmt::join(collector.begin(), collector.end(), "/")); + fmt::join(comps.begin(), comps.end(), "/")); } bool diff --git a/src/textfile_sub_source.hh b/src/textfile_sub_source.hh index 844d25a6..feb12b84 100644 --- a/src/textfile_sub_source.hh +++ b/src/textfile_sub_source.hh @@ -147,6 +147,9 @@ public: nonstd::optional anchor_for_row(vis_line_t vl) override; + nonstd::optional adjacent_anchor(vis_line_t vl, + direction dir) override; + std::unordered_set get_anchors() override; void quiesce() override; diff --git a/src/textview_curses.hh b/src/textview_curses.hh index cd717092..30d2bda1 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -314,6 +314,17 @@ public: virtual nonstd::optional row_for_anchor(const std::string& id) = 0; + enum class direction { + prev, + next, + }; + + virtual nonstd::optional adjacent_anchor(vis_line_t vl, + direction dir) + { + return nonstd::nullopt; + } + virtual nonstd::optional anchor_for_row(vis_line_t vl) = 0; virtual std::unordered_set get_anchors() = 0; diff --git a/src/time-extension-functions.cc b/src/time-extension-functions.cc index fddadb09..1ca688b3 100644 --- a/src/time-extension-functions.cc +++ b/src/time-extension-functions.cc @@ -234,7 +234,6 @@ sql_timezone(std::string tz_str, string_fragment ts_str) } alb.append(" unrecognized input"); } - log_debug("wtf %s", ts_attr.get_string().c_str()); um.with_note(ts_attr); throw um; } diff --git a/src/time_formats.am b/src/time_formats.am index ad265748..4565a372 100644 --- a/src/time_formats.am +++ b/src/time_formats.am @@ -17,6 +17,7 @@ TIME_FORMATS = \ "%Y-%m-%d %H:%M:%S:%L" \ "%Y-%m-%d %H:%M:%S" \ "%Y-%m-%d %H:%M" \ + "%Y-%m-%dT%H:%M:%S %p %Z" \ "%Y-%m-%dT%H:%M:%S.%N%z" \ "%y-%m-%dT%H:%M:%S.%N%z" \ "%Y-%m-%dT%H:%M:%S.%f%z" \ diff --git a/src/url_handler.cc b/src/url_handler.cc index a44dc9dc..a9092536 100644 --- a/src/url_handler.cc +++ b/src/url_handler.cc @@ -86,7 +86,7 @@ looper::handler_looper::loop_body() if (exec_res.isErr()) { auto um = exec_res.unwrapErr(); log_error( - "wtf %s", + "%s", um.to_attr_line().get_string().c_str()); } }); diff --git a/src/view_curses.cc b/src/view_curses.cc index f2886c69..9922b221 100644 --- a/src/view_curses.cc +++ b/src/view_curses.cc @@ -192,6 +192,12 @@ view_curses::mvwattrline(WINDOW* window, char_index += 1; break; + case '\b': + expanded_line.append("\u232b"); + utf_adjustments.emplace_back(lpc, -1); + char_index += 1; + break; + case '\r': case '\n': expanded_line.push_back(' '); diff --git a/src/view_helpers.cc b/src/view_helpers.cc index dfd9a9ee..b99e4c19 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -144,6 +144,11 @@ public: this->tds_doc_sections.m_indents = std::move(indents); } + void set_sections_root(std::unique_ptr&& hn) + { + this->tds_doc_sections.m_sections_root = std::move(hn); + } + void text_crumbs_for_line(int line, std::vector& crumbs) override { @@ -299,8 +304,8 @@ public: = interval_tree::Interval; std::shared_ptr pss_interval_tree; - std::vector> pss_hier_nods; std::shared_ptr pss_hier_tree; + std::unique_ptr pss_root_node; }; static void @@ -324,7 +329,7 @@ open_pretty_view() return; } - std::vector full_text; + attr_line_t full_text; delete pretty_tc->get_sub_source(); pretty_tc->set_sub_source(nullptr); @@ -361,6 +366,11 @@ open_pretty_view() al.get_string(), text_sub_source::RF_FULL | text_sub_source::RF_REWRITE); lss.text_attrs_for_line(*log_tc, vl, al.get_attrs()); + { + const auto orig_lr + = find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE); + require(orig_lr.is_valid()); + } scrub_ansi_string(al.get_string(), &al.get_attrs()); if (log_tc->get_hide_fields()) { al.apply_hide(); @@ -368,6 +378,7 @@ open_pretty_view() const auto orig_lr = find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE); + require(orig_lr.is_valid()); const auto body_lr = find_string_attr_range(al.get_attrs(), &SA_BODY); auto orig_al = al.subline(orig_lr.lr_start, orig_lr.length()); @@ -419,7 +430,8 @@ open_pretty_view() } }); line_off += pretty_line.get_string().length(); - full_text.emplace_back(pretty_line); + full_text.append(pretty_line); + full_text.append("\n"); } first_line = false; @@ -467,29 +479,28 @@ open_pretty_view() data_scanner ds(orig_al.get_string()); pretty_printer pp(&ds, orig_al.get_attrs()); - attr_line_t pretty_al; - pp.append_to(pretty_al); - pretty_al.rtrim(); + pp.append_to(full_text); all_intervals = pp.take_intervals(); hier_nodes.emplace_back(pp.take_hier_root()); hier_tree_vec.emplace_back( - 0, pretty_al.length(), hier_nodes.back().get()); + 0, full_text.length(), hier_nodes.back().get()); pretty_indents = pp.take_indents(); - - pretty_al.split_lines(full_text); } } auto* pts = new pretty_sub_source(); pts->pss_interval_tree = std::make_shared( std::move(all_intervals)); - pts->pss_hier_nods = std::move(hier_nodes); + auto root_node = std::make_unique(); + root_node->hn_children = std::move(hier_nodes); pts->pss_hier_tree = std::make_shared( std::move(hier_tree_vec)); + pts->pss_root_node = std::move(root_node); pts->set_indents(std::move(pretty_indents)); - pts->replace_with(full_text); + pts->replace_with_mutable(full_text, + top_tc->get_sub_source()->get_text_format()); pretty_tc->set_sub_source(pts); if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) { pretty_tc->set_top(0_vl); @@ -498,12 +509,6 @@ open_pretty_view() pretty_tc->redo_search(); } -template -static void -ignore_case(const T&) -{ -} - static void build_all_help_text() { diff --git a/test/Makefile.am b/test/Makefile.am index e01c0424..23aaa479 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -253,6 +253,7 @@ dist_noinst_DATA = \ bad-config2/formats/invalid-config/config.truncated.json \ bad-config-json/formats/invalid-json/format.json \ bad-config-json/formats/invalid-key/format.json \ + books.json \ books.xml \ file_for_dot_read.sql \ datafile_simple.0 \ diff --git a/test/books.json b/test/books.json new file mode 100644 index 00000000..c8855591 --- /dev/null +++ b/test/books.json @@ -0,0 +1,14 @@ +{ + "catalog": [ + { + "author": "Gambardella, Matthew", + "title": "XML Developer's Guide", + "description": "An in-depth look at creating applications with XML." + }, + { + "author": "Ralls, Kim", + "title": "Midnight Rain", + "description": "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world." + } + ] +} \ No newline at end of file diff --git a/test/expected/expected.am b/test/expected/expected.am index e3c84fd5..09764e95 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -1198,10 +1198,14 @@ EXPECTED_FILES = \ $(srcdir)/%reldir%/test_text_file.sh_0bba304f34ae07c4fa9e91e0b42f5fe98654a6a8.out \ $(srcdir)/%reldir%/test_text_file.sh_11fd274911e45a743b4de616888a64183d07cb76.err \ $(srcdir)/%reldir%/test_text_file.sh_11fd274911e45a743b4de616888a64183d07cb76.out \ + $(srcdir)/%reldir%/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.err \ + $(srcdir)/%reldir%/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out \ $(srcdir)/%reldir%/test_text_file.sh_1ce4056d72b871f8bb844c86aade2a9b1da58030.err \ $(srcdir)/%reldir%/test_text_file.sh_1ce4056d72b871f8bb844c86aade2a9b1da58030.out \ $(srcdir)/%reldir%/test_text_file.sh_4226123565a53b4e3f80e602c1f294721e8e07bf.err \ $(srcdir)/%reldir%/test_text_file.sh_4226123565a53b4e3f80e602c1f294721e8e07bf.out \ + $(srcdir)/%reldir%/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.err \ + $(srcdir)/%reldir%/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.out \ $(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err \ $(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out \ $(srcdir)/%reldir%/test_text_file.sh_5e9320f18d066e6fc930dbbffc357af64312bd4b.err \ diff --git a/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out b/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out index f3f671fe..6aa2ff5a 100644 --- a/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out +++ b/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out @@ -4839,7 +4839,7 @@ "alt-msg": "" }, "x7b": { - "command": ":prev-location", + "command": ":prev-section", "alt-msg": "${keymap_def_next_location}" }, "x7c": { @@ -4847,7 +4847,7 @@ "alt-msg": "" }, "x7d": { - "command": ":next-location", + "command": ":next-section", "alt-msg": "${keymap_def_prev_location}" } }, @@ -5031,11 +5031,11 @@ "keymap_def_clear": "Press ${ansi_bold}C${ansi_norm} to clear marked messages", "keymap_def_db_view": "Press ${ansi_bold}v${ansi_norm}/${ansi_bold}V${ansi_norm} to switch to the SQL result view", "keymap_def_hist_view": "Press ${ansi_bold}i${ansi_norm}/${ansi_bold}I${ansi_norm} to switch to the histogram view", - "keymap_def_next_location": "Press ${ansi_bold}}${ansi_norm} to move to the next location in history", "keymap_def_next_mark": "Press ${ansi_bold}c${ansi_norm} to copy marked lines to the clipboard; press ${ansi_bold}C${ansi_norm} to clear marked lines", + "keymap_def_next_section": "Press ${ansi_bold}}${ansi_norm} to move to the next section in history", "keymap_def_next_user_mark": "Press ${ansi_bold}u${ansi_norm}/${ansi_bold}U${ansi_norm} to move forward/backward through user bookmarks", "keymap_def_pop_view": "Press ${ansi_bold}q${ansi_norm} to return to the previous view", - "keymap_def_prev_location": "Press ${ansi_bold}{${ansi_norm} to move to the previous location in history", + "keymap_def_prev_section": "Press ${ansi_bold}{${ansi_norm} to move to the previous section in history", "keymap_def_scroll_horiz": "Press \\'${ansi_bold}>${ansi_norm}\\' or \\'${ansi_bold}<${ansi_norm}\\' to scroll horizontally to a search result", "keymap_def_text_view": "Press ${ansi_bold}t${ansi_norm} to switch to the text view", "keymap_def_time_offset": "Press ${ansi_bold}s${ansi_norm}/${ansi_bold}S${ansi_norm} to move forward/backward through slow downs", diff --git a/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out b/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out index a8263cc3..753f84d5 100644 --- a/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out +++ b/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out @@ -3,11 +3,11 @@ /global/keymap_def_clear -> default-keymap.json:13 /global/keymap_def_db_view -> default-keymap.json:8 /global/keymap_def_hist_view -> default-keymap.json:9 -/global/keymap_def_next_location -> default-keymap.json:15 /global/keymap_def_next_mark -> default-keymap.json:16 +/global/keymap_def_next_section -> default-keymap.json:15 /global/keymap_def_next_user_mark -> default-keymap.json:7 /global/keymap_def_pop_view -> default-keymap.json:11 -/global/keymap_def_prev_location -> default-keymap.json:14 +/global/keymap_def_prev_section -> default-keymap.json:14 /global/keymap_def_scroll_horiz -> default-keymap.json:6 /global/keymap_def_text_view -> default-keymap.json:10 /global/keymap_def_time_offset -> default-keymap.json:17 diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index c0472c9b..d10bf871 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -1092,7 +1092,8 @@ For support questions, email: number, percent into the file, timestamp, or an anchor in a text file See Also - :next-location, :next-mark, :prev-location, :prev-mark, :relative-goto + :next-location, :next-mark, :next-section, :prev-location, :prev-mark, + :prev-section, :relative-goto Examples #1 To go to line 22: :goto 22  @@ -1232,7 +1233,8 @@ For support questions, email: ══════════════════════════════════════════════════════════════════════ Move to the next position in the location history See Also - :goto, :next-mark, :prev-location, :prev-mark, :relative-goto + :goto, :next-mark, :next-section, :prev-location, :prev-mark, + :prev-section, :relative-goto :next-mark type1 [... typeN] ══════════════════════════════════════════════════════════════════════ @@ -1241,14 +1243,21 @@ For support questions, email: type The type of bookmark -- error, warning, search, user, file, meta See Also - :goto, :hide-unmarked-lines, :mark, :next-location, :prev-location, - :prev-mark, :prev-mark, :relative-goto + :goto, :hide-unmarked-lines, :mark, :next-location, :next-section, + :prev-location, :prev-mark, :prev-mark, :prev-section, :relative-goto Example #1 To go to the next error: :next-mark error  +:next-section +══════════════════════════════════════════════════════════════════════ + Move to the next section in the document +See Also + :goto, :next-location, :next-mark, :prev-location, :prev-mark, + :prev-section, :relative-goto + :open path1 [... pathN] ══════════════════════════════════════════════════════════════════════ Open the given file(s) in lnav. Opening files on machines @@ -1315,7 +1324,8 @@ For support questions, email: ══════════════════════════════════════════════════════════════════════ Move to the previous position in the location history See Also - :goto, :next-location, :next-mark, :prev-mark, :relative-goto + :goto, :next-location, :next-mark, :next-section, :prev-mark, + :prev-section, :relative-goto :prev-mark type1 [... typeN] ══════════════════════════════════════════════════════════════════════ @@ -1326,13 +1336,21 @@ For support questions, email: user, file, meta See Also :goto, :hide-unmarked-lines, :mark, :next-location, :next-mark, - :next-mark, :prev-location, :relative-goto + :next-mark, :next-section, :prev-location, :prev-section, + :relative-goto Example #1 To go to the previous error: :prev-mark error  +:prev-section +══════════════════════════════════════════════════════════════════════ + Move to the previous section in the document +See Also + :goto, :next-location, :next-mark, :next-section, :prev-location, + :prev-mark, :relative-goto + :prompt type [--alt] [prompt] [initial-value] ══════════════════════════════════════════════════════════════════════ Open the given prompt @@ -1409,7 +1427,8 @@ For support questions, email: Parameter line-count|N% The amount to move the view by. See Also - :goto, :next-location, :next-mark, :prev-location, :prev-mark + :goto, :next-location, :next-mark, :next-section, :prev-location, + :prev-mark, :prev-section Examples #1 To move 22 lines down in the view: :relative-goto +22  diff --git a/test/expected/test_cmds.sh_b755a8b48c0f602f0270500b0117b76e11db546e.out b/test/expected/test_cmds.sh_b755a8b48c0f602f0270500b0117b76e11db546e.out index 5fb4d900..e27bfe5d 100644 --- a/test/expected/test_cmds.sh_b755a8b48c0f602f0270500b0117b76e11db546e.out +++ b/test/expected/test_cmds.sh_b755a8b48c0f602f0270500b0117b76e11db546e.out @@ -33,5 +33,6 @@ Apr 7 07:32:56 Tim-Stacks-iMac.local logger[234]: Bad data { abc, 123, 456 -) -}] + ) +} +] diff --git a/test/expected/test_json_format.sh_168cac40c27f547044c89d39eb0ff2ef81da4b21.out b/test/expected/test_json_format.sh_168cac40c27f547044c89d39eb0ff2ef81da4b21.out index fa37c2f5..96ded5f3 100644 --- a/test/expected/test_json_format.sh_168cac40c27f547044c89d39eb0ff2ef81da4b21.out +++ b/test/expected/test_json_format.sh_168cac40c27f547044c89d39eb0ff2ef81da4b21.out @@ -1,8 +1,8 @@ {"ts": "2013-09-06T20:00:48.124817Z", "lvl": "TRACE", "msg": "trace test"} {"ts": "2013-09-06T20:00:49.124817Z", "lvl": "INFO", "msg": "Starting up \u001B[0;32mservice\u001B[0m"} {"ts": "2013-09-06T22:00:49.124817Z", "lvl": "INFO", "msg": "Shutting down service", "user": "steve@example.com"} -{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "Details...\n"} -{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "Details...\n"} +{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "D\bDetails...\n"} +{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "D\bDe\betails...\n"} {"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG3", "msg": "Details...\n"} {"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG2", "msg": "Details...\n"} {"ts": "2013-09-06 22:01:00Z", "lvl": "DEBUG", "msg": "Details..."} diff --git a/test/expected/test_json_format.sh_469f005b0708d629bc95f0c48a5e390f440c1fef.out b/test/expected/test_json_format.sh_469f005b0708d629bc95f0c48a5e390f440c1fef.out index 83ed37aa..ad4b0dbc 100644 --- a/test/expected/test_json_format.sh_469f005b0708d629bc95f0c48a5e390f440c1fef.out +++ b/test/expected/test_json_format.sh_469f005b0708d629bc95f0c48a5e390f440c1fef.out @@ -6,9 +6,9 @@ [2013-09-06T22:00:49.124] ⋮ Shutting down service user: steve@example.com -[2013-09-06T22:00:59.124] ⋮ Details... +[2013-09-06T22:00:59.124] ⋮ Details... -[2013-09-06T22:00:59.124] ⋮ Details... +[2013-09-06T22:00:59.124] ⋮ Details... [2013-09-06T22:00:59.124] ⋮ Details... diff --git a/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out b/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out index d4f71bfe..bfb866a7 100644 --- a/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out +++ b/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out @@ -6,10 +6,10 @@ [2013-09-06T22:00:49.124000Z] ⋮ Shutting down servicebork bork bork user: mailto:steve@example.com -[2013-09-06T22:00:59.124000Z] ⋮ Details... +[2013-09-06T22:00:59.124000Z] ⋮ Details... bork bork bork -[2013-09-06T22:00:59.124000Z] ⋮ Details... +[2013-09-06T22:00:59.124000Z] ⋮ Details... bork bork bork [2013-09-06T22:00:59.124000Z] ⋮ Details... diff --git a/test/expected/test_sessions.sh_ba1ded92531350668301431db64df2d2f4a2e9ee.out b/test/expected/test_sessions.sh_ba1ded92531350668301431db64df2d2f4a2e9ee.out index c582aa5c..0e301c33 100644 --- a/test/expected/test_sessions.sh_ba1ded92531350668301431db64df2d2f4a2e9ee.out +++ b/test/expected/test_sessions.sh_ba1ded92531350668301431db64df2d2f4a2e9ee.out @@ -2,6 +2,4 @@ ├ org.lnav.test: ╰ Hello, World! 192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7" - 📝 Annotations available, focus on this line and use :annotate to apply them 192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7" - 📝 Annotations available, focus on this line and use :annotate to apply them diff --git a/test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.err b/test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out b/test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out new file mode 100644 index 00000000..84ebd402 --- /dev/null +++ b/test/expected/test_text_file.sh_143a40164c93c7ec44a66e7940b92b128a421147.out @@ -0,0 +1,5 @@ + "title": "Midnight Rain", + "description": "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world." + } + ] +} diff --git a/test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.err b/test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.out b/test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.out new file mode 100644 index 00000000..e6de131b --- /dev/null +++ b/test/expected/test_text_file.sh_4dd174410d702a7b4be794fb6fa2c8889bd768d6.out @@ -0,0 +1,6 @@ + "author": "Ralls, Kim", + "title": "Midnight Rain", + "description": "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world." + } + ] +} diff --git a/test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.err b/test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.out b/test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.out new file mode 100644 index 00000000..84ebd402 --- /dev/null +++ b/test/expected/test_text_file.sh_596b120fbea638472a27964444e262b4572afacc.out @@ -0,0 +1,5 @@ + "title": "Midnight Rain", + "description": "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world." + } + ] +} diff --git a/test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.err b/test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.out b/test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.out new file mode 100644 index 00000000..e6de131b --- /dev/null +++ b/test/expected/test_text_file.sh_8a4954af3e536b3789b1fd5b33519e9d444cc933.out @@ -0,0 +1,6 @@ + "author": "Ralls, Kim", + "title": "Midnight Rain", + "description": "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world." + } + ] +} diff --git a/test/logfile_json.json b/test/logfile_json.json index fa37c2f5..96ded5f3 100644 --- a/test/logfile_json.json +++ b/test/logfile_json.json @@ -1,8 +1,8 @@ {"ts": "2013-09-06T20:00:48.124817Z", "lvl": "TRACE", "msg": "trace test"} {"ts": "2013-09-06T20:00:49.124817Z", "lvl": "INFO", "msg": "Starting up \u001B[0;32mservice\u001B[0m"} {"ts": "2013-09-06T22:00:49.124817Z", "lvl": "INFO", "msg": "Shutting down service", "user": "steve@example.com"} -{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "Details...\n"} -{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "Details...\n"} +{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG5", "msg": "D\bDetails...\n"} +{"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG4", "msg": "D\bDe\betails...\n"} {"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG3", "msg": "Details...\n"} {"ts": "2013-09-06T22:00:59.124817Z", "lvl": "DEBUG2", "msg": "Details...\n"} {"ts": "2013-09-06 22:01:00Z", "lvl": "DEBUG", "msg": "Details..."} diff --git a/test/test_text_file.sh b/test/test_text_file.sh index 916e9fd6..0d5e4442 100644 --- a/test/test_text_file.sh +++ b/test/test_text_file.sh @@ -79,3 +79,21 @@ run_cap_test ${lnav_test} -n \ -c ';SELECT top_meta FROM lnav_top_view' \ -c ':write-json-to -' \ ${test_dir}/formats/jsontest/format.json + +run_cap_test ${lnav_test} -n \ + -c ':goto 3' \ + -c ':next-section' \ + ${test_dir}/books.json + +run_cap_test ${lnav_test} -n \ + -c ':goto 3' \ + -c ':next-section' \ + < ${test_dir}/books.json + +run_cap_test ${lnav_test} -n \ + -c ':goto #/catalog/1/title' \ + ${test_dir}/books.json + +run_cap_test ${lnav_test} -n \ + -c ':goto #/catalog/1/title' \ + < ${test_dir}/books.json