[hotkeys] change curly braces to move to the next/prev section

and... a bunch of other stuff
pull/1242/head
Tim Stack 3 months ago
parent 0eb394f4b8
commit ee345321f7

@ -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:

@ -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;

@ -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;

@ -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);
}
}

@ -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;
}
}

@ -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

@ -27,6 +27,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <algorithm>
#include <utility>
#include <vector>
@ -60,6 +61,117 @@ hier_node::lookup_child(section_key_t key) const
}));
}
nonstd::optional<size_t>
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_result>
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::child_neighbors_result>
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<const hier_node*>
hier_node::lookup_path(const hier_node* root,
const std::vector<section_key_t>& path)
@ -81,11 +193,24 @@ hier_node::lookup_path(const hier_node* root,
return retval;
}
std::vector<section_key_t>
metadata::path_for_range(size_t start, size_t stop)
{
std::vector<section_key_t> 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<section_interval_t> mb_intervals;
std::vector<section_type_interval_t> mb_type_intervals;
std::unique_ptr<hier_node> mb_root_node;
std::set<size_t> 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<int64_t>();
}
});
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<hier_node>());
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<hier_node>());
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<int>(
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<int>(
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<size_t> sw_indents;
std::vector<element> sw_values{};
std::vector<data_token_t> sw_container_tokens;
std::vector<interval_state> sw_interval_state;
std::vector<lnav::document::section_interval_t> sw_intervals;
std::vector<lnav::document::section_type_interval_t> sw_type_intervals;

@ -69,6 +69,18 @@ struct hier_node {
nonstd::optional<hier_node*> lookup_child(section_key_t key) const;
nonstd::optional<size_t> child_index(const hier_node* hn) const;
struct child_neighbors_result {
nonstd::optional<const hier_node*> cnr_previous;
nonstd::optional<const hier_node*> cnr_next;
};
nonstd::optional<child_neighbors_result> child_neighbors(
const hier_node* hn, file_off_t offset) const;
nonstd::optional<child_neighbors_result> line_neighbors(size_t ln) const;
nonstd::optional<size_t> 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<hier_node> m_sections_root;
section_types_tree_t m_section_types_tree;
std::set<size_t> m_indents;
text_format_t m_text_format{text_format_t::TF_UNKNOWN};
std::vector<section_key_t> path_for_range(size_t start, size_t stop);
std::vector<breadcrumb::possibility> possibility_provider(
const std::vector<section_key_t>& path);

@ -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);
}
}
}

@ -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<context> fos_contexts;

@ -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."
}
]
}

