diff --git a/NEWS.md b/NEWS.md index 43b59a56..665c1724 100644 --- a/NEWS.md +++ b/NEWS.md @@ -63,7 +63,8 @@ Features: variables are now defined inside **lnav** and refer to the location of the user's configuration directory and the directory where cached data is stored, respectively. -* The `
` tag is now recognized in Markdown files.
+* The `` and `` tags are now recognized in
+ Markdown files.
* The `style` attribute in `` tags is now supported.
The following CSS properties and values are supported:
* `color` and `background-color` with CSS color names
diff --git a/README.md b/README.md
index 5f1185a1..2138d07d 100644
--- a/README.md
+++ b/README.md
@@ -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)
[![lnav](https://snapcraft.io/lnav/badge.svg)](https://snapcraft.io/lnav)
-[](https://discord.gg/erBPnKwz7R)
+[](https://discord.gg/erBPnKwz7R)
_This is the source repository for **lnav**, visit [https://lnav.org](https://lnav.org) for a high level overview._
diff --git a/src/base/ansi_scrubber.cc b/src/base/ansi_scrubber.cc
index 5306618d..0a334c14 100644
--- a/src/base/ansi_scrubber.cc
+++ b/src/base/ansi_scrubber.cc
@@ -34,6 +34,7 @@
#include "ansi_scrubber.hh"
#include "ansi_vars.hh"
+#include "base/lnav_log.hh"
#include "base/opt_util.hh"
#include "config.h"
#include "pcrepp/pcre2pp.hh"
@@ -44,7 +45,7 @@ static const lnav::pcre2pp::code&
ansi_regex()
{
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;
}
@@ -124,6 +125,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
const auto& regex = ansi_regex();
int64_t origin_offset = 0;
int last_origin_offset_end = 0;
+ nonstd::optional href;
+ size_t href_start = 0;
replace(str.begin(), str.end(), '\0', ' ');
auto matcher = regex.capture_from(str).into(md);
@@ -239,134 +242,164 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
continue;
}
- if (!md[1]) {
- continue;
- }
-
- auto seq = md[1].value();
- auto terminator = md[2].value();
struct line_range lr;
text_attrs attrs;
bool has_attrs = false;
nonstd::optional role;
- size_t lpc;
-
- switch (terminator[0]) {
- case 'm':
- for (lpc = seq.sf_begin;
- lpc != std::string::npos && lpc < (size_t) seq.sf_end;)
- {
- auto ansi_code_res = scn::scan_value(
- 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 (md[3]) {
+ auto osc_id = scn::scan_value(md[3]->to_string_view());
+
+ if (osc_id) {
+ switch (osc_id.value()) {
+ case 8:
+ auto split_res
+ = md[4]->split_pair(string_fragment::tag1{';'});
+ if (split_res) {
+ // auto params = split_res->first;
+ auto uri = split_res->second;
+
+ if (href) {
+ if (sa != nullptr) {
+ sa->emplace_back(
+ line_range{(int) href_start,
+ (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) {
- attrs.ta_bg_color = ansi_code - 40;
+ break;
+ }
+ }
+ } 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(
+ 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) {
- 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;
+ lpc = str.find(';', lpc);
+ if (lpc != std::string::npos) {
+ lpc += 1;
}
}
- lpc = str.find(';', lpc);
- if (lpc != std::string::npos) {
- lpc += 1;
- }
- }
- has_attrs = true;
- break;
+ has_attrs = true;
+ break;
- case 'C': {
- auto spaces_res
- = scn::scan_value(seq.to_string_view());
+ case 'C': {
+ auto spaces_res
+ = scn::scan_value(seq.to_string_view());
- if (spaces_res && spaces_res.value() > 0) {
- str.insert((std::string::size_type) sf.sf_end,
- spaces_res.value(),
- ' ');
+ if (spaces_res && spaces_res.value() > 0) {
+ str.insert((std::string::size_type) sf.sf_end,
+ spaces_res.value(),
+ ' ');
+ }
+ break;
}
- break;
- }
- case 'H': {
- unsigned int row = 0, spaces = 0;
+ case 'H': {
+ unsigned int row = 0, spaces = 0;
- if (scn::scan(seq.to_string_view(), "{};{}", row, spaces)
- && spaces > 1)
- {
- int ispaces = spaces - 1;
- if (ispaces > sf.sf_begin) {
- str.insert((unsigned long) sf.sf_end,
- ispaces - sf.sf_begin,
- ' ');
+ if (scn::scan(seq.to_string_view(), "{};{}", row, spaces)
+ && spaces > 1)
+ {
+ int ispaces = spaces - 1;
+ if (ispaces > sf.sf_begin) {
+ str.insert((unsigned long) sf.sf_end,
+ ispaces - sf.sf_begin,
+ ' ');
+ }
}
+ break;
}
- break;
- }
- case 'O': {
- auto role_res = scn::scan_value(seq.to_string_view());
+ case 'O': {
+ auto role_res = scn::scan_value(seq.to_string_view());
- if (role_res) {
- role_t role_tmp = (role_t) role_res.value();
- if (role_tmp > role_t::VCR_NONE
- && role_tmp < role_t::VCR__MAX)
- {
- role = role_tmp;
- has_attrs = true;
+ if (role_res) {
+ role_t role_tmp = (role_t) role_res.value();
+ if (role_tmp > role_t::VCR_NONE
+ && role_tmp < role_t::VCR__MAX)
+ {
+ role = role_tmp;
+ has_attrs = true;
+ }
}
+ break;
}
- break;
}
}
- str.erase(str.begin() + sf.sf_begin, str.begin() + sf.sf_end);
- 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 (rit->sa_range.lr_end != -1) {
- continue;
+ if (md[1] || md[3]) {
+ str.erase(str.begin() + sf.sf_begin, str.begin() + sf.sf_end);
+ 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 (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;
- if (!attrs.empty()) {
- sa->emplace_back(lr, VC_STYLE.value(attrs));
+ lr.lr_start = sf.sf_begin;
+ lr.lr_end = -1;
+ 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(lr, VC_ROLE.value(r));
- };
+ 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();
}
- 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) {
diff --git a/src/base/lnav.console.cc b/src/base/lnav.console.cc
index d3a43069..2277ebae 100644
--- a/src/base/lnav.console.cc
+++ b/src/base/lnav.console.cc
@@ -312,6 +312,7 @@ println(FILE* file, const attr_line_t& al)
auto line_style = fmt::text_style{};
auto fg_style = fmt::text_style{};
auto start = last_point.value();
+ nonstd::optional href;
for (const auto& attr : al.get_attrs()) {
if (!attr.sa_range.contains(start)
@@ -321,7 +322,10 @@ println(FILE* file, const attr_line_t& al)
}
try {
- if (attr.sa_type == &VC_BACKGROUND) {
+ if (attr.sa_type == &VC_HYPERLINK) {
+ auto saw = string_attr_wrapper(&attr);
+ href = saw.get();
+ } else if (attr.sa_type == &VC_BACKGROUND) {
auto saw = string_attr_wrapper(&attr);
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;
}
+ if (href) {
+ fmt::print(file, FMT_STRING("\x1b]8;;{}\x1b\\"), href.value());
+ }
if (start < str.size()) {
auto actual_end = std::min(str.size(), static_cast(point));
fmt::print(file,
@@ -491,6 +498,9 @@ println(FILE* file, const attr_line_t& al)
FMT_STRING("{}"),
str.substr(start, actual_end - start));
}
+ if (href) {
+ fmt::print(file, FMT_STRING("\x1b]8;;\x1b\\"));
+ }
last_point = point;
}
fmt::print(file, "\n");
diff --git a/src/base/string_attr_type.cc b/src/base/string_attr_type.cc
index f7d7551b..2e1df8d2 100644
--- a/src/base/string_attr_type.cc
+++ b/src/base/string_attr_type.cc
@@ -50,3 +50,4 @@ string_attr_type VC_GRAPHIC("graphic");
string_attr_type VC_BLOCK_ELEM("block-elem");
string_attr_type VC_FOREGROUND("foreground");
string_attr_type VC_BACKGROUND("background");
+string_attr_type VC_HYPERLINK("hyperlink");
diff --git a/src/base/string_attr_type.hh b/src/base/string_attr_type.hh
index 7a027185..6a39424d 100644
--- a/src/base/string_attr_type.hh
+++ b/src/base/string_attr_type.hh
@@ -233,6 +233,7 @@ extern string_attr_type VC_GRAPHIC;
extern string_attr_type VC_BLOCK_ELEM;
extern string_attr_type VC_FOREGROUND;
extern string_attr_type VC_BACKGROUND;
+extern string_attr_type VC_HYPERLINK;
namespace lnav {
@@ -246,6 +247,14 @@ preformatted(S str)
return std::make_pair(std::move(str), SA_PREFORMATTED.template value());
}
+template
+inline std::pair
+href(S str, std::string href)
+{
+ return std::make_pair(std::move(str),
+ VC_HYPERLINK.template value(std::move(href)));
+}
+
} // namespace attrs
} // namespace string
@@ -699,6 +708,13 @@ inline std::pair operator"" _snippet_border(
VC_ROLE.template value(role_t::VCR_SNIPPET_BORDER));
}
+inline std::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 roles
diff --git a/src/field_overlay_source.cc b/src/field_overlay_source.cc
index 3cb7242d..9ea11374 100644
--- a/src/field_overlay_source.cc
+++ b/src/field_overlay_source.cc
@@ -134,7 +134,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv,
time_lr.lr_end = time_str.length();
time_line.with_attr(
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_str.append(humanize::time::point::from_tv(ll->get_timeval())
.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,
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,
time_range.length(),
format->get_timestamp_formats(),
&tm,
- actual_tv,
- false)
- || dts.scan(
- time_src, time_range.length(), nullptr, &tm, actual_tv, false))
+ actual_tv)
+ || dts.scan(time_src, time_range.length(), nullptr, &tm, actual_tv))
{
sql_strftime(
orig_timestamp, sizeof(orig_timestamp), actual_tv, 'T');
diff --git a/src/formats/vmk_log.json b/src/formats/vmk_log.json
index 1339dcf1..962575e0 100644
--- a/src/formats/vmk_log.json
+++ b/src/formats/vmk_log.json
@@ -12,6 +12,7 @@
"pattern": "^(?(?:\\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+(?\\w+)\\((?\\d+)\\)(?:\\[\\+\\]|\\+)? (?:vmkernel|vmkwarning):\\s* (?:cpu(?\\d+):(?\\d+)(?: opID=(?[^\\)]+))?\\))?((?:(?:WARNING|ALERT)|(?[^:]+)): )?(?.*)"
}
},
+ "ordered-by-time": false,
"level-field": "level",
"level": {
"debug": "^Db$",
diff --git a/src/hotkeys.cc b/src/hotkeys.cc
index 64994afe..c6960a4f 100644
--- a/src/hotkeys.cc
+++ b/src/hotkeys.cc
@@ -376,36 +376,43 @@ handle_paging_key(int ch)
break;
case 'J':
- 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])))
- {
+ if (tc->is_selectable()) {
+ tc->toggle_user_mark(&textview_curses::BM_USER,
+ tc->get_selection());
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 (tc->get_selection() + 1_vl < tc->get_inner_height()) {
+ tc->set_selection(tc->get_selection() + 1_vl);
}
- if (lnav_data.ld_last_user_mark[tc] + 1
- >= tc->get_inner_height())
+ } else {
+ 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,
- 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->toggle_user_mark(
+ &textview_curses::BM_USER,
+ vis_line_t(lnav_data.ld_last_user_mark[tc]));
}
tc->reload_data();
diff --git a/src/lnav.cc b/src/lnav.cc
index 418df896..be7f272e 100644
--- a/src/lnav.cc
+++ b/src/lnav.cc
@@ -721,7 +721,9 @@ make it easier to navigate through files quickly.
.append(lnav::roles::file(lnav::paths::workdir().string()))
.append("\n\n")
.append("Documentation"_h1)
- .append(": https://docs.lnav.org\n")
+ .append(": ")
+ .append("https://docs.lnav.org"_hyperlink)
+ .append("\n")
.append("Contact"_h1)
.append("\n")
.append(" ")
@@ -739,7 +741,7 @@ make it easier to navigate through files quickly.
static void
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()
&& !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc])))
{
diff --git a/src/lnav.indexing.cc b/src/lnav.indexing.cc
index d1b69b4e..1c3df91b 100644
--- a/src/lnav.indexing.cc
+++ b/src/lnav.indexing.cc
@@ -353,7 +353,7 @@ rebuild_indexes(nonstd::optional 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]) {
auto* gantt_source = lnav_data.ld_views[LNV_GANTT].get_sub_source();
if (gantt_source != nullptr) {
@@ -361,9 +361,11 @@ rebuild_indexes(nonstd::optional deadline)
}
}
- auto* tss = tc->get_sub_source();
- lnav_data.ld_filter_status_source.update_filtered(tss);
- lnav_data.ld_scroll_broadcaster(tc);
+ if (retval > 0) {
+ auto* tss = tc->get_sub_source();
+ lnav_data.ld_filter_status_source.update_filtered(tss);
+ lnav_data.ld_scroll_broadcaster(tc);
+ }
};
return retval;
diff --git a/src/log_format.cc b/src/log_format.cc
index 7f4945d2..d941dd3b 100644
--- a/src/log_format.cc
+++ b/src/log_format.cc
@@ -1287,16 +1287,41 @@ external_log_format::scan(logfile& lf,
{
this->lf_date_time.relock(ls);
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;
+ 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(log_ms)
+ .count();
+ log_tv.tv_usec
+ = std::chrono::duration_cast(log_ms)
+ .count();
+ }
+
if (!((log_time_tm.et_flags & ETF_DAY_SET)
&& (log_time_tm.et_flags & ETF_MONTH_SET)
&& (log_time_tm.et_flags & ETF_YEAR_SET)))
diff --git a/src/log_format_impls.cc b/src/log_format_impls.cc
index 2033373e..cff53278 100644
--- a/src/log_format_impls.cc
+++ b/src/log_format_impls.cc
@@ -130,6 +130,24 @@ class generic_log_format : public log_format {
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(
+ log_ms)
+ .count();
+ log_tv.tv_usec
+ = std::chrono::duration_cast(
+ log_ms)
+ .count();
+ }
+
dst.emplace_back(li.li_file_range.fr_offset, log_tv, level_val);
return scan_match{0};
}
diff --git a/src/md2attr_line.cc b/src/md2attr_line.cc
index 44393a91..314697e7 100644
--- a/src/md2attr_line.cc
+++ b/src/md2attr_line.cc
@@ -42,6 +42,7 @@
#include "view_curses.hh"
using namespace lnav::roles::literals;
+using namespace md4cpp::literals;
static const std::map CODE_NAME_TO_TEXT_FORMAT
= {
@@ -462,6 +463,8 @@ md2attr_line::enter_span(const md4cpp::event_handler::span& sp)
if (sp.is()) {
last_block.append(" ");
this->ml_code_depth += 1;
+ } else if (sp.is()) {
+ last_block.append(":framed_picture:"_emoji).append(" ");
}
return Ok();
}
@@ -517,7 +520,14 @@ md2attr_line::leave_span(const md4cpp::event_handler::span& sp)
} else if (sp.is()) {
auto* a_detail = sp.get();
auto href_str = std::string(a_detail->href.text, a_detail->href.size);
-
+ line_range lr{
+ static_cast(this->ml_span_starts.back()),
+ static_cast(last_block.length()),
+ };
+ last_block.with_attr({
+ lr,
+ VC_HYPERLINK.value(href_str),
+ });
this->append_url_footnote(href_str);
} else if (sp.is()) {
auto* img_detail = sp.get();
@@ -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));
}
-static attr_line_t
-to_attr_line(const pugi::xml_node& doc)
+attr_line_t
+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_PRE = string_fragment::from_const("pre");
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());
}
for (const auto& child : doc.children()) {
- if (child.name() == NAME_SPAN) {
+ if (child.name() == NAME_IMG) {
+ nonstd::optional 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 left_border;
nonstd::optional right_border;
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();
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")) {
child_al.erase(0, 1);
}
@@ -781,6 +847,7 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
break;
}
case MD_TEXT_HTML: {
+ auto last_block_start_length = last_block.length();
last_block.append(sf);
struct open_tag {
@@ -789,8 +856,9 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
struct close_tag {
std::string ct_name;
};
+ struct empty_tag {};
- mapbox::util::variant tag{
+ mapbox::util::variant tag{
mapbox::util::no_init{}};
if (sf.startswith("")) {
@@ -800,22 +868,26 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
.first.to_string(),
};
} else if (sf.startswith("<")) {
- tag = open_tag{
- sf.substr(1)
- .split_when(
- [](char ch) { return ch == ' ' || ch == '>'; })
- .first.to_string(),
- };
+ if (sf.endswith("/>")) {
+ tag = empty_tag{};
+ } else {
+ tag = open_tag{
+ sf.substr(1)
+ .split_when(
+ [](char ch) { return ch == ' ' || ch == '>'; })
+ .first.to_string(),
+ };
+ }
}
if (tag.valid()) {
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()) {
return;
}
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) {
if (this->ml_html_starts.empty()) {
@@ -845,9 +917,31 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
} else {
last_block.erase(
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, &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;
diff --git a/src/md2attr_line.hh b/src/md2attr_line.hh
index 6a2ef8e6..212b3ac2 100644
--- a/src/md2attr_line.hh
+++ b/src/md2attr_line.hh
@@ -34,6 +34,10 @@
#include "ghc/filesystem.hpp"
#include "md4cpp.hh"
+namespace pugi {
+class xml_node;
+}
+
class md2attr_line : public md4cpp::typed_event_handler {
public:
md2attr_line() { this->ml_blocks.resize(1); }
@@ -77,6 +81,7 @@ private:
void append_url_footnote(std::string href);
void flush_footnotes();
+ attr_line_t to_attr_line(const pugi::xml_node& doc);
nonstd::optional ml_source_path;
std::vector ml_blocks;
diff --git a/src/view_curses.cc b/src/view_curses.cc
index 0b2e9a83..c2be62ba 100644
--- a/src/view_curses.cc
+++ b/src/view_curses.cc
@@ -123,9 +123,9 @@ struct utf_to_display_adjustment {
int uda_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
@@ -185,6 +185,12 @@ view_curses::mvwattrline(WINDOW* window,
break;
}
+ case '\x1b':
+ expanded_line.append("\u238b");
+ utf_adjustments.emplace_back(lpc, -1);
+ char_index += 1;
+ break;
+
case '\r':
case '\n':
expanded_line.push_back(' ');
diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out
index 12076194..5a47da2b 100644
--- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out
+++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out
@@ -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 / , : , ;
, or | .
- ▌[1mNote[0m: The regular expression format used by lnav is [4mPCRE[1][0m
+ ▌[1mNote[0m: The regular expression format used by lnav is ]8;;http://perldoc.perl.org/perlre.html\[4mPCRE[0m]8;;\[4m[1][0m
▌(Perl-Compatible Regular Expressions).
▌
▌ ▌[1] - http://perldoc.perl.org/perlre.html
diff --git a/test/expected/test_logfile.sh_08d731a04c877a34819b35de185e30a74c9fd497.out b/test/expected/test_logfile.sh_08d731a04c877a34819b35de185e30a74c9fd497.out
index 0465ab4c..1d2ec4e8 100644
--- a/test/expected/test_logfile.sh_08d731a04c877a34819b35de185e30a74c9fd497.out
+++ b/test/expected/test_logfile.sh_08d731a04c877a34819b35de185e30a74c9fd497.out
@@ -1,3 +1,3 @@
-2600-12-03 09:23:00 0:
-2600-12-03 09:23:00 0:
-2600-12-03 09:23:00 0:
+2600-01-03 09:23:00 0:
+00:2 0 00:00:00 0:
+2600-01-03 09:23:00 0:
diff --git a/test/expected/test_sql_views_vtab.sh_32acc1a8bb5028636fdbf08f077f9a835ab51bec.out b/test/expected/test_sql_views_vtab.sh_32acc1a8bb5028636fdbf08f077f9a835ab51bec.out
index 1d4600e4..d25ba3ed 100644
--- a/test/expected/test_sql_views_vtab.sh_32acc1a8bb5028636fdbf08f077f9a835ab51bec.out
+++ b/test/expected/test_sql_views_vtab.sh_32acc1a8bb5028636fdbf08f077f9a835ab51bec.out
@@ -11,7 +11,7 @@ Run ./autogen.sh if compiling from a cloned repository.
[1mSee Also[0m
-[4mAngle-grinder[1][0m is a tool to slice and dice log files on the
+]8;;https://github.com/rcoh/angle-grinder\[4mAngle-grinder[0m]8;;\[4m[1][0m is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
diff --git a/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out b/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out
index d0f87f26..4eb96103 100644
--- a/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out
+++ b/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out
@@ -1,4 +1,4 @@
-[4mBuild[1][0m[4m[2][0m [4mDocs[3][0m[4m[4][0m [4mCoverage Status[5][0m[4m[6][0m [4mlnav[7][0m[4m[8][0m
+]8;;https://github.com/tstack/lnav/actions?query=workflow%3Aci-build\[4m🖼 Build[1][0m]8;;\[4m[2][0m ]8;;https://docs.lnav.org\[4m🖼 Docs[3][0m]8;;\[4m[4][0m ]8;;https://coveralls.io/github/tstack/lnav?branch=master\[4m🖼 Coverage Status[5][0m]8;;\[4m[6][0m ]8;;https://snapcraft.io/lnav\[4m🖼 lnav[7][0m]8;;\[4m[8][0m
▌[1] - https://github.com/tstack/lnav/workflows/ci-build/badge.svg
▌[2] - https://github.com/tstack/lnav/actions?query=workflow%3Aci-build
@@ -9,13 +9,12 @@
▌[7] - https://snapcraft.io/lnav/badge.svg
▌[8] - https://snapcraft.io/lnav
-[4m[1][0m
+]8;;https://discord.gg/erBPnKwz7R\[4m🖼 [0m]8;;\]8;;https://discord.gg/erBPnKwz7R\[4mDiscord Logo[0m]8;;\]8;;https://discord.gg/erBPnKwz7R\[4m[1][0m]8;;\[4m[2][0m
- ▌[1] - https://discord.gg/erBPnKwz7R
+ ▌[1] - https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg
+ ▌[2] - https://discord.gg/erBPnKwz7R
-[4mThis is the source repository for [0m[1m[4mlnav[0m[4m, visit [0m[4mhttps://lnav.org[1][0m[4m for[0m
+[4mThis is the source repository for [0m[1m[4mlnav[0m[4m, visit [0m]8;;https://lnav.org\[4mhttps://lnav.org[0m]8;;\[4m[1][0m[4m for[0m
[4ma high level overview.[0m
▌[1] - https://lnav.org
@@ -32,7 +31,7 @@ to no setup.
The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow.
-[4mScreenshot[1][0m[4m[2][0m
+]8;;docs/assets/images/lnav-syslog.png\[4m🖼 Screenshot[1][0m]8;;\[4m[2][0m
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
@@ -49,8 +48,8 @@ with highlights. Errors are red and warnings are yellow.
[1mInstallation[0m
-[4mDownload a statically-linked binary for Linux/MacOS from the release[0m
-[4mpage[1][0m
+]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\[4mDownload a statically-linked binary for Linux/MacOS from the release[0m]8;;\
+]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\[4mpage[0m]8;;\[4m[1][0m
▌[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.
The following alternatives are also available:
- [33m•[0m [4msupport@lnav.org[1][0m
- [33m•[0m [4mDiscord[2][0m
- [33m•[0m [4mGoogle Groups[3][0m
+ [33m•[0m ]8;;mailto:support@lnav.org\[4msupport@lnav.org[0m]8;;\[4m[1][0m
+ [33m•[0m ]8;;https://discord.gg/erBPnKwz7R\[4mDiscord[0m]8;;\[4m[2][0m
+ [33m•[0m ]8;;https://groups.google.com/g/lnav\[4mGoogle Groups[0m]8;;\[4m[3][0m
▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R
@@ -121,9 +120,9 @@ The following alternatives are also available:
[1mLinks[0m
- [33m•[0m [4mMain Site[1][0m
- [33m•[0m [1m[4mDocumentation[0m[4m[2][0m on Read the Docs
- [33m•[0m [4mInternal Architecture[3][0m
+ [33m•[0m ]8;;https://lnav.org\[4mMain Site[0m]8;;\[4m[1][0m
+ [33m•[0m ]8;;https://docs.lnav.org\[1m[4mDocumentation[0m]8;;\[4m[2][0m on Read the Docs
+ [33m•[0m ]8;;ARCHITECTURE.md\[4mInternal Architecture[0m]8;;\[4m[3][0m
▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org
@@ -131,7 +130,7 @@ The following alternatives are also available:
[1mContributing[0m
- [33m•[0m [4mBecome a Sponsor on GitHub[1][0m
+ [33m•[0m ]8;;https://github.com/sponsors/tstack\[4mBecome a Sponsor on GitHub[0m]8;;\[4m[1][0m
▌[1] - https://github.com/sponsors/tstack
@@ -170,7 +169,7 @@ Run ./autogen.sh if compiling from a cloned repository.
[1mSee Also[0m
-[4mAngle-grinder[1][0m is a tool to slice and dice log files on the
+]8;;https://github.com/rcoh/angle-grinder\[4mAngle-grinder[0m]8;;\[4m[1][0m is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
diff --git a/test/expected/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.out b/test/expected/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.out
index 9e9836d8..0057ec78 100644
--- a/test/expected/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.out
+++ b/test/expected/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.out
@@ -3,7 +3,7 @@
The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow.
-[4mScreenshot[1][0m[4m[2][0m
+]8;;docs/assets/images/lnav-syslog.png\[4m🖼 Screenshot[1][0m]8;;\[4m[2][0m
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
@@ -20,8 +20,8 @@ with highlights. Errors are red and warnings are yellow.
[1mInstallation[0m
-[4mDownload a statically-linked binary for Linux/MacOS from the release[0m
-[4mpage[1][0m
+]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\[4mDownload a statically-linked binary for Linux/MacOS from the release[0m]8;;\
+]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\[4mpage[0m]8;;\[4m[1][0m
▌[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.
The following alternatives are also available:
- [33m•[0m [4msupport@lnav.org[1][0m
- [33m•[0m [4mDiscord[2][0m
- [33m•[0m [4mGoogle Groups[3][0m
+ [33m•[0m ]8;;mailto:support@lnav.org\[4msupport@lnav.org[0m]8;;\[4m[1][0m
+ [33m•[0m ]8;;https://discord.gg/erBPnKwz7R\[4mDiscord[0m]8;;\[4m[2][0m
+ [33m•[0m ]8;;https://groups.google.com/g/lnav\[4mGoogle Groups[0m]8;;\[4m[3][0m
▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R
@@ -92,9 +92,9 @@ The following alternatives are also available:
[1mLinks[0m
- [33m•[0m [4mMain Site[1][0m
- [33m•[0m [1m[4mDocumentation[0m[4m[2][0m on Read the Docs
- [33m•[0m [4mInternal Architecture[3][0m
+ [33m•[0m ]8;;https://lnav.org\[4mMain Site[0m]8;;\[4m[1][0m
+ [33m•[0m ]8;;https://docs.lnav.org\[1m[4mDocumentation[0m]8;;\[4m[2][0m on Read the Docs
+ [33m•[0m ]8;;ARCHITECTURE.md\[4mInternal Architecture[0m]8;;\[4m[3][0m
▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org
@@ -102,7 +102,7 @@ The following alternatives are also available:
[1mContributing[0m
- [33m•[0m [4mBecome a Sponsor on GitHub[1][0m
+ [33m•[0m ]8;;https://github.com/sponsors/tstack\[4mBecome a Sponsor on GitHub[0m]8;;\[4m[1][0m
▌[1] - https://github.com/sponsors/tstack
@@ -141,7 +141,7 @@ Run ./autogen.sh if compiling from a cloned repository.
[1mSee Also[0m
-[4mAngle-grinder[1][0m is a tool to slice and dice log files on the
+]8;;https://github.com/rcoh/angle-grinder\[4mAngle-grinder[0m]8;;\[4m[1][0m is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
diff --git a/test/expected/test_text_file.sh_d59b67113864ef5e77267d7fd8ad4072f5aef0fc.out b/test/expected/test_text_file.sh_d59b67113864ef5e77267d7fd8ad4072f5aef0fc.out
index 39994981..30b1d02d 100644
--- a/test/expected/test_text_file.sh_d59b67113864ef5e77267d7fd8ad4072f5aef0fc.out
+++ b/test/expected/test_text_file.sh_d59b67113864ef5e77267d7fd8ad4072f5aef0fc.out
@@ -5,6 +5,13 @@
[33m•[0m Two
[33m•[0m 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
+
[1m[31mBold red[0m
[4m[44mUnderline[0m
diff --git a/test/expected/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.out b/test/expected/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.out
index 9e9836d8..0057ec78 100644
--- a/test/expected/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.out
+++ b/test/expected/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.out
@@ -3,7 +3,7 @@
The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow.
-[4mScreenshot[1][0m[4m[2][0m
+]8;;docs/assets/images/lnav-syslog.png\[4m🖼 Screenshot[1][0m]8;;\[4m[2][0m
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
@@ -20,8 +20,8 @@ with highlights. Errors are red and warnings are yellow.
[1mInstallation[0m
-[4mDownload a statically-linked binary for Linux/MacOS from the release[0m
-[4mpage[1][0m
+]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\[4mDownload a statically-linked binary for Linux/MacOS from the release[0m]8;;\
+]8;;https://github.com/tstack/lnav/releases/latest#release-artifacts\[4mpage[0m]8;;\[4m[1][0m
▌[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.
The following alternatives are also available:
- [33m•[0m [4msupport@lnav.org[1][0m
- [33m•[0m [4mDiscord[2][0m
- [33m•[0m [4mGoogle Groups[3][0m
+ [33m•[0m ]8;;mailto:support@lnav.org\[4msupport@lnav.org[0m]8;;\[4m[1][0m
+ [33m•[0m ]8;;https://discord.gg/erBPnKwz7R\[4mDiscord[0m]8;;\[4m[2][0m
+ [33m•[0m ]8;;https://groups.google.com/g/lnav\[4mGoogle Groups[0m]8;;\[4m[3][0m
▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R
@@ -92,9 +92,9 @@ The following alternatives are also available:
[1mLinks[0m
- [33m•[0m [4mMain Site[1][0m
- [33m•[0m [1m[4mDocumentation[0m[4m[2][0m on Read the Docs
- [33m•[0m [4mInternal Architecture[3][0m
+ [33m•[0m ]8;;https://lnav.org\[4mMain Site[0m]8;;\[4m[1][0m
+ [33m•[0m ]8;;https://docs.lnav.org\[1m[4mDocumentation[0m]8;;\[4m[2][0m on Read the Docs
+ [33m•[0m ]8;;ARCHITECTURE.md\[4mInternal Architecture[0m]8;;\[4m[3][0m
▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org
@@ -102,7 +102,7 @@ The following alternatives are also available:
[1mContributing[0m
- [33m•[0m [4mBecome a Sponsor on GitHub[1][0m
+ [33m•[0m ]8;;https://github.com/sponsors/tstack\[4mBecome a Sponsor on GitHub[0m]8;;\[4m[1][0m
▌[1] - https://github.com/sponsors/tstack
@@ -141,7 +141,7 @@ Run ./autogen.sh if compiling from a cloned repository.
[1mSee Also[0m
-[4mAngle-grinder[1][0m is a tool to slice and dice log files on the
+]8;;https://github.com/rcoh/angle-grinder\[4mAngle-grinder[0m]8;;\[4m[1][0m is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
diff --git a/test/test_ansi_scrubber.cc b/test/test_ansi_scrubber.cc
index 87969a3e..8aa21e95 100644
--- a/test/test_ansi_scrubber.cc
+++ b/test/test_ansi_scrubber.cc
@@ -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().c_str());
+ }
+ }
+ }
+
string_attrs_t sa;
string str_cp;
diff --git a/test/textfile_0.md b/test/textfile_0.md
index 4f652fff..7bda8e1a 100644
--- a/test/textfile_0.md
+++ b/test/textfile_0.md
@@ -8,6 +8,10 @@
* Two
* Three
+
+
+
+
Bold red
Underline