[ansi_scrubber] handle unknown sequences

... and many other tweaks.
pull/1205/head
Tim Stack 10 months ago
parent ce391166ee
commit dd8a23ad51

@ -63,7 +63,8 @@ Features:
variables are now defined inside **lnav** and refer to variables are now defined inside **lnav** and refer to
the location of the user's configuration directory and the location of the user's configuration directory and
the directory where cached data is stored, respectively. the directory where cached data is stored, respectively.
* The `<pre>` tag is now recognized in Markdown files. * The `<pre>` and `<img>` tags are now recognized in
Markdown files.
* The `style` attribute in `<span>` tags is now supported. * The `style` attribute in `<span>` tags is now supported.
The following CSS properties and values are supported: The following CSS properties and values are supported:
* `color` and `background-color` with CSS color names * `color` and `background-color` with CSS color names

@ -5,7 +5,7 @@
[![Coverage Status](https://coveralls.io/repos/github/tstack/lnav/badge.svg?branch=master)](https://coveralls.io/github/tstack/lnav?branch=master) [![Coverage Status](https://coveralls.io/repos/github/tstack/lnav/badge.svg?branch=master)](https://coveralls.io/github/tstack/lnav?branch=master)
[![lnav](https://snapcraft.io/lnav/badge.svg)](https://snapcraft.io/lnav) [![lnav](https://snapcraft.io/lnav/badge.svg)](https://snapcraft.io/lnav)
[<img src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg" height="20"/>](https://discord.gg/erBPnKwz7R) [<img src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg" height="20" alt="Discord Logo"/>](https://discord.gg/erBPnKwz7R)
_This is the source repository for **lnav**, visit [https://lnav.org](https://lnav.org) for a high level overview._ _This is the source repository for **lnav**, visit [https://lnav.org](https://lnav.org) for a high level overview._

@ -34,6 +34,7 @@
#include "ansi_scrubber.hh" #include "ansi_scrubber.hh"
#include "ansi_vars.hh" #include "ansi_vars.hh"
#include "base/lnav_log.hh"
#include "base/opt_util.hh" #include "base/opt_util.hh"
#include "config.h" #include "config.h"
#include "pcrepp/pcre2pp.hh" #include "pcrepp/pcre2pp.hh"
@ -44,7 +45,7 @@ static const lnav::pcre2pp::code&
ansi_regex() ansi_regex()
{ {
static const auto retval = lnav::pcre2pp::code::from_const( static const auto retval = lnav::pcre2pp::code::from_const(
"\x1b\\[([\\d=;\\?]*)([a-zA-Z])|(?:\\X\x08\\X)+"); R"(\x1b\[([\d=;\?]*)([a-zA-Z])|\x1b\](\d+);(.*?)(?:\x07|\x1b\\)|(?:\X\x08\X)+)");
return retval; return retval;
} }
@ -124,6 +125,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
const auto& regex = ansi_regex(); const auto& regex = ansi_regex();
int64_t origin_offset = 0; int64_t origin_offset = 0;
int last_origin_offset_end = 0; int last_origin_offset_end = 0;
nonstd::optional<std::string> href;
size_t href_start = 0;
replace(str.begin(), str.end(), '\0', ' '); replace(str.begin(), str.end(), '\0', ' ');
auto matcher = regex.capture_from(str).into(md); auto matcher = regex.capture_from(str).into(md);
@ -239,134 +242,164 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
continue; continue;
} }
if (!md[1]) {
continue;
}
auto seq = md[1].value();
auto terminator = md[2].value();
struct line_range lr; struct line_range lr;
text_attrs attrs; text_attrs attrs;
bool has_attrs = false; bool has_attrs = false;
nonstd::optional<role_t> role; nonstd::optional<role_t> role;
size_t lpc;
if (md[3]) {
switch (terminator[0]) { auto osc_id = scn::scan_value<int32_t>(md[3]->to_string_view());
case 'm':
for (lpc = seq.sf_begin; if (osc_id) {
lpc != std::string::npos && lpc < (size_t) seq.sf_end;) switch (osc_id.value()) {
{ case 8:
auto ansi_code_res = scn::scan_value<int>( auto split_res
scn::string_view{&str[lpc], &str[seq.sf_end]}); = md[4]->split_pair(string_fragment::tag1{';'});
if (split_res) {
if (ansi_code_res) { // auto params = split_res->first;
auto ansi_code = ansi_code_res.value(); auto uri = split_res->second;
if (90 <= ansi_code && ansi_code <= 97) {
ansi_code -= 60; if (href) {
attrs.ta_attrs |= A_STANDOUT; if (sa != nullptr) {
} sa->emplace_back(
if (30 <= ansi_code && ansi_code <= 37) { line_range{(int) href_start,
attrs.ta_fg_color = ansi_code - 30; (int) str.size()},
VC_HYPERLINK.value(href.value()));
}
href = nonstd::nullopt;
}
if (!uri.empty()) {
href = uri.to_string();
href_start = sf.sf_begin;
}
} }
if (40 <= ansi_code && ansi_code <= 47) { break;
attrs.ta_bg_color = ansi_code - 40; }
}
} else if (md[1]) {
auto seq = md[1].value();
auto terminator = md[2].value();
switch (terminator[0]) {
case 'm':
for (auto lpc = seq.sf_begin;
lpc != std::string::npos && lpc < (size_t) seq.sf_end;)
{
auto ansi_code_res = scn::scan_value<int>(
scn::string_view{&str[lpc], &str[seq.sf_end]});
if (ansi_code_res) {
auto ansi_code = ansi_code_res.value();
if (90 <= ansi_code && ansi_code <= 97) {
ansi_code -= 60;
attrs.ta_attrs |= A_STANDOUT;
}
if (30 <= ansi_code && ansi_code <= 37) {
attrs.ta_fg_color = ansi_code - 30;
}
if (40 <= ansi_code && ansi_code <= 47) {
attrs.ta_bg_color = ansi_code - 40;
}
switch (ansi_code) {
case 1:
attrs.ta_attrs |= A_BOLD;
break;
case 2:
attrs.ta_attrs |= A_DIM;
break;
case 4:
attrs.ta_attrs |= A_UNDERLINE;
break;
case 7:
attrs.ta_attrs |= A_REVERSE;
break;
}
} }
switch (ansi_code) { lpc = str.find(';', lpc);
case 1: if (lpc != std::string::npos) {
attrs.ta_attrs |= A_BOLD; lpc += 1;
break;
case 2:
attrs.ta_attrs |= A_DIM;
break;
case 4:
attrs.ta_attrs |= A_UNDERLINE;
break;
case 7:
attrs.ta_attrs |= A_REVERSE;
break;
} }
} }
lpc = str.find(';', lpc); has_attrs = true;
if (lpc != std::string::npos) { break;
lpc += 1;
}
}
has_attrs = true;
break;
case 'C': { case 'C': {
auto spaces_res auto spaces_res
= scn::scan_value<unsigned int>(seq.to_string_view()); = scn::scan_value<unsigned int>(seq.to_string_view());
if (spaces_res && spaces_res.value() > 0) { if (spaces_res && spaces_res.value() > 0) {
str.insert((std::string::size_type) sf.sf_end, str.insert((std::string::size_type) sf.sf_end,
spaces_res.value(), spaces_res.value(),
' '); ' ');
}
break;
} }
break;
}
case 'H': { case 'H': {
unsigned int row = 0, spaces = 0; unsigned int row = 0, spaces = 0;
if (scn::scan(seq.to_string_view(), "{};{}", row, spaces) if (scn::scan(seq.to_string_view(), "{};{}", row, spaces)
&& spaces > 1) && spaces > 1)
{ {
int ispaces = spaces - 1; int ispaces = spaces - 1;
if (ispaces > sf.sf_begin) { if (ispaces > sf.sf_begin) {
str.insert((unsigned long) sf.sf_end, str.insert((unsigned long) sf.sf_end,
ispaces - sf.sf_begin, ispaces - sf.sf_begin,
' '); ' ');
}
} }
break;
} }
break;
}
case 'O': { case 'O': {
auto role_res = scn::scan_value<int>(seq.to_string_view()); auto role_res = scn::scan_value<int>(seq.to_string_view());
if (role_res) { if (role_res) {
role_t role_tmp = (role_t) role_res.value(); role_t role_tmp = (role_t) role_res.value();
if (role_tmp > role_t::VCR_NONE if (role_tmp > role_t::VCR_NONE
&& role_tmp < role_t::VCR__MAX) && role_tmp < role_t::VCR__MAX)
{ {
role = role_tmp; role = role_tmp;
has_attrs = true; has_attrs = true;
}
} }
break;
} }
break;
} }
} }
str.erase(str.begin() + sf.sf_begin, str.begin() + sf.sf_end); if (md[1] || md[3]) {
if (sa != nullptr) { str.erase(str.begin() + sf.sf_begin, str.begin() + sf.sf_end);
shift_string_attrs(*sa, sf.sf_begin, -sf.length()); if (sa != nullptr) {
shift_string_attrs(*sa, sf.sf_begin, -sf.length());
if (has_attrs) {
for (auto rit = sa->rbegin(); rit != sa->rend(); rit++) { if (has_attrs) {
if (rit->sa_range.lr_end != -1) { for (auto rit = sa->rbegin(); rit != sa->rend(); rit++) {
continue; if (rit->sa_range.lr_end != -1) {
continue;
}
rit->sa_range.lr_end = sf.sf_begin;
} }
rit->sa_range.lr_end = sf.sf_begin; lr.lr_start = sf.sf_begin;
} lr.lr_end = -1;
lr.lr_start = sf.sf_begin; if (!attrs.empty()) {
lr.lr_end = -1; sa->emplace_back(lr, VC_STYLE.value(attrs));
if (!attrs.empty()) { }
sa->emplace_back(lr, VC_STYLE.value(attrs)); role | [&lr, &sa](role_t r) {
sa->emplace_back(lr, VC_ROLE.value(r));
};
} }
role | [&lr, &sa](role_t r) { sa->emplace_back(
sa->emplace_back(lr, VC_ROLE.value(r)); line_range{last_origin_offset_end, sf.sf_begin},
}; SA_ORIGIN_OFFSET.value(origin_offset));
last_origin_offset_end = sf.sf_begin;
origin_offset += sf.length();
} }
sa->emplace_back(line_range{last_origin_offset_end, sf.sf_begin},
SA_ORIGIN_OFFSET.value(origin_offset));
last_origin_offset_end = sf.sf_begin;
origin_offset += sf.length();
}
matcher.reload_input(str, sf.sf_begin); matcher.reload_input(str, sf.sf_begin);
}
} }
if (sa != nullptr && last_origin_offset_end > 0) { if (sa != nullptr && last_origin_offset_end > 0) {

@ -312,6 +312,7 @@ println(FILE* file, const attr_line_t& al)
auto line_style = fmt::text_style{}; auto line_style = fmt::text_style{};
auto fg_style = fmt::text_style{}; auto fg_style = fmt::text_style{};
auto start = last_point.value(); auto start = last_point.value();
nonstd::optional<std::string> href;
for (const auto& attr : al.get_attrs()) { for (const auto& attr : al.get_attrs()) {
if (!attr.sa_range.contains(start) if (!attr.sa_range.contains(start)
@ -321,7 +322,10 @@ println(FILE* file, const attr_line_t& al)
} }
try { try {
if (attr.sa_type == &VC_BACKGROUND) { if (attr.sa_type == &VC_HYPERLINK) {
auto saw = string_attr_wrapper<std::string>(&attr);
href = saw.get();
} else if (attr.sa_type == &VC_BACKGROUND) {
auto saw = string_attr_wrapper<int64_t>(&attr); auto saw = string_attr_wrapper<int64_t>(&attr);
auto color_opt = curses_color_to_terminal_color(saw.get()); auto color_opt = curses_color_to_terminal_color(saw.get());
@ -484,6 +488,9 @@ println(FILE* file, const attr_line_t& al)
line_style |= default_bg_style; line_style |= default_bg_style;
} }
if (href) {
fmt::print(file, FMT_STRING("\x1b]8;;{}\x1b\\"), href.value());
}
if (start < str.size()) { if (start < str.size()) {
auto actual_end = std::min(str.size(), static_cast<size_t>(point)); auto actual_end = std::min(str.size(), static_cast<size_t>(point));
fmt::print(file, fmt::print(file,
@ -491,6 +498,9 @@ println(FILE* file, const attr_line_t& al)
FMT_STRING("{}"), FMT_STRING("{}"),
str.substr(start, actual_end - start)); str.substr(start, actual_end - start));
} }
if (href) {
fmt::print(file, FMT_STRING("\x1b]8;;\x1b\\"));
}
last_point = point; last_point = point;
} }
fmt::print(file, "\n"); fmt::print(file, "\n");

@ -50,3 +50,4 @@ string_attr_type<int64_t> VC_GRAPHIC("graphic");
string_attr_type<block_elem_t> VC_BLOCK_ELEM("block-elem"); string_attr_type<block_elem_t> VC_BLOCK_ELEM("block-elem");
string_attr_type<int64_t> VC_FOREGROUND("foreground"); string_attr_type<int64_t> VC_FOREGROUND("foreground");
string_attr_type<int64_t> VC_BACKGROUND("background"); string_attr_type<int64_t> VC_BACKGROUND("background");
string_attr_type<std::string> VC_HYPERLINK("hyperlink");

@ -233,6 +233,7 @@ extern string_attr_type<int64_t> VC_GRAPHIC;
extern string_attr_type<block_elem_t> VC_BLOCK_ELEM; extern string_attr_type<block_elem_t> VC_BLOCK_ELEM;
extern string_attr_type<int64_t> VC_FOREGROUND; extern string_attr_type<int64_t> VC_FOREGROUND;
extern string_attr_type<int64_t> VC_BACKGROUND; extern string_attr_type<int64_t> VC_BACKGROUND;
extern string_attr_type<std::string> VC_HYPERLINK;
namespace lnav { namespace lnav {
@ -246,6 +247,14 @@ preformatted(S str)
return std::make_pair(std::move(str), SA_PREFORMATTED.template value()); return std::make_pair(std::move(str), SA_PREFORMATTED.template value());
} }
template<typename S>
inline std::pair<S, string_attr_pair>
href(S str, std::string href)
{
return std::make_pair(std::move(str),
VC_HYPERLINK.template value(std::move(href)));
}
} // namespace attrs } // namespace attrs
} // namespace string } // namespace string
@ -699,6 +708,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _snippet_border(
VC_ROLE.template value(role_t::VCR_SNIPPET_BORDER)); VC_ROLE.template value(role_t::VCR_SNIPPET_BORDER));
} }
inline std::pair<std::string, string_attr_pair> operator"" _link(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_HYPERLINK.template value(std::string(str, len)));
}
} // namespace literals } // namespace literals
} // namespace roles } // namespace roles

