[markdown] some minor improvements

pull/1179/head
Tim Stack 10 months ago
parent b2bd2bf8c9
commit 4b9f81a65a

@ -44,6 +44,11 @@ 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 `<pre>` tag is now recognized in Markdown files.
* The `style` attribute in `<span>` tags is now supported.
Basic properties like `color`, `background-color`,
`font-weight`, and `text-decoration` can be used. CSS
color names should work as well.
Bug Fixes:
* Binary data piped into stdin should now be treated the same

@ -103,7 +103,7 @@ function(bin2c)
DEPENDS bin2c "${FILE_TO_LINK}")
endfunction(bin2c)
foreach (FILE_TO_LINK animals.json ansi-palette.json diseases.json emojis.json xml-entities.json xterm-palette.json help.txt help.md init.sql words.json)
foreach (FILE_TO_LINK animals.json ansi-palette.json css-color-names.json diseases.json emojis.json xml-entities.json xterm-palette.json help.txt help.md init.sql words.json)
string(REPLACE "." "-" DST_FILE "${FILE_TO_LINK}")
add_custom_command(
OUTPUT "${DST_FILE}.h" "${DST_FILE}.cc"
@ -196,7 +196,7 @@ set(BUILTIN_LNAV_SCRIPTS
scripts/partition-by-boot.lnav scripts/rename-stdin.lnav
scripts/search-for.lnav
scripts/workdir-url-handler.lnav
)
)
set(BUILTIN_LNAV_SCRIPT_PATHS ${BUILTIN_LNAV_SCRIPTS})
@ -328,7 +328,7 @@ add_library(lnavfileio STATIC
line_buffer.cc
pollable.cc
shared_buffer.cc
)
)
target_include_directories(lnavfileio PRIVATE . ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(lnavfileio cppfmt spookyhash pcrepp base BZip2::BZip2 ZLIB::ZLIB)
@ -612,7 +612,7 @@ target_include_directories(diag PUBLIC . fmtlib ${CMAKE_CURRENT_BINARY_DIR}
third-party
third-party/base64/include
third-party/rapidyaml
)
)
target_link_libraries(
diag

@ -95,6 +95,7 @@ LNAV_BUILT_FILES = \
ansi-palette-json.cc \
builtin-scripts.cc \
builtin-sh-scripts.cc \
css-color-names-json.cc \
default-config.cc \
default-formats.cc \
diseases-json.cc \
@ -158,11 +159,13 @@ LDADD = \
# emojis.json is from https://gist.github.com/oliveratgithub/0bf11a9aff0d6da7b46f1490f86a71eb/
# xml-entities.json is from https://html.spec.whatwg.org/entities.json
# css-color-names.json is from https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
dist_noinst_DATA = \
alpha-release.sh \
animals.json \
ansi-palette.json \
css-color-names.json \
diseases.json \
emojis.json \
$(BUILTIN_LNAVSCRIPTS) \
@ -524,6 +527,7 @@ DISTCLEANFILES = \
ansi-palette-json.h \
builtin-scripts.h \
builtin-sh-scripts.h \
css-color-names-json.h \
default-config.h \
default-formats.h \
diseases-json.h \

@ -242,8 +242,8 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
auto seq = md[1].value();
auto terminator = md[2].value();
struct line_range lr;
bool has_attrs = false;
text_attrs attrs;
bool has_attrs = false;
nonstd::optional<role_t> role;
size_t lpc;
@ -349,7 +349,7 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
}
lr.lr_start = sf.sf_begin;
lr.lr_end = -1;
if (attrs.ta_attrs || attrs.ta_fg_color || attrs.ta_bg_color) {
if (!attrs.empty()) {
sa->emplace_back(lr, VC_STYLE.value(attrs));
}
role | [&lr, &sa](role_t r) {

@ -115,6 +115,16 @@ trim(const std::string& str)
return str.substr(start, end - start);
}
inline const char*
ltrim(const char* str)
{
while (isspace(*str)) {
str += 1;
}
return str;
}
inline std::string
rtrim(const std::string& str)
{

@ -0,0 +1,150 @@
{
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"aqua": "#00ffff",
"aquamarine": "#7fffd4",
"azure": "#f0ffff",
"beige": "#f5f5dc",
"bisque": "#ffe4c4",
"black": "#000000",
"blanchedalmond": "#ffebcd",
"blue": "#0000ff",
"blueviolet": "#8a2be2",
"brown": "#a52a2a",
"burlywood": "#deb887",
"cadetblue": "#5f9ea0",
"chartreuse": "#7fff00",
"chocolate": "#d2691e",
"coral": "#ff7f50",
"cornflowerblue": "#6495ed",
"cornsilk": "#fff8dc",
"crimson": "#dc143c",
"cyan": "#00ffff",
"darkblue": "#00008b",
"darkcyan": "#008b8b",
"darkgoldenrod": "#b8860b",
"darkgray": "#a9a9a9",
"darkgreen": "#006400",
"darkgrey": "#a9a9a9",
"darkkhaki": "#bdb76b",
"darkmagenta": "#8b008b",
"darkolivegreen": "#556b2f",
"darkorange": "#ff8c00",
"darkorchid": "#9932cc",
"darkred": "#8b0000",
"darksalmon": "#e9967a",
"darkseagreen": "#8fbc8f",
"darkslateblue": "#483d8b",
"darkslategray": "#2f4f4f",
"darkslategrey": "#2f4f4f",
"darkturquoise": "#00ced1",
"darkviolet": "#9400d3",
"deeppink": "#ff1493",
"deepskyblue": "#00bfff",
"dimgray": "#696969",
"dimgrey": "#696969",
"dodgerblue": "#1e90ff",
"firebrick": "#b22222",
"floralwhite": "#fffaf0",
"forestgreen": "#228b22",
"fuchsia": "#ff00ff",
"gainsboro": "#dcdcdc",
"ghostwhite": "#f8f8ff",
"goldenrod": "#daa520",
"gold": "#ffd700",
"gray": "#808080",
"green": "#008000",
"greenyellow": "#adff2f",
"grey": "#808080",
"honeydew": "#f0fff0",
"hotpink": "#ff69b4",
"indianred": "#cd5c5c",
"indigo": "#4b0082",
"ivory": "#fffff0",
"khaki": "#f0e68c",
"lavenderblush": "#fff0f5",
"lavender": "#e6e6fa",
"lawngreen": "#7cfc00",
"lemonchiffon": "#fffacd",
"lightblue": "#add8e6",
"lightcoral": "#f08080",
"lightcyan": "#e0ffff",
"lightgoldenrodyellow": "#fafad2",
"lightgray": "#d3d3d3",
"lightgreen": "#90ee90",
"lightgrey": "#d3d3d3",
"lightpink": "#ffb6c1",
"lightsalmon": "#ffa07a",
"lightseagreen": "#20b2aa",
"lightskyblue": "#87cefa",
"lightslategray": "#778899",
"lightslategrey": "#778899",
"lightsteelblue": "#b0c4de",
"lightyellow": "#ffffe0",
"lime": "#00ff00",
"limegreen": "#32cd32",
"linen": "#faf0e6",
"magenta": "#ff00ff",
"maroon": "#800000",
"mediumaquamarine": "#66cdaa",
"mediumblue": "#0000cd",
"mediumorchid": "#ba55d3",
"mediumpurple": "#9370db",
"mediumseagreen": "#3cb371",
"mediumslateblue": "#7b68ee",
"mediumspringgreen": "#00fa9a",
"mediumturquoise": "#48d1cc",
"mediumvioletred": "#c71585",
"midnightblue": "#191970",
"mintcream": "#f5fffa",
"mistyrose": "#ffe4e1",
"moccasin": "#ffe4b5",
"navajowhite": "#ffdead",
"navy": "#000080",
"oldlace": "#fdf5e6",
"olive": "#808000",
"olivedrab": "#6b8e23",
"orange": "#ffa500",
"orangered": "#ff4500",
"orchid": "#da70d6",
"palegoldenrod": "#eee8aa",
"palegreen": "#98fb98",
"paleturquoise": "#afeeee",
"palevioletred": "#db7093",
"papayawhip": "#ffefd5",
"peachpuff": "#ffdab9",
"peru": "#cd853f",
"pink": "#ffc0cb",
"plum": "#dda0dd",
"powderblue": "#b0e0e6",
"purple": "#800080",
"rebeccapurple": "#663399",
"red": "#ff0000",
"rosybrown": "#bc8f8f",
"royalblue": "#4169e1",
"saddlebrown": "#8b4513",
"salmon": "#fa8072",
"sandybrown": "#f4a460",
"seagreen": "#2e8b57",
"seashell": "#fff5ee",
"sienna": "#a0522d",
"silver": "#c0c0c0",
"skyblue": "#87ceeb",
"slateblue": "#6a5acd",
"slategray": "#708090",
"slategrey": "#708090",
"snow": "#fffafa",
"springgreen": "#00ff7f",
"steelblue": "#4682b4",
"tan": "#d2b48c",
"teal": "#008080",
"thistle": "#d8bfd8",
"tomato": "#ff6347",
"turquoise": "#40e0d0",
"violet": "#ee82ee",
"wheat": "#f5deb3",
"white": "#ffffff",
"whitesmoke": "#f5f5f5",
"yellow": "#ffff00",
"yellowgreen": "#9acd32"
}

@ -474,6 +474,9 @@ field_overlay_source::build_meta_line(const listview_curses& lv,
}
auto comment_lines = al.rtrim().split_lines();
if (comment_lines.back().empty()) {
comment_lines.pop_back();
}
for (size_t lpc = 0; lpc < comment_lines.size(); lpc++) {
auto& comment_line = comment_lines[lpc];

@ -488,11 +488,129 @@ md2attr_line::leave_span(const md4cpp::event_handler::span& sp)
return Ok();
}
static attr_line_t
to_attr_line(const pugi::xml_node& doc)
{
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");
static const auto NAME_BG = string_fragment::from_const("background-color");
static const auto NAME_FONT_WEIGHT
= string_fragment::from_const("font-weight");
static const auto NAME_TEXT_DECO
= string_fragment::from_const("text-decoration");
static const auto& vc = view_colors::singleton();
attr_line_t retval;
if (doc.children().empty()) {
retval.append(doc.text().get());
}
for (const auto& child : doc.children()) {
if (child.name() == NAME_SPAN) {
auto styled_span = attr_line_t(child.text().get());
auto span_class = child.attribute("class");
if (span_class) {
auto cl_iter = vc.vc_class_to_role.find(span_class.value());
if (cl_iter == vc.vc_class_to_role.end()) {
log_error("unknown span class: %s", span_class.value());
} else {
styled_span.with_attr_for_all(cl_iter->second);
}
}
text_attrs ta;
auto span_style = child.attribute("style");
if (span_style) {
auto style_sf = string_fragment::from_c_str(span_style.value());
while (!style_sf.empty()) {
auto split_res
= style_sf.split_when(string_fragment::tag1{';'});
auto colon_split_res = split_res.first.split_pair(
string_fragment::tag1{':'});
if (colon_split_res) {
auto key = colon_split_res->first.trim();
auto value = colon_split_res->second.trim();
if (key == NAME_FG) {
auto color_res
= styling::color_unit::from_str(value);
if (color_res.isErr()) {
log_error("invalid color: %.*s -- %s",
value.length(),
value.data(),
color_res.unwrapErr().c_str());
} else {
ta.ta_fg_color
= vc.match_color(color_res.unwrap());
}
} else if (key == NAME_BG) {
auto color_res
= styling::color_unit::from_str(value);
if (color_res.isErr()) {
log_error(
"invalid background-color: %.*s -- %s",
value.length(),
value.data(),
color_res.unwrapErr().c_str());
} else {
ta.ta_bg_color
= vc.match_color(color_res.unwrap());
}
} else if (key == NAME_FONT_WEIGHT) {
if (value == "bold" || value == "bolder") {
ta.ta_attrs |= A_BOLD;
}
} else if (key == NAME_TEXT_DECO) {
auto deco_sf = value;
while (!deco_sf.empty()) {
auto deco_split_res = deco_sf.split_when(
string_fragment::tag1{' '});
if (deco_split_res.first.trim() == "underline")
{
ta.ta_attrs |= A_UNDERLINE;
}
deco_sf = deco_split_res.second;
}
}
}
style_sf = split_res.second;
}
if (!ta.empty()) {
styled_span.with_attr_for_all(VC_STYLE.value(ta));
}
}
retval.append(styled_span);
} else if (child.name() == NAME_PRE) {
auto pre_al = attr_line_t();
for (const auto& sub : child.children()) {
auto child_al = to_attr_line(sub);
if (pre_al.empty() && startswith(child_al.get_string(), "\n")) {
child_al.erase(0, 1);
}
pre_al.append(child_al);
}
pre_al.with_attr_for_all(SA_PREFORMATTED.value());
retval.append(pre_al);
} else {
retval.append(child.text().get());
}
}
return retval;
}
Result<void, std::string>
md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
{
static const auto& entity_map = md4cpp::get_xml_entity_map();
static const auto& vc = view_colors::singleton();
auto& last_block = this->ml_blocks.back();
@ -517,40 +635,64 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
}
case MD_TEXT_HTML: {
last_block.append(sf);
if (sf.startswith("<span ")) {
this->ml_html_span_starts.push_back(last_block.length()
- sf.length());
} else if (sf == "</span>" && !this->ml_html_span_starts.empty()) {
std::string html_span = last_block.get_string().substr(
this->ml_html_span_starts.back());
pugi::xml_document doc;
auto load_res = doc.load_string(html_span.c_str());
if (load_res) {
auto span = doc.child("span");
if (span) {
auto styled_span = attr_line_t(span.text().get());
auto span_class = span.attribute("class");
if (span_class) {
auto cl_iter
= vc.vc_class_to_role.find(span_class.value());
if (cl_iter == vc.vc_class_to_role.end()) {
log_error("unknown span class: %s",
span_class.value());
} else {
styled_span.with_attr_for_all(cl_iter->second);
}
struct open_tag {
std::string ot_name;
};
struct close_tag {
std::string ct_name;
};
mapbox::util::variant<open_tag, close_tag> tag;
if (sf.startswith("</")) {
tag = close_tag{
sf.substr(2)
.split_when(string_fragment::tag1{'>'})
.first.to_string(),
};
} else if (sf.startswith("<")) {
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) {
if (!this->ml_html_starts.empty()) {
return;
}
last_block.erase(this->ml_html_span_starts.back());
last_block.append(styled_span);
}
} else {
log_error("failed to parse: %s", load_res.description());
}
this->ml_html_span_starts.pop_back();
this->ml_html_starts.emplace_back(
ot.ot_name, last_block.length() - sf.length());
},
[this, &last_block](const close_tag& ct) {
if (this->ml_html_starts.empty()) {
return;
}
if (this->ml_html_starts.back().first != ct.ct_name) {
return;
}
const auto html_span = last_block.get_string().substr(
this->ml_html_starts.back().second);
pugi::xml_document doc;
auto load_res = doc.load_string(html_span.c_str());
if (!load_res) {
log_error("failed to parse: %s",
load_res.description());
} else {
last_block.erase(
this->ml_html_starts.back().second);
last_block.append(to_attr_line(doc));
}
this->ml_html_starts.pop_back();
});
}
break;
}

@ -83,7 +83,7 @@ private:
std::vector<list_block_t> ml_list_stack;
std::vector<table_t> ml_tables;
std::vector<size_t> ml_span_starts;
std::vector<size_t> ml_html_span_starts;
std::vector<std::pair<std::string, size_t>> ml_html_starts;
std::vector<attr_line_t> ml_footnotes;
int32_t ml_code_depth{0};
};

@ -81,7 +81,11 @@ plain_text_source::replace_with(const attr_line_t& text_lines)
this->tds_doc_sections = lnav::document::discover_metadata(text_lines);
file_off_t off = 0;
for (auto& line : text_lines.split_lines()) {
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;

@ -1023,9 +1023,13 @@ annotate_sql_statement(attr_line_t& al)
lnav::pcre2pp::code::from_const(R"(\A'[^']*('(?:'[^']*')*|$))"),
&SQL_STRING_ATTR,
},
{
lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
&SQL_NUMBER_ATTR,
},
{
lnav::pcre2pp::code::from_const(
R"(\A-?\d+(?:\.\d*(?:[eE][\-\+]?\d+)?)?|0x[0-9a-fA-F]+$)"),
R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
&SQL_NUMBER_ATTR,
},
{

@ -33,6 +33,7 @@
#include "ansi-palette-json.h"
#include "config.h"
#include "css-color-names-json.h"
#include "fmt/format.h"
#include "xterm-palette-json.h"
#include "yajlpp/yajlpp.hh"
@ -67,6 +68,29 @@ static const typed_json_path_container<std::vector<term_color>>
.with_children(term_color_handler),
};
struct css_color_names {
std::map<std::string, std::string> ccn_name_to_color;
};
static const typed_json_path_container<css_color_names> css_color_names_handlers
= {
yajlpp::pattern_property_handler("(?<css_color_name>.*)")
.for_field(&css_color_names::ccn_name_to_color),
};
static const css_color_names&
get_css_color_names()
{
static const intern_string_t iname
= intern_string::lookup(css_color_names_json.get_name());
static const auto INSTANCE
= css_color_names_handlers.parser_for(iname)
.of(css_color_names_json.to_string_fragment())
.unwrap();
return INSTANCE;
}
term_color_palette*
xterm_colors()
{
@ -86,12 +110,21 @@ ansi_colors()
}
Result<rgb_color, std::string>
rgb_color::from_str(const string_fragment& sf)
rgb_color::from_str(string_fragment sf)
{
if (sf.empty()) {
return Ok(rgb_color());
}
if (sf[0] != '#') {
const auto& css_colors = get_css_color_names();
const auto& iter = css_colors.ccn_name_to_color.find(sf.to_string());
if (iter != css_colors.ccn_name_to_color.end()) {
sf = string_fragment::from_str(iter->second);
}
}
rgb_color rgb_out;
if (sf[0] == '#') {

@ -43,7 +43,7 @@
#include "yajlpp/yajlpp_def.hh"
struct rgb_color {
static Result<rgb_color, std::string> from_str(const string_fragment& sf);
static Result<rgb_color, std::string> from_str(string_fragment sf);
explicit rgb_color(short r = -1, short g = -1, short b = -1)
: rc_r(r), rc_g(g), rc_b(b)

@ -596,6 +596,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_sql_anno.sh_c909647ed0e585002074f55c946f3033df1815b2.out \
$(srcdir)/%reldir%/test_sql_anno.sh_ce0506ee7a12eb0f7b970522cc6a79180ecb20cc.err \
$(srcdir)/%reldir%/test_sql_anno.sh_ce0506ee7a12eb0f7b970522cc6a79180ecb20cc.out \
$(srcdir)/%reldir%/test_sql_anno.sh_de46094b6e005285dc0921ef9979e36240c5042d.err \
$(srcdir)/%reldir%/test_sql_anno.sh_de46094b6e005285dc0921ef9979e36240c5042d.out \
$(srcdir)/%reldir%/test_sql_anno.sh_f3c64191d6016767a5857fbb1bad26548586bb96.err \
$(srcdir)/%reldir%/test_sql_anno.sh_f3c64191d6016767a5857fbb1bad26548586bb96.out \
$(srcdir)/%reldir%/test_sql_coll_func.sh_077cab6e271c914daf5b221cc512853077891f35.err \
@ -1064,6 +1066,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_text_file.sh_ac872aadda29b9a824361a2c711d62ec1c75d40f.out \
$(srcdir)/%reldir%/test_text_file.sh_c2a346ca1da2da4346f1d310212e166767993ce9.err \
$(srcdir)/%reldir%/test_text_file.sh_c2a346ca1da2da4346f1d310212e166767993ce9.out \
$(srcdir)/%reldir%/test_text_file.sh_d59b67113864ef5e77267d7fd8ad4072f5aef0fc.err \
$(srcdir)/%reldir%/test_text_file.sh_d59b67113864ef5e77267d7fd8ad4072f5aef0fc.out \
$(srcdir)/%reldir%/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.err \
$(srcdir)/%reldir%/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.out \
$()

@ -2086,7 +2086,7 @@ For support questions, email:
unparse_url(), upper(), xpath()
Example
#1 To get a string with the code points 0x48 and 0x49:
;SELECT char(0x48, 0x49) 
;SELECT char(0x48, 0x49) 
@ -4807,4 +4807,3 @@ For support questions, email:
cte-table-name The name for the temporary table.
select-stmt The SELECT statement used to
populate the temporary table.

@ -0,0 +1,7 @@
SELECT 0x77, 123, 123e4
sql_keyword ------
sql_number ----
sql_comma -
sql_number ---
sql_comma -
sql_number -----

@ -16,4 +16,3 @@ command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -175,4 +175,3 @@ command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -146,4 +146,3 @@ command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -1,4 +1,4 @@
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -0,0 +1,13 @@
Test
• One
• Two
• Three
Bold red
Underline
Hello,
World!

@ -146,4 +146,3 @@ command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -50,3 +50,5 @@ run_cap_test ./drive_sql_anno \
run_cap_test ./drive_sql_anno "SELECT * FROM foo.bar"
run_cap_test ./drive_sql_anno "SELECT json_object('abc', 'def') ->> '$.abc'"
run_cap_test ./drive_sql_anno "SELECT 0x77, 123, 123e4"

@ -34,3 +34,6 @@ run_cap_test ${lnav_test} -n \
run_cap_test ${lnav_test} -n \
${test_dir}/textfile_ansi_expanding.0
run_cap_test ${lnav_test} -n \
${test_dir}/textfile_0.md

@ -7,3 +7,12 @@
* One
* Two
* Three
<span style="color: #f00; font-weight: bold">Bold red</span>
<span style="text-decoration: underline; background-color: darkblue">Underline</span>
<pre>
Hello,
<span class="name">World</span>!
</pre>

Loading…
Cancel
Save