@ -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`
----

@ -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": {

@ -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) {

@ -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();

@ -1144,6 +1144,62 @@ com_goto_location(exec_context& ec,
return Ok(retval);
}
static Result<std::string, lnav::console::user_message>
com_next_section(exec_context& ec,
std::string cmdline,
std::vector<std::string>& 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<text_anchors*>(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<std::string, lnav::console::user_message>
com_prev_section(exec_context& ec,
std::string cmdline,
std::vector<std::string>& 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<text_anchors*>(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,

@ -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

@ -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());

@ -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()));

@ -31,6 +31,7 @@
#include "base/itertools.hh"
#include "config.h"
#include "scn/scn.h"
static std::vector<plain_text_source::text_line>
to_text_line(const std::vector<attr_line_t>& 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<lnav::document::section_key_t> path;
while (!hier_sf.empty()) {
auto comp_pair = hier_sf.split_when(string_fragment::tag1{'/'});
auto scan_res
= scn::scan_value<int64_t>(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<std::string>())
{
return text_anchors::to_anchor_string(
path_for_line.back().get<std::string>());
}
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<vis_line_t>
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<std::string>()) {
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<std::string>()) {
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;
}

@ -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<std::string>& text_lines);
@ -123,6 +126,8 @@ public:
nonstd::optional<vis_line_t> row_for_anchor(const std::string& id) override;
nonstd::optional<std::string> anchor_for_row(vis_line_t vl) override;
std::unordered_set<std::string> get_anchors() override;
nonstd::optional<vis_line_t> adjacent_anchor(vis_line_t vl,
direction dir) override;
protected:
size_t compute_longest_line();

@ -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<lnav::document::hier_node>());

@ -97,9 +97,9 @@ public:
std::set<size_t> 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<data_token_t> pp_container_tokens{};
std::stack<int> pp_body_lines{};
data_scanner* pp_scanner;
string_attrs_t pp_attrs;

@ -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();
}

@ -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<line_filter_observer*>(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<breadcrumb::possibility> { 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<line_filter_observer*>(
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<breadcrumb::possibility> { return {}; },
[](const auto& key) {});
}
} else {
auto* lfo
= dynamic_cast<line_filter_observer*>(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<plain_text_source>();
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<file_ssize_t>(content.length()),
static_cast<file_ssize_t>(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<file_ssize_t>(st.st_size),
static_cast<file_ssize_t>(lf->get_index_size()),
{},
};
}
@ -1159,6 +1160,142 @@ textfile_sub_source::get_anchors()
return retval;
}
static nonstd::optional<vis_line_t>
to_vis_line(const std::shared_ptr<logfile>& 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<vis_line_t>
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<line_filter_observer*>(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<std::string>()) {
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<std::string>()) {
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<std::string>
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<std::string> 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<std::string>())
{
return text_anchors::to_anchor_string(
path_for_line.back().get<std::string>());
}
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

@ -147,6 +147,9 @@ public:
nonstd::optional<std::string> anchor_for_row(vis_line_t vl) override;
nonstd::optional<vis_line_t> adjacent_anchor(vis_line_t vl,
direction dir) override;
std::unordered_set<std::string> get_anchors() override;
void quiesce() override;

@ -314,6 +314,17 @@ public:
virtual nonstd::optional<vis_line_t> row_for_anchor(const std::string& id)
= 0;
enum class direction {
prev,
next,
};
virtual nonstd::optional<vis_line_t> adjacent_anchor(vis_line_t vl,
direction dir)
{
return nonstd::nullopt;
}
virtual nonstd::optional<std::string> anchor_for_row(vis_line_t vl) = 0;
virtual std::unordered_set<std::string> get_anchors() = 0;

@ -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;
}

@ -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" \

@ -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());
}
});

@ -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(' ');

@ -144,6 +144,11 @@ public:
this->tds_doc_sections.m_indents = std::move(indents);
}
void set_sections_root(std::unique_ptr<lnav::document::hier_node>&& hn)
{
this->tds_doc_sections.m_sections_root = std::move(hn);
}
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override
{
@ -299,8 +304,8 @@ public:
= interval_tree::Interval<file_off_t, lnav::document::hier_node*>;
std::shared_ptr<lnav::document::sections_tree_t> pss_interval_tree;
std::vector<std::unique_ptr<lnav::document::hier_node>> pss_hier_nods;
std::shared_ptr<hier_tree_t> pss_hier_tree;
std::unique_ptr<lnav::document::hier_node> pss_root_node;
};
static void
@ -324,7 +329,7 @@ open_pretty_view()
return;
}
std::vector<attr_line_t> 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<lnav::document::sections_tree_t>(
std::move(all_intervals));
pts->pss_hier_nods = std::move(hier_nodes);
auto root_node = std::make_unique<lnav::document::hier_node>();
root_node->hn_children = std::move(hier_nodes);
pts->pss_hier_tree = std::make_shared<pretty_sub_source::hier_tree_t>(
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<typename T>
static void
ignore_case(const T&)
{
}
static void
build_all_help_text()
{

@ -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 \

@ -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."
}
]
}

@ -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 \

@ -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",

@ -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

@ -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 

@ -33,5 +33,6 @@ Apr 7 07:32:56 Tim-Stacks-iMac.local logger[234]: Bad data {
abc,
123,
456
)
}]
)
}
]

@ -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..."}

@ -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...

@ -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...

@ -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

@ -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."
}
]
}

@ -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."
}
]
}

@ -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."
}
]
}

@ -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."
}
]
}

@ -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..."}

@ -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

Loading…
Cancel
Save