@ -134,7 +134,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv,
time_lr.lr_end = time_str.length(); time_lr.lr_end = time_str.length();
time_line.with_attr( time_line.with_attr(
string_attr(time_lr, VC_STYLE.value(text_attrs{A_BOLD}))); string_attr(time_lr, VC_STYLE.value(text_attrs{A_BOLD})));
time_str.append(" -- "); time_str.append(" \u2014 ");
time_lr.lr_start = time_str.length(); time_lr.lr_start = time_str.length();
time_str.append(humanize::time::point::from_tv(ll->get_timeval()) time_str.append(humanize::time::point::from_tv(ll->get_timeval())
.with_convert_to_local(true) .with_convert_to_local(true)
@ -157,14 +157,13 @@ field_overlay_source::build_field_lines(const listview_curses& lv,
dts.set_base_time(format->lf_date_time.dts_base_time, dts.set_base_time(format->lf_date_time.dts_base_time,
format->lf_date_time.dts_base_tm.et_tm); format->lf_date_time.dts_base_tm.et_tm);
dts.dts_zoned_to_local = format->lf_date_time.dts_zoned_to_local;
if (format->lf_date_time.scan(time_src, if (format->lf_date_time.scan(time_src,
time_range.length(), time_range.length(),
format->get_timestamp_formats(), format->get_timestamp_formats(),
&tm, &tm,
actual_tv, actual_tv)
false) || dts.scan(time_src, time_range.length(), nullptr, &tm, actual_tv))
|| dts.scan(
time_src, time_range.length(), nullptr, &tm, actual_tv, false))
{ {
sql_strftime( sql_strftime(
orig_timestamp, sizeof(orig_timestamp), actual_tv, 'T'); orig_timestamp, sizeof(orig_timestamp), actual_tv, 'T');

@ -12,6 +12,7 @@
"pattern": "^(?<timestamp>(?:\\S{3,8}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z))\\s+(?<level>\\w+)\\((?<syslog_pri>\\d+)\\)(?:\\[\\+\\]|\\+)? (?:vmkernel|vmkwarning):\\s* (?:cpu(?<cpu>\\d+):(?<world_id>\\d+)(?: opID=(?<opid>[^\\)]+))?\\))?((?:(?:WARNING|ALERT)|(?<subsystem>[^:]+)): )?(?<body>.*)" "pattern": "^(?<timestamp>(?:\\S{3,8}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z))\\s+(?<level>\\w+)\\((?<syslog_pri>\\d+)\\)(?:\\[\\+\\]|\\+)? (?:vmkernel|vmkwarning):\\s* (?:cpu(?<cpu>\\d+):(?<world_id>\\d+)(?: opID=(?<opid>[^\\)]+))?\\))?((?:(?:WARNING|ALERT)|(?<subsystem>[^:]+)): )?(?<body>.*)"
} }
}, },
"ordered-by-time": false,
"level-field": "level", "level-field": "level",
"level": { "level": {
"debug": "^Db$", "debug": "^Db$",

@ -376,36 +376,43 @@ handle_paging_key(int ch)
break; break;
case 'J': case 'J':
if (lnav_data.ld_last_user_mark.find(tc) if (tc->is_selectable()) {
== lnav_data.ld_last_user_mark.end() tc->toggle_user_mark(&textview_curses::BM_USER,
|| !tc->is_line_visible( tc->get_selection());
vis_line_t(lnav_data.ld_last_user_mark[tc])))
{
lnav_data.ld_select_start[tc] = tc->get_selection(); lnav_data.ld_select_start[tc] = tc->get_selection();
lnav_data.ld_last_user_mark[tc] = tc->get_selection(); lnav_data.ld_last_user_mark[tc] = tc->get_selection();
} else { if (tc->get_selection() + 1_vl < tc->get_inner_height()) {
vis_line_t height; tc->set_selection(tc->get_selection() + 1_vl);
unsigned long width;
tc->get_dimensions(height, width);
if (lnav_data.ld_last_user_mark[tc] > (tc->get_bottom() - 2)
&& tc->get_selection() + height < tc->get_inner_height())
{
tc->shift_top(1_vl);
} }
if (lnav_data.ld_last_user_mark[tc] + 1 } else {
>= tc->get_inner_height()) if (lnav_data.ld_last_user_mark.find(tc)
== lnav_data.ld_last_user_mark.end()
|| !tc->is_line_visible(
vis_line_t(lnav_data.ld_last_user_mark[tc])))
{ {
break; lnav_data.ld_select_start[tc] = tc->get_selection();
lnav_data.ld_last_user_mark[tc] = tc->get_selection();
} else {
vis_line_t height;
unsigned long width;
tc->get_dimensions(height, width);
if (lnav_data.ld_last_user_mark[tc] > (tc->get_bottom() - 2)
&& tc->get_selection() + height
< tc->get_inner_height())
{
tc->shift_top(1_vl);
}
if (lnav_data.ld_last_user_mark[tc] + 1
>= tc->get_inner_height())
{
break;
}
lnav_data.ld_last_user_mark[tc] += 1;
} }
lnav_data.ld_last_user_mark[tc] += 1; tc->toggle_user_mark(
} &textview_curses::BM_USER,
tc->toggle_user_mark(&textview_curses::BM_USER, vis_line_t(lnav_data.ld_last_user_mark[tc]));
vis_line_t(lnav_data.ld_last_user_mark[tc]));
if (tc->is_selectable()
&& tc->get_selection() + 1_vl < tc->get_inner_height())
{
tc->set_selection(tc->get_selection() + 1_vl);
} }
tc->reload_data(); tc->reload_data();

@ -721,7 +721,9 @@ make it easier to navigate through files quickly.
.append(lnav::roles::file(lnav::paths::workdir().string())) .append(lnav::roles::file(lnav::paths::workdir().string()))
.append("\n\n") .append("\n\n")
.append("Documentation"_h1) .append("Documentation"_h1)
.append(": https://docs.lnav.org\n") .append(": ")
.append("https://docs.lnav.org"_hyperlink)
.append("\n")
.append("Contact"_h1) .append("Contact"_h1)
.append("\n") .append("\n")
.append(" ") .append(" ")
@ -739,7 +741,7 @@ make it easier to navigate through files quickly.
static void static void
clear_last_user_mark(listview_curses* lv) clear_last_user_mark(listview_curses* lv)
{ {
textview_curses* tc = (textview_curses*) lv; auto* tc = (textview_curses*) lv;
if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end() if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end()
&& !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) && !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc])))
{ {

@ -353,7 +353,7 @@ rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
} }
} }
lnav_data.ld_view_stack.top() | [&closed_files](auto tc) { lnav_data.ld_view_stack.top() | [&closed_files, &retval](auto tc) {
if (!closed_files.empty() && tc == &lnav_data.ld_views[LNV_GANTT]) { if (!closed_files.empty() && tc == &lnav_data.ld_views[LNV_GANTT]) {
auto* gantt_source = lnav_data.ld_views[LNV_GANTT].get_sub_source(); auto* gantt_source = lnav_data.ld_views[LNV_GANTT].get_sub_source();
if (gantt_source != nullptr) { if (gantt_source != nullptr) {
@ -361,9 +361,11 @@ rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
} }
} }
auto* tss = tc->get_sub_source(); if (retval > 0) {
lnav_data.ld_filter_status_source.update_filtered(tss); auto* tss = tc->get_sub_source();
lnav_data.ld_scroll_broadcaster(tc); lnav_data.ld_filter_status_source.update_filtered(tss);
lnav_data.ld_scroll_broadcaster(tc);
}
}; };
return retval; return retval;

@ -1287,16 +1287,41 @@ external_log_format::scan(logfile& lf,
{ {
this->lf_date_time.relock(ls); this->lf_date_time.relock(ls);
continue; continue;
} else {
log_debug("%s:%d:date-time re-locked to %d",
lf.get_unique_path().c_str(),
dst.size(),
this->lf_date_time.dts_fmt_lock);
} }
if (last != nullptr) {
auto old_flags = this->lf_timestamp_flags & DATE_TIME_SET_FLAGS;
auto new_flags = log_time_tm.et_flags & DATE_TIME_SET_FLAGS;
// It is unlikely a valid timestamp would lose much
// precision.
if (new_flags != old_flags) {
continue;
}
}
log_debug("%s:%d:date-time re-locked to %d",
lf.get_unique_path().c_str(),
dst.size(),
this->lf_date_time.dts_fmt_lock);
} }
this->lf_timestamp_flags = log_time_tm.et_flags; this->lf_timestamp_flags = log_time_tm.et_flags;
if (!(this->lf_timestamp_flags
& (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
&& !dst.empty() && dst.back().get_time() == log_tv.tv_sec
&& dst.back().get_millis() != 0)
{
auto log_ms = std::chrono::milliseconds(dst.back().get_millis());
log_time_tm.et_nsec
= std::chrono::duration_cast<std::chrono::nanoseconds>(log_ms)
.count();
log_tv.tv_usec
= std::chrono::duration_cast<std::chrono::microseconds>(log_ms)
.count();
}
if (!((log_time_tm.et_flags & ETF_DAY_SET) if (!((log_time_tm.et_flags & ETF_DAY_SET)
&& (log_time_tm.et_flags & ETF_MONTH_SET) && (log_time_tm.et_flags & ETF_MONTH_SET)
&& (log_time_tm.et_flags & ETF_YEAR_SET))) && (log_time_tm.et_flags & ETF_YEAR_SET)))

@ -130,6 +130,24 @@ class generic_log_format : public log_format {
this->check_for_new_year(dst, log_time, log_tv); this->check_for_new_year(dst, log_time, log_tv);
} }
if (!(this->lf_timestamp_flags
& (ETF_MILLIS_SET | ETF_MICROS_SET | ETF_NANOS_SET))
&& !dst.empty() && dst.back().get_time() == log_tv.tv_sec
&& dst.back().get_millis() != 0)
{
auto log_ms
= std::chrono::milliseconds(dst.back().get_millis());
log_time.et_nsec
= std::chrono::duration_cast<std::chrono::nanoseconds>(
log_ms)
.count();
log_tv.tv_usec
= std::chrono::duration_cast<std::chrono::microseconds>(
log_ms)
.count();
}
dst.emplace_back(li.li_file_range.fr_offset, log_tv, level_val); dst.emplace_back(li.li_file_range.fr_offset, log_tv, level_val);
return scan_match{0}; return scan_match{0};
} }

@ -42,6 +42,7 @@
#include "view_curses.hh" #include "view_curses.hh"
using namespace lnav::roles::literals; using namespace lnav::roles::literals;
using namespace md4cpp::literals;
static const std::map<string_fragment, text_format_t> CODE_NAME_TO_TEXT_FORMAT static const std::map<string_fragment, text_format_t> CODE_NAME_TO_TEXT_FORMAT
= { = {
@ -462,6 +463,8 @@ md2attr_line::enter_span(const md4cpp::event_handler::span& sp)
if (sp.is<span_code>()) { if (sp.is<span_code>()) {
last_block.append(" "); last_block.append(" ");
this->ml_code_depth += 1; this->ml_code_depth += 1;
} else if (sp.is<MD_SPAN_IMG_DETAIL*>()) {
last_block.append(":framed_picture:"_emoji).append(" ");
} }
return Ok(); return Ok();
} }
@ -517,7 +520,14 @@ md2attr_line::leave_span(const md4cpp::event_handler::span& sp)
} else if (sp.is<MD_SPAN_A_DETAIL*>()) { } else if (sp.is<MD_SPAN_A_DETAIL*>()) {
auto* a_detail = sp.get<MD_SPAN_A_DETAIL*>(); auto* a_detail = sp.get<MD_SPAN_A_DETAIL*>();
auto href_str = std::string(a_detail->href.text, a_detail->href.size); auto href_str = std::string(a_detail->href.text, a_detail->href.size);
line_range lr{
static_cast<int>(this->ml_span_starts.back()),
static_cast<int>(last_block.length()),
};
last_block.with_attr({
lr,
VC_HYPERLINK.value(href_str),
});
this->append_url_footnote(href_str); this->append_url_footnote(href_str);
} else if (sp.is<MD_SPAN_IMG_DETAIL*>()) { } else if (sp.is<MD_SPAN_IMG_DETAIL*>()) {
auto* img_detail = sp.get<MD_SPAN_IMG_DETAIL*>(); auto* img_detail = sp.get<MD_SPAN_IMG_DETAIL*>();
@ -617,9 +627,10 @@ span_style_border(border_side side, const string_fragment& value)
return attr_line_t(ch).with_attr_for_all(VC_STYLE.value(border_attrs)); return attr_line_t(ch).with_attr_for_all(VC_STYLE.value(border_attrs));
} }
static attr_line_t attr_line_t
to_attr_line(const pugi::xml_node& doc) md2attr_line::to_attr_line(const pugi::xml_node& doc)
{ {
static const auto NAME_IMG = string_fragment::from_const("img");
static const auto NAME_SPAN = string_fragment::from_const("span"); static const auto NAME_SPAN = string_fragment::from_const("span");
static const auto NAME_PRE = string_fragment::from_const("pre"); static const auto NAME_PRE = string_fragment::from_const("pre");
static const auto NAME_FG = string_fragment::from_const("color"); static const auto NAME_FG = string_fragment::from_const("color");
@ -639,7 +650,62 @@ to_attr_line(const pugi::xml_node& doc)
retval.append(doc.text().get()); retval.append(doc.text().get());
} }
for (const auto& child : doc.children()) { for (const auto& child : doc.children()) {
if (child.name() == NAME_SPAN) { if (child.name() == NAME_IMG) {
nonstd::optional<std::string> src_href;
std::string link_label;
auto img_src = child.attribute("src");
auto img_alt = child.attribute("alt");
if (img_alt) {
link_label = img_alt.value();
} else if (img_src) {
link_label = ghc::filesystem::path(img_src.value())
.filename()
.string();
} else {
link_label = "img";
}
if (img_src) {
auto src_value = std::string(img_src.value());
if (is_url(src_value)) {
src_href = src_value;
} else {
auto src_path = ghc::filesystem::path(src_value);
std::error_code ec;
if (src_path.is_relative() && this->ml_source_path) {
src_path = this->ml_source_path.value().parent_path()
/ src_path;
}
auto canon_path = ghc::filesystem::canonical(src_path, ec);
if (!ec) {
src_path = canon_path;
}
src_href = fmt::format(FMT_STRING("file://{}"),
src_path.string());
}
}
if (src_href) {
retval.append(":framed_picture:"_emoji)
.append(" ")
.append(
lnav::string::attrs::href(link_label, src_href.value()))
.appendf(FMT_STRING("[{}]"), this->ml_footnotes.size() + 1);
auto href
= attr_line_t()
.append(lnav::roles::hyperlink(src_href.value()))
.append(" ");
href.with_attr_for_all(
VC_ROLE.value(role_t::VCR_FOOTNOTE_TEXT));
href.with_attr_for_all(SA_PREFORMATTED.value());
this->ml_footnotes.emplace_back(href);
} else {
retval.append(link_label);
}
} else if (child.name() == NAME_SPAN) {
nonstd::optional<attr_line_t> left_border; nonstd::optional<attr_line_t> left_border;
nonstd::optional<attr_line_t> right_border; nonstd::optional<attr_line_t> right_border;
auto styled_span = attr_line_t(child.text().get()); auto styled_span = attr_line_t(child.text().get());
@ -738,7 +804,7 @@ to_attr_line(const pugi::xml_node& doc)
auto pre_al = attr_line_t(); auto pre_al = attr_line_t();
for (const auto& sub : child.children()) { for (const auto& sub : child.children()) {
auto child_al = to_attr_line(sub); auto child_al = this->to_attr_line(sub);
if (pre_al.empty() && startswith(child_al.get_string(), "\n")) { if (pre_al.empty() && startswith(child_al.get_string(), "\n")) {
child_al.erase(0, 1); child_al.erase(0, 1);
} }
@ -781,6 +847,7 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
break; break;
} }
case MD_TEXT_HTML: { case MD_TEXT_HTML: {
auto last_block_start_length = last_block.length();
last_block.append(sf); last_block.append(sf);
struct open_tag { struct open_tag {
@ -789,8 +856,9 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
struct close_tag { struct close_tag {
std::string ct_name; std::string ct_name;
}; };
struct empty_tag {};
mapbox::util::variant<open_tag, close_tag> tag{ mapbox::util::variant<open_tag, close_tag, empty_tag> tag{
mapbox::util::no_init{}}; mapbox::util::no_init{}};
if (sf.startswith("</")) { if (sf.startswith("</")) {
@ -800,22 +868,26 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
.first.to_string(), .first.to_string(),
}; };
} else if (sf.startswith("<")) { } else if (sf.startswith("<")) {
tag = open_tag{ if (sf.endswith("/>")) {
sf.substr(1) tag = empty_tag{};
.split_when( } else {
[](char ch) { return ch == ' ' || ch == '>'; }) tag = open_tag{
.first.to_string(), sf.substr(1)
}; .split_when(
[](char ch) { return ch == ' ' || ch == '>'; })
.first.to_string(),
};
}
} }
if (tag.valid()) { if (tag.valid()) {
tag.match( tag.match(
[this, &sf, &last_block](const open_tag& ot) { [this, last_block_start_length](const open_tag& ot) {
if (!this->ml_html_starts.empty()) { if (!this->ml_html_starts.empty()) {
return; return;
} }
this->ml_html_starts.emplace_back( this->ml_html_starts.emplace_back(
ot.ot_name, last_block.length() - sf.length()); ot.ot_name, last_block_start_length);
}, },
[this, &last_block](const close_tag& ct) { [this, &last_block](const close_tag& ct) {
if (this->ml_html_starts.empty()) { if (this->ml_html_starts.empty()) {
@ -845,9 +917,31 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
} else { } else {
last_block.erase( last_block.erase(
this->ml_html_starts.back().second); this->ml_html_starts.back().second);
last_block.append(to_attr_line(doc)); last_block.append(this->to_attr_line(doc));
} }
this->ml_html_starts.pop_back(); this->ml_html_starts.pop_back();
},
[this, &sf, &last_block, last_block_start_length](
const empty_tag&) {
const auto html_span = sf.to_string();
pugi::xml_document doc;
auto load_res = doc.load_string(html_span.c_str());
if (!load_res) {
log_error("XML parsing failure at %d: %s",
load_res.offset,
load_res.description());
auto error_line = sf.find_boundaries_around(
load_res.offset, string_fragment::tag1{'\n'});
log_error(" %.*s",
error_line.length(),
error_line.data());
} else {
last_block.erase(last_block_start_length);
last_block.append(this->to_attr_line(doc));
}
}); });
} }
break; break;

@ -34,6 +34,10 @@
#include "ghc/filesystem.hpp" #include "ghc/filesystem.hpp"
#include "md4cpp.hh" #include "md4cpp.hh"
namespace pugi {
class xml_node;
}
class md2attr_line : public md4cpp::typed_event_handler<attr_line_t> { class md2attr_line : public md4cpp::typed_event_handler<attr_line_t> {
public: public:
md2attr_line() { this->ml_blocks.resize(1); } md2attr_line() { this->ml_blocks.resize(1); }
@ -77,6 +81,7 @@ private:
void append_url_footnote(std::string href); void append_url_footnote(std::string href);
void flush_footnotes(); void flush_footnotes();
attr_line_t to_attr_line(const pugi::xml_node& doc);
nonstd::optional<ghc::filesystem::path> ml_source_path; nonstd::optional<ghc::filesystem::path> ml_source_path;
std::vector<attr_line_t> ml_blocks; std::vector<attr_line_t> ml_blocks;

@ -123,9 +123,9 @@ struct utf_to_display_adjustment {
int uda_offset; int uda_offset;
utf_to_display_adjustment(int utf_origin, int offset) utf_to_display_adjustment(int utf_origin, int offset)
: uda_origin(utf_origin), uda_offset(offset){ : uda_origin(utf_origin), uda_offset(offset)
{
}; }
}; };
void void
@ -185,6 +185,12 @@ view_curses::mvwattrline(WINDOW* window,
break; break;
} }
case '\x1b':
expanded_line.append("\u238b");
utf_adjustments.emplace_back(lpc, -1);
char_index += 1;
break;
case '\r': case '\r':
case '\n': case '\n':
expanded_line.push_back(' '); expanded_line.push_back(' ');

@ -424,7 +424,7 @@ can always use q to pop the top view off of the stack.
CTRL+], ESCAPE Abort command-line entry started with / , : , ; CTRL+], ESCAPE Abort command-line entry started with / , : , ;
, or | . , or | .
Note: The regular expression format used by lnav is PCRE[1] Note: The regular expression format used by lnav is ]8;;http://perldoc.perl.org/perlre.html\PCRE]8;;\[1]
▌(Perl-Compatible Regular Expressions). ▌(Perl-Compatible Regular Expressions).
▌ ▌[1] - http://perldoc.perl.org/perlre.html ▌ ▌[1] - http://perldoc.perl.org/perlre.html

@ -1,3 +1,3 @@
2600-12-03 09:23:00 0: 2600-01-03 09:23:00 0:
2600-12-03 09:23:00 0: 00:2 0 00:00:00 0:
2600-12-03 09:23:00 0: 2600-01-03 09:23:00 0:

@ -11,7 +11,7 @@ Run ./autogen.sh if compiling from a cloned repository.
See Also See Also
Angle-grinder[1] is a tool to slice and dice log files on the ]8;;https://github.com/rcoh/angle-grinder\Angle-grinder]8;;\[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language, command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with. you might find this tool more comfortable to work with.

@ -1,4 +1,4 @@
Build[1][2] Docs[3][4] Coverage Status[5][6] lnav[7][8] ]8;;https://github.com/tstack/lnav/actions?query=workflow%3Aci-build\🖼 Build[1]]8;;\[2] ]8;;https://docs.lnav.org\🖼 Docs[3]]8;;\[4] ]8;;https://coveralls.io/github/tstack/lnav?branch=master\🖼 Coverage Status[5]]8;;\[6] ]8;;https://snapcraft.io/lnav\🖼 lnav[7]]8;;\[8]
▌[1] - https://github.com/tstack/lnav/workflows/ci-build/badge.svg ▌[1] - https://github.com/tstack/lnav/workflows/ci-build/badge.svg
▌[2] - https://github.com/tstack/lnav/actions?query=workflow%3Aci-build ▌[2] - https://github.com/tstack/lnav/actions?query=workflow%3Aci-build
@ -9,13 +9,12 @@
▌[7] - https://snapcraft.io/lnav/badge.svg ▌[7] - https://snapcraft.io/lnav/badge.svg
▌[8] - https://snapcraft.io/lnav ▌[8] - https://snapcraft.io/lnav
<img ]8;;https://discord.gg/erBPnKwz7R\🖼 ]8;;\]8;;https://discord.gg/erBPnKwz7R\Discord Logo]8;;\]8;;https://discord.gg/erBPnKwz7R\[1]]8;;\[2]
src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg"
height="20"/>[1]
▌[1] - https://discord.gg/erBPnKwz7R ▌[1] - https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg
▌[2] - https://discord.gg/erBPnKwz7R
This is the source repository for lnav, visit https://lnav.org[1] for This is the source repository for lnav, visit ]8;;https://lnav.org\https://lnav.org]8;;\[1] for
a high level overview. a high level overview.
▌[1] - https://lnav.org ▌[1] - https://lnav.org
@ -32,7 +31,7 @@ to no setup.
The following screenshot shows a syslog file. Log lines are displayed The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow. with highlights. Errors are red and warnings are yellow.
Screenshot[1][2] ]8;;docs/assets/images/lnav-syslog.png\🖼 Screenshot[1]]8;;\[2]
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png ▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png ▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
@ -49,8 +48,8 @@ with highlights. Errors are red and warnings are yellow.
Installation Installation
Download a statically-linked binary for Linux/MacOS from the release ]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\Download a statically-linked binary for Linux/MacOS from the release]8;;\
page[1] ]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\page]8;;\[1]
▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts ▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts
@ -111,9 +110,9 @@ log lines fed into lnav via journalctl 's -b option.
Please file issues on this repository or use the discussions section. Please file issues on this repository or use the discussions section.
The following alternatives are also available: The following alternatives are also available:
• support@lnav.org[1] • ]8;;mailto:support@lnav.org\support@lnav.org]8;;\[1]
• Discord[2] • ]8;;https://discord.gg/erBPnKwz7R\Discord]8;;\[2]
• Google Groups[3] • ]8;;https://groups.google.com/g/lnav\Google Groups]8;;\[3]
▌[1] - mailto:support@lnav.org ▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R ▌[2] - https://discord.gg/erBPnKwz7R
@ -121,9 +120,9 @@ The following alternatives are also available:
Links Links
• Main Site[1] • ]8;;https://lnav.org\Main Site]8;;\[1]
• Documentation[2] on Read the Docs • ]8;;https://docs.lnav.org\Documentation]8;;\[2] on Read the Docs
• Internal Architecture[3] • ]8;;ARCHITECTURE.md\Internal Architecture]8;;\[3]
▌[1] - https://lnav.org ▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org ▌[2] - https://docs.lnav.org
@ -131,7 +130,7 @@ The following alternatives are also available:
Contributing Contributing
• Become a Sponsor on GitHub[1] • ]8;;https://github.com/sponsors/tstack\Become a Sponsor on GitHub]8;;\[1]
▌[1] - https://github.com/sponsors/tstack ▌[1] - https://github.com/sponsors/tstack
@ -170,7 +169,7 @@ Run ./autogen.sh if compiling from a cloned repository.
See Also See Also
Angle-grinder[1] is a tool to slice and dice log files on the ]8;;https://github.com/rcoh/angle-grinder\Angle-grinder]8;;\[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language, command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with. you might find this tool more comfortable to work with.

@ -3,7 +3,7 @@
The following screenshot shows a syslog file. Log lines are displayed The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow. with highlights. Errors are red and warnings are yellow.
Screenshot[1][2] ]8;;docs/assets/images/lnav-syslog.png\🖼 Screenshot[1]]8;;\[2]
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png ▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png ▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
@ -20,8 +20,8 @@ with highlights. Errors are red and warnings are yellow.
Installation Installation
Download a statically-linked binary for Linux/MacOS from the release ]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\Download a statically-linked binary for Linux/MacOS from the release]8;;\
page[1] ]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\page]8;;\[1]
▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts ▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts
@ -82,9 +82,9 @@ log lines fed into lnav via journalctl 's -b option.
Please file issues on this repository or use the discussions section. Please file issues on this repository or use the discussions section.
The following alternatives are also available: The following alternatives are also available:
• support@lnav.org[1] • ]8;;mailto:support@lnav.org\support@lnav.org]8;;\[1]
• Discord[2] • ]8;;https://discord.gg/erBPnKwz7R\Discord]8;;\[2]
• Google Groups[3] • ]8;;https://groups.google.com/g/lnav\Google Groups]8;;\[3]
▌[1] - mailto:support@lnav.org ▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R ▌[2] - https://discord.gg/erBPnKwz7R
@ -92,9 +92,9 @@ The following alternatives are also available:
Links Links
• Main Site[1] • ]8;;https://lnav.org\Main Site]8;;\[1]
• Documentation[2] on Read the Docs • ]8;;https://docs.lnav.org\Documentation]8;;\[2] on Read the Docs
• Internal Architecture[3] • ]8;;ARCHITECTURE.md\Internal Architecture]8;;\[3]
▌[1] - https://lnav.org ▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org ▌[2] - https://docs.lnav.org
@ -102,7 +102,7 @@ The following alternatives are also available:
Contributing Contributing
• Become a Sponsor on GitHub[1] • ]8;;https://github.com/sponsors/tstack\Become a Sponsor on GitHub]8;;\[1]
▌[1] - https://github.com/sponsors/tstack ▌[1] - https://github.com/sponsors/tstack
@ -141,7 +141,7 @@ Run ./autogen.sh if compiling from a cloned repository.
See Also See Also
Angle-grinder[1] is a tool to slice and dice log files on the ]8;;https://github.com/rcoh/angle-grinder\Angle-grinder]8;;\[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language, command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with. you might find this tool more comfortable to work with.

@ -5,6 +5,13 @@
• Two • Two
• Three • Three
🖼 ]8;;file://{top_srcdir}/docs/lnav-tui.png\lnav-tui.png]8;;\[1]
🖼 ]8;;file://{top_srcdir}/docs/lnav-architecture.png\The internal architecture of lnav]8;;\[2]
▌[1] - file://{top_srcdir}/docs/lnav-tui.png
▌[2] - file://{top_srcdir}/docs/lnav-architecture.png
Bold red Bold red
Underline Underline

@ -3,7 +3,7 @@
The following screenshot shows a syslog file. Log lines are displayed The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow. with highlights. Errors are red and warnings are yellow.
Screenshot[1][2] ]8;;docs/assets/images/lnav-syslog.png\🖼 Screenshot[1]]8;;\[2]
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png ▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png ▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
@ -20,8 +20,8 @@ with highlights. Errors are red and warnings are yellow.
Installation Installation
Download a statically-linked binary for Linux/MacOS from the release ]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\Download a statically-linked binary for Linux/MacOS from the release]8;;\
page[1] ]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\page]8;;\[1]
▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts ▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts
@ -82,9 +82,9 @@ log lines fed into lnav via journalctl 's -b option.
Please file issues on this repository or use the discussions section. Please file issues on this repository or use the discussions section.
The following alternatives are also available: The following alternatives are also available:
• support@lnav.org[1] • ]8;;mailto:support@lnav.org\support@lnav.org]8;;\[1]
• Discord[2] • ]8;;https://discord.gg/erBPnKwz7R\Discord]8;;\[2]
• Google Groups[3] • ]8;;https://groups.google.com/g/lnav\Google Groups]8;;\[3]
▌[1] - mailto:support@lnav.org ▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R ▌[2] - https://discord.gg/erBPnKwz7R
@ -92,9 +92,9 @@ The following alternatives are also available:
Links Links
• Main Site[1] • ]8;;https://lnav.org\Main Site]8;;\[1]
• Documentation[2] on Read the Docs • ]8;;https://docs.lnav.org\Documentation]8;;\[2] on Read the Docs
• Internal Architecture[3] • ]8;;ARCHITECTURE.md\Internal Architecture]8;;\[3]
▌[1] - https://lnav.org ▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org ▌[2] - https://docs.lnav.org
@ -102,7 +102,7 @@ The following alternatives are also available:
Contributing Contributing
• Become a Sponsor on GitHub[1] • ]8;;https://github.com/sponsors/tstack\Become a Sponsor on GitHub]8;;\[1]
▌[1] - https://github.com/sponsors/tstack ▌[1] - https://github.com/sponsors/tstack
@ -141,7 +141,7 @@ Run ./autogen.sh if compiling from a cloned repository.
See Also See Also
Angle-grinder[1] is a tool to slice and dice log files on the ]8;;https://github.com/rcoh/angle-grinder\Angle-grinder]8;;\[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language, command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with. you might find this tool more comfortable to work with.

@ -90,6 +90,27 @@ main(int argc, char* argv[])
} }
} }
{
auto hlink = std::string(
"\033]8;;http://example.com\033\\This is a "
"link\033]8;;\033\\\n");
string_attrs_t sa;
scrub_ansi_string(hlink, &sa);
printf("hlink %d %d %s", hlink.size(), sa.size(), hlink.c_str());
assert(sa.size() == 4);
for (const auto& attr : sa) {
printf("attr %d:%d %s\n",
attr.sa_range.lr_start,
attr.sa_range.lr_end,
attr.sa_type->sat_name);
if (attr.sa_type == &VC_HYPERLINK) {
printf(" value: %s\n",
attr.sa_value.get<std::string>().c_str());
}
}
}
string_attrs_t sa; string_attrs_t sa;
string str_cp; string str_cp;

@ -8,6 +8,10 @@
* Two * Two
* Three * Three
<img src="../docs/lnav-tui.png" />
<img src="../docs/lnav-architecture.png" alt="The internal architecture of lnav" />
<span style="color: #f00; font-weight: bold">Bold red</span> <span style="color: #f00; font-weight: bold">Bold red</span>
<span style="text-decoration: underline; background-color: darkblue">Underline</span> <span style="text-decoration: underline; background-color: darkblue">Underline</span>

Loading…
Cancel
Save