[md4c] initial markdown support

pull/1006/head
Timothy Stack 2 years ago
parent 0c7f6145c9
commit 31a670ce27

@ -9,6 +9,10 @@ lnav v0.10.2:
an array of values, enter the index to jump to.
* The pretty-print view will now show breadcrumbs that indicate the
location of the top line in the view with the prettified structure.
* Markdown files (those with a .md extension) are now rendered in the
TEXT view. The breadcrumb bar at the top will also be updated
depending on the section of the document that you are in and you
can use it to jump to different parts of the doc.
* Added an integration with regex101.com to make it easier to edit
log message regular expressions. Using the new "management CLI"
(activated by the -m option), a log format can be created from

@ -1,6 +1,6 @@
# aminclude_static.am generated automatically by Autoconf
# from AX_AM_MACROS_STATIC on Fri Apr 22 09:39:38 PDT 2022
# from AX_AM_MACROS_STATIC on Mon May 16 23:40:06 PDT 2022
# Code coverage

@ -308,6 +308,9 @@ AM_CONDITIONAL(USE_INCLUDED_YAJL, test $HAVE_LOCAL_YAJL -eq 0)
AM_CONDITIONAL(HAVE_LIBCURL, test x"$LIBCURL" != x"")
AM_CONDITIONAL([DISABLE_DOCUMENTATION], [ test x"$cross_compiling" != x"no" ])
USER_CXXFLAGS="${CXXFLAGS}"
AC_SUBST(USER_CXXFLAGS)
AC_CONFIG_HEADERS([src/config.h])
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([TESTS_ENVIRONMENT])

@ -348,6 +348,16 @@
"title": "/ui/theme-defs/<theme_name>/styles/h6",
"$ref": "#/definitions/style"
},
"hr": {
"description": "Styling for horizontal rules",
"title": "/ui/theme-defs/<theme_name>/styles/hr",
"$ref": "#/definitions/style"
},
"hyperlink": {
"description": "Styling for hyperlinks",
"title": "/ui/theme-defs/<theme_name>/styles/hyperlink",
"$ref": "#/definitions/style"
},
"list-glyph": {
"description": "Styling for glyphs that prefix a list item",
"title": "/ui/theme-defs/<theme_name>/styles/list-glyph",
@ -357,6 +367,36 @@
"description": "Styling for the separator between breadcrumbs",
"title": "/ui/theme-defs/<theme_name>/styles/breadcrumb",
"$ref": "#/definitions/style"
},
"table-border": {
"description": "Styling for table borders",
"title": "/ui/theme-defs/<theme_name>/styles/table-border",
"$ref": "#/definitions/style"
},
"table-header": {
"description": "Styling for table headers",
"title": "/ui/theme-defs/<theme_name>/styles/table-header",
"$ref": "#/definitions/style"
},
"quote-border": {
"description": "Styling for quoted-block borders",
"title": "/ui/theme-defs/<theme_name>/styles/quote-border",
"$ref": "#/definitions/style"
},
"quoted-text": {
"description": "Styling for quoted text blocks",
"title": "/ui/theme-defs/<theme_name>/styles/quoted-text",
"$ref": "#/definitions/style"
},
"footnote-border": {
"description": "Styling for footnote borders",
"title": "/ui/theme-defs/<theme_name>/styles/footnote-border",
"$ref": "#/definitions/style"
},
"footnote-text": {
"description": "Styling for footnote text",
"title": "/ui/theme-defs/<theme_name>/styles/footnote-text",
"$ref": "#/definitions/style"
}
},
"additionalProperties": false
@ -366,6 +406,16 @@
"title": "/ui/theme-defs/<theme_name>/syntax-styles",
"type": "object",
"properties": {
"quoted-code": {
"description": "Styling for quoted code blocks",
"title": "/ui/theme-defs/<theme_name>/syntax-styles/quoted-code",
"$ref": "#/definitions/style"
},
"code-border": {
"description": "Styling for quoted-code borders",
"title": "/ui/theme-defs/<theme_name>/syntax-styles/code-border",
"$ref": "#/definitions/style"
},
"keyword": {
"description": "Styling for keywords in source files",
"title": "/ui/theme-defs/<theme_name>/syntax-styles/keyword",

@ -99,7 +99,7 @@ function(bin2c)
DEPENDS bin2c "${FILE_TO_LINK}")
endfunction(bin2c)
foreach (FILE_TO_LINK ansi-palette.json xterm-palette.json help.txt init.sql)
foreach (FILE_TO_LINK ansi-palette.json emojis.json xml-entities.json xterm-palette.json help.txt help.md init.sql)
string(REPLACE "." "-" DST_FILE "${FILE_TO_LINK}")
add_custom_command(
OUTPUT "${DST_FILE}.h" "${DST_FILE}.cc"
@ -247,6 +247,7 @@ add_library(
config.h.in
all_logs_vtab.cc
archive_manager.cc
document.sections.cc
bin2c.hh
bookmarks.cc
bottom_status_source.cc
@ -294,6 +295,8 @@ add_library(
log_search_table.cc
logfile.cc
logfile_sub_source.cc
md2attr_line.cc
md4cpp.cc
network-extension-functions.cc
data_scanner.cc
data_scanner_re.cc
@ -343,11 +346,14 @@ add_library(
xpath_vtab.cc
xterm_mouse.cc
spookyhash/SpookyV2.cpp
third-party/md4c/md4c.c
third-party/sqlite/ext/series.c
third-party/sqlite/ext/dbdump.c
all_logs_vtab.hh
archive_manager.hh
archive_manager.cfg.hh
document.sections.hh
big_array.hh
bottom_status_source.hh
bound_tags.hh
@ -394,6 +400,8 @@ add_library(
logfile.hh
logfile_fwd.hh
logfile_stats.hh
md2attr_line.hh
md4cpp.hh
optional.hpp
papertrail_proc.hh
pcap_manager.hh
@ -477,6 +485,8 @@ add_library(
third-party/CLI/ConfigFwd.hpp
third-party/intervaltree/IntervalTree.h
third-party/md4c/md4c.h
)
set(lnav_SRCS lnav.cc)

@ -1,13 +1,15 @@
include $(top_srcdir)/aminclude_static.am
CXXFLAGS =
SUBDIRS = tools fmtlib pcrepp base tailer pugixml yajl yajlpp formats/logfmt .
bin_PROGRAMS = lnav
noinst_PROGRAMS = lnav-test
noinst_LIBRARIES = libdiag.a
noinst_LIBRARIES = libdiag.a libdatascanner.a
PTIME_V = $(PTIME_V_@AM_V@)
PTIME_V_ = $(PTIME_V_@AM_DEFAULT_V@)
@ -52,6 +54,9 @@ builtin-sh-scripts.h builtin-sh-scripts.cc: tools/bin2c$(BUILD_EXEEXT) $(BUILTIN
%-txt.cc %-txt.h: $(srcdir)/%.txt tools/bin2c$(BUILD_EXEEXT)
$(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-txt $<
%-md.cc %-md.h: $(srcdir)/%.md tools/bin2c$(BUILD_EXEEXT)
$(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-md $<
%-sql.cc %-sql.h: $(srcdir)/%.sql tools/bin2c$(BUILD_EXEEXT)
$(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-sql $<
@ -81,7 +86,9 @@ log_format_loader.$(OBJEXT): \
styling.$(OBJEXT): ansi-palette-json.h xterm-palette-json.h
view_helpers.$(OBJEXT): help-txt.h
view_helpers.$(OBJEXT): help-txt.h help-md.h
md4cpp.$(OBJEXT): xml-entities-json.h emojis-json.h
LNAV_BUILT_FILES = \
ansi-palette-json.h \
@ -94,17 +101,23 @@ LNAV_BUILT_FILES = \
default-config.cc \
default-formats.h \
default-formats.cc \
emojis-json.h \
emojis-json.cc \
help-txt.h \
help-txt.cc \
help-md.h \
help-md.cc \
init-sql.h \
init-sql.cc \
time_fmts.cc \
xml-entities-json.h \
xml-entities-json.cc \
xterm-palette-json.h \
xterm-palette-json.cc
AM_LIBS = $(CODE_COVERAGE_LIBS)
AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS) $(USER_CXXFLAGS)
AM_LDFLAGS = \
$(STATIC_LDFLAGS) \
@ -127,6 +140,7 @@ AM_CPPFLAGS = \
LDADD = \
libdiag.a \
libdatascanner.a \
base/libbase.a \
formats/logfmt/liblogfmt.a \
fmtlib/libcppfmt.a \
@ -143,16 +157,22 @@ LDADD = \
$(LIBARCHIVE_LIBS) \
$(LIBCURL)
# emojis.json is from https://gist.github.com/oliveratgithub/0bf11a9aff0d6da7b46f1490f86a71eb/
# xml-entities.json is from https://html.spec.whatwg.org/entities.json
dist_noinst_DATA = \
alpha-release.sh \
ansi-palette.json \
emojis.json \
$(BUILTIN_LNAVSCRIPTS) \
$(BUILTIN_SHSCRIPTS) \
$(CONFIG_FILES) \
$(FORMAT_FILES) \
xml-entities.json \
xterm-palette.json
noinst_HEADERS = \
third-party/md4c/md4c.h \
all_logs_vtab.hh \
archive_manager.hh \
archive_manager.cfg.hh \
@ -172,6 +192,7 @@ noinst_HEADERS = \
data_parser.hh \
db_sub_source.hh \
doc_status_source.hh \
document.sections.hh \
dump_internals.hh \
elem_to_json.hh \
environ_vtab.hh \
@ -187,6 +208,7 @@ noinst_HEADERS = \
fts_fuzzy_match.hh \
grep_highlighter.hh \
grep_proc.hh \
help.md \
help.txt \
help_text.hh \
help_text_formatter.hh \
@ -226,6 +248,8 @@ noinst_HEADERS = \
mapbox/variant.hpp \
mapbox/variant_io.hpp \
mapbox/variant_visitor.hpp \
md2attr_line.hh \
md4cpp.hh \
optional.hpp \
papertrail_proc.hh \
pcap_manager.hh \
@ -322,9 +346,16 @@ THIRD_PARTY_SRCS = \
third-party/CLI/ConfigFwd.hpp \
third-party/doctest-root/doctest/doctest.h \
third-party/intervaltree/IntervalTree.h \
third-party/md4c/md4c.c \
third-party/sqlite/ext/dbdump.c \
third-party/sqlite/ext/series.c
libdatascanner_a_SOURCES = \
data_scanner_re.cc
# XXX The data_scanner_re optimized build is taking 30+ minutes to run for
# some reason, so we need to override the flags
libdatascanner_a_CXXFLAGS = -O1
libdiag_a_SOURCES = \
$(THIRD_PARTY_SRCS) \
all_logs_vtab.cc \
@ -337,8 +368,8 @@ libdiag_a_SOURCES = \
command_executor.cc \
curl_looper.cc \
data_scanner.cc \
data_scanner_re.cc \
db_sub_source.cc \
document.sections.cc \
dump_internals.cc \
elem_to_json.cc \
environ_vtab.cc \
@ -377,6 +408,8 @@ libdiag_a_SOURCES = \
log_search_table.cc \
logfile.cc \
logfile_sub_source.cc \
md2attr_line.cc \
md4cpp.cc \
network-extension-functions.cc \
data_parser.cc \
papertrail_proc.cc \

@ -3,6 +3,7 @@ add_library(
../config.h.in
ansi_scrubber.cc
attr_line.cc
attr_line.builder.cc
auto_pid.cc
date_time_scanner.cc
fs_util.cc
@ -24,6 +25,7 @@ add_library(
ansi_scrubber.hh
attr_line.hh
attr_line.builder.hh
auto_fd.hh
auto_mem.hh
auto_pid.hh
@ -58,6 +60,7 @@ target_link_libraries(base cppfmt pcre::libpcre ncurses::libcurses pthread)
add_executable(
test_base
attr_line.tests.cc
fs_util.tests.cc
humanize.file_size.tests.cc
humanize.network.tests.cc

@ -21,6 +21,7 @@ noinst_LIBRARIES = libbase.a
noinst_HEADERS = \
ansi_scrubber.hh \
attr_line.hh \
attr_line.builder.hh \
auto_fd.hh \
auto_mem.hh \
auto_pid.hh \
@ -57,6 +58,7 @@ noinst_HEADERS = \
libbase_a_SOURCES = \
ansi_scrubber.cc \
attr_line.cc \
attr_line.builder.cc \
auto_pid.cc \
date_time_scanner.cc \
fs_util.cc \
@ -80,6 +82,7 @@ check_PROGRAMS = \
test_base
test_base_SOURCES = \
attr_line.tests.cc \
fs_util.tests.cc \
humanize.file_size.tests.cc \
humanize.network.tests.cc \

@ -0,0 +1,30 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "attr_line.builder.hh"

@ -0,0 +1,119 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lnav_attr_line_builder_hh
#define lnav_attr_line_builder_hh
#include <utility>
#include "attr_line.hh"
class attr_line_builder {
public:
explicit attr_line_builder(attr_line_t& al) : alb_line(al) {}
class attr_guard {
public:
attr_guard(attr_line_t& al, string_attr_pair sap)
: ag_line(al), ag_start(al.get_string().length()),
ag_attr(std::move(sap))
{
}
attr_guard(const attr_guard&) = delete;
attr_guard& operator=(const attr_guard&) = delete;
attr_guard(attr_guard&& other) noexcept
: ag_line(other.ag_line), ag_start(other.ag_start),
ag_attr(std::move(other.ag_attr))
{
other.ag_start = nonstd::nullopt;
}
~attr_guard()
{
if (this->ag_start) {
this->ag_line.al_attrs.emplace_back(
line_range{
this->ag_start.value(),
(int) this->ag_line.get_string().length(),
},
this->ag_attr);
}
}
private:
attr_line_t& ag_line;
nonstd::optional<int> ag_start;
string_attr_pair ag_attr;
};
attr_guard with_attr(string_attr_pair sap)
{
return {this->alb_line, std::move(sap)};
}
template<typename... Args>
attr_line_builder& overlay_attr(Args... args)
{
this->alb_line.al_attrs.template emplace_back(args...);
return *this;
}
template<typename... Args>
attr_line_builder& overlay_attr_for_char(int index, Args... args)
{
this->alb_line.al_attrs.template emplace_back(
line_range{index, index + 1}, args...);
return *this;
}
template<typename... Args>
attr_line_builder& append(Args... args)
{
this->alb_line.append(args...);
return *this;
}
attr_line_builder& indent(size_t amount)
{
auto pre = this->with_attr(SA_PREFORMATTED.value());
this->append(amount, ' ');
return *this;
}
private:
attr_line_t& alb_line;
};
#endif

@ -35,6 +35,7 @@
#include "auto_mem.hh"
#include "config.h"
#include "lnav_log.hh"
#include "pcrepp/pcrepp.hh"
attr_line_t&
attr_line_t::with_ansi_string(const char* str, ...)
@ -63,6 +64,88 @@ attr_line_t::with_ansi_string(const std::string& str)
return *this;
}
namespace text_stream {
struct word {
string_fragment w_word;
string_fragment w_remaining;
};
struct space {
string_fragment s_value;
string_fragment s_remaining;
};
struct corrupt {
string_fragment c_value;
string_fragment c_remaining;
};
struct eof {
string_fragment e_remaining;
};
using chunk = mapbox::util::variant<word, space, corrupt, eof>;
chunk
consume(const string_fragment text)
{
static const pcrepp WORD_RE(R"((*UTF)^[^\p{Z}\p{So}\p{C}]+)");
static const pcrepp SPACE_RE(R"((*UTF)^\s)");
if (text.empty()) {
return eof{text};
}
pcre_input pi(text);
pcre_context_static<30> pc;
if (WORD_RE.match(pc, pi)) {
auto split_res = text.split_n(pc.all()->length()).value();
return word{split_res.first, split_res.second};
}
if (SPACE_RE.match(pc, pi)) {
auto split_res = text.split_n(pc.all()->length()).value();
return space{split_res.first, split_res.second};
}
auto csize_res = ww898::utf::utf8::char_size(
[&text]() { return std::make_pair(text.front(), text.length()); });
if (csize_res.isErr()) {
auto split_res = text.split_n(1);
return corrupt{split_res->first, split_res->second};
}
auto split_res = text.split_n(csize_res.unwrap());
return word{split_res->first, split_res->second};
}
} // namespace text_stream
static void
split_attrs(attr_line_t& al, const line_range& lr)
{
string_attrs_t new_attrs;
for (auto& attr : al.al_attrs) {
if (!lr.intersects(attr.sa_range)) {
continue;
}
new_attrs.emplace_back(line_range{lr.lr_end, attr.sa_range.lr_end},
std::make_pair(attr.sa_type, attr.sa_value));
attr.sa_range.lr_end = lr.lr_start;
}
for (auto& new_attr : new_attrs) {
al.al_attrs.emplace_back(std::move(new_attr));
}
}
attr_line_t&
attr_line_t::insert(size_t index,
const attr_line_t& al,
@ -74,7 +157,7 @@ attr_line_t::insert(size_t index,
this->al_string.insert(index, al.al_string);
for (auto& sa : al.al_attrs) {
for (const auto& sa : al.al_attrs) {
this->al_attrs.emplace_back(sa);
line_range& lr = this->al_attrs.back().sa_range;
@ -85,88 +168,148 @@ attr_line_t::insert(size_t index,
}
}
if (tws != nullptr && (int) this->al_string.length() > tws->tws_width) {
ssize_t start_pos = index;
ssize_t line_start = this->al_string.rfind('\n', start_pos);
if (tws == nullptr) {
return *this;
}
if (line_start == (ssize_t) std::string::npos) {
line_start = 0;
} else {
line_start += 1;
}
static const pcrepp SPACE_RE(R"(\s?)");
ssize_t line_len = index - line_start;
ssize_t usable_width = tws->tws_width - tws->tws_indent;
ssize_t avail
= std::max((ssize_t) 0, (ssize_t) tws->tws_width - line_len);
ssize_t starting_line_index = this->al_string.rfind('\n', index);
if (starting_line_index == std::string::npos) {
starting_line_index = 0;
} else {
starting_line_index += 1;
}
if (avail == 0) {
avail = INT_MAX;
const ssize_t usable_width = tws->tws_width - tws->tws_indent;
auto text_to_wrap
= string_fragment{this->al_string.data(), (int) starting_line_index};
string_fragment last_word;
size_t line_ch_count = 0;
auto needs_indent = false;
while (!text_to_wrap.empty()) {
if (needs_indent) {
this->insert(text_to_wrap.sf_begin,
tws->tws_indent + tws->tws_padding_indent,
' ');
auto indent_lr = line_range{
text_to_wrap.sf_begin,
text_to_wrap.sf_begin + tws->tws_indent,
};
split_attrs(*this, indent_lr);
indent_lr.lr_end += tws->tws_padding_indent;
line_ch_count += tws->tws_padding_indent;
this->al_attrs.emplace_back(indent_lr, SA_PREFORMATTED.value());
text_to_wrap = text_to_wrap.prepend(
this->al_string.data(),
tws->tws_indent + tws->tws_padding_indent);
needs_indent = false;
}
auto chunk = text_stream::consume(text_to_wrap);
while (start_pos < (int) this->al_string.length()) {
ssize_t lpc;
// Find the end of a word or a breakpoint.
for (lpc = start_pos; lpc < (int) this->al_string.length()
&& (isalnum(this->al_string[lpc])
|| this->al_string[lpc] == ','
|| this->al_string[lpc] == '_'
|| this->al_string[lpc] == '.'
|| this->al_string[lpc] == ';');
lpc++)
{
if (this->al_string[lpc] == '-' || this->al_string[lpc] == '.')
{
lpc += 1;
break;
}
}
text_to_wrap = chunk.match(
[&](text_stream::word word) {
auto ch_count = word.w_word.utf8_length().unwrap();
if ((avail != usable_width) && (lpc - start_pos > avail)) {
// Need to wrap the word. Do the wrap.
this->insert(start_pos, 1, '\n')
.insert(start_pos + 1, tws->tws_indent, ' ');
start_pos += 1 + tws->tws_indent;
avail = tws->tws_width - tws->tws_indent;
} else {
// There's still room to add stuff.
avail -= (lpc - start_pos);
while (lpc < (int) this->al_string.length() && avail) {
if (this->al_string[lpc] == '\n') {
this->insert(lpc + 1, tws->tws_indent, ' ');
avail = usable_width;
lpc += 1 + tws->tws_indent;
break;
}
if (isalnum(this->al_string[lpc])
|| this->al_string[lpc] == '_') {
break;
if ((line_ch_count + ch_count) > usable_width
&& find_string_attr_containing(this->al_attrs,
&SA_PREFORMATTED,
text_to_wrap.sf_begin)
== this->al_attrs.end())
{
this->insert(word.w_word.sf_begin, 1, '\n');
this->insert(word.w_word.sf_begin + 1,
tws->tws_indent + tws->tws_padding_indent,
' ');
auto indent_lr = line_range{
word.w_word.sf_begin + 1,
word.w_word.sf_begin + 1 + tws->tws_indent,
};
split_attrs(*this, indent_lr);
indent_lr.lr_end += tws->tws_padding_indent;
this->al_attrs.emplace_back(indent_lr,
SA_PREFORMATTED.value());
line_ch_count = tws->tws_padding_indent + ch_count;
auto trailing_space_count = 0;
if (!last_word.empty()) {
trailing_space_count
= word.w_word.sf_begin - last_word.sf_begin;
this->erase(last_word.sf_begin, trailing_space_count);
}
avail -= 1;
lpc += 1;
return word.w_remaining
.erase_before(this->al_string.data(),
trailing_space_count)
.prepend(this->al_string.data(),
1 + tws->tws_indent + tws->tws_padding_indent);
}
line_ch_count += ch_count;
return word.w_remaining;
},
[&](text_stream::space space) {
if (space.s_value == "\n") {
line_ch_count = 0;
needs_indent = true;
return space.s_remaining;
}
start_pos = lpc;
if (!avail) {
this->insert(start_pos, 1, '\n')
.insert(start_pos + 1, tws->tws_indent, ' ');
start_pos += 1 + tws->tws_indent;
avail = usable_width;
for (lpc = start_pos; lpc < (int) this->al_string.length()
&& this->al_string[lpc] == ' ';
lpc++)
{
}
if (lpc != start_pos) {
this->erase(start_pos, (lpc - start_pos));
if (line_ch_count > 0) {
auto ch_count = space.s_value.utf8_length().unwrap();
if ((line_ch_count + ch_count) > usable_width
&& find_string_attr_containing(this->al_attrs,
&SA_PREFORMATTED,
text_to_wrap.sf_begin)
== this->al_attrs.end())
{
this->erase(space.s_value.sf_begin,
space.s_value.length());
this->insert(space.s_value.sf_begin, "\n");
line_ch_count = 0;
needs_indent = true;
auto trailing_space_count = 0;
if (!last_word.empty()) {
trailing_space_count
= space.s_value.sf_begin - last_word.sf_begin;
this->erase(last_word.sf_end, trailing_space_count);
}
return space.s_remaining
.erase_before(
this->al_string.data(),
space.s_value.length() + trailing_space_count)
.prepend(this->al_string.data(), 1);
}
line_ch_count += ch_count;
} else if (find_string_attr_containing(this->al_attrs,
&SA_PREFORMATTED,
text_to_wrap.sf_begin)
== this->al_attrs.end())
{
this->erase(space.s_value.sf_begin, space.s_value.length());
return space.s_remaining.erase_before(
this->al_string.data(), space.s_value.length());
}
}
return space.s_remaining;
},
[](text_stream::corrupt corrupt) {
return corrupt.c_remaining;
},
[](text_stream::eof eof) {
return eof.e_remaining;
});
if (chunk.is<text_stream::word>()) {
last_word = text_to_wrap;
}
}
ensure(this->al_string.data() == text_to_wrap.sf_string);
ensure(text_to_wrap.sf_begin <= text_to_wrap.sf_end);
}
return *this;
}
@ -303,10 +446,16 @@ attr_line_t::erase(size_t pos, size_t len)
attr_line_t&
attr_line_t::pad_to(size_t size)
{
const auto curr_len = this->length();
const auto curr_len = this->utf8_length_or_length();
if (curr_len < size) {
this->append((size - curr_len), ' ');
for (auto& attr : this->al_attrs) {
if (attr.sa_range.lr_start == 0 && attr.sa_range.lr_end == curr_len)
{
attr.sa_range.lr_end = this->al_string.length();
}
}
}
return *this;

@ -53,16 +53,15 @@ struct line_range {
explicit line_range(int start = -1, int end = -1)
: lr_start(start), lr_end(end){};
bool is_valid() const
{
return this->lr_start != -1;
}
bool is_valid() const { return this->lr_start != -1; }
int length() const
{
return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start;
}
bool empty() const { return this->length() == 0; }
int end_for_string(const std::string& str) const
{
return this->lr_end == -1 ? str.length() : this->lr_end;
@ -70,17 +69,20 @@ struct line_range {
bool contains(int pos) const
{
return this->lr_start <= pos && pos < this->lr_end;
return this->lr_start <= pos
&& (this->lr_end == -1 || pos < this->lr_end);
}
bool contains(const struct line_range& other) const
{
return this->contains(other.lr_start) && other.lr_end <= this->lr_end;
return this->contains(other.lr_start)
&& (this->lr_end == -1 || other.lr_end <= this->lr_end);
}
bool intersects(const struct line_range& other) const
{
return this->contains(other.lr_start) || this->contains(other.lr_end);
return this->contains(other.lr_start) || this->contains(other.lr_end)
|| other.contains(this->lr_start);
}
line_range intersection(const struct line_range& other) const;
@ -347,6 +349,12 @@ struct text_wrap_settings {
return *this;
}
text_wrap_settings& with_padding_indent(int indent)
{
this->tws_padding_indent = indent;
return *this;
}
text_wrap_settings& with_width(int width)
{
this->tws_width = width;
@ -355,6 +363,7 @@ struct text_wrap_settings {
int tws_indent{2};
int tws_width{80};
int tws_padding_indent{0};
};
/**
@ -504,6 +513,12 @@ public:
return *this;
}
attr_line_t& append(const string_fragment& str)
{
this->al_string.append(str.data(), str.length());
return *this;
}
template<typename S>
attr_line_t& append(S str)
{
@ -648,6 +663,16 @@ public:
return retval;
}
Result<size_t, const char*> utf8_length() const
{
return utf8_string_length(this->al_string);
}
ssize_t utf8_length_or_length() const
{
return utf8_string_length(this->al_string).unwrapOr(this->length());
}
std::string get_substring(const line_range& lr) const
{
if (!lr.is_valid()) {

@ -0,0 +1,91 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <iostream>
#include "attr_line.hh"
#include "config.h"
#include "doctest/doctest.h"
using namespace lnav::roles::literals;
TEST_CASE("attr_line_t::basic-wrapping")
{
text_wrap_settings tws = {3, 21};
attr_line_t to_be_wrapped{"This line, right here, needs to be wrapped."};
attr_line_t dst;
to_be_wrapped.al_attrs.emplace_back(
line_range{0, (int) to_be_wrapped.al_string.length()},
VC_ROLE.value(role_t::VCR_ERROR));
dst.append(to_be_wrapped, &tws);
CHECK(dst.get_string() ==
"This line, right\n"
" here, needs to be\n"
" wrapped.");
for (const auto& attr : dst.al_attrs) {
printf("attr %d:%d %s\n",
attr.sa_range.lr_start,
attr.sa_range.lr_end,
attr.sa_type->sat_name);
}
}
TEST_CASE("attr_line_t::unicode-wrap")
{
text_wrap_settings tws = {3, 21};
attr_line_t prefix;
prefix.append(" ")
.append("\u2022"_list_glyph)
.append(" ")
.with_attr_for_all(SA_PREFORMATTED.value());
attr_line_t body;
body.append("This is a long line that needs to be wrapped and indented");
attr_line_t li;
li.append(prefix)
.append(body, &tws)
.with_attr_for_all(SA_PREFORMATTED.value());
attr_line_t dst;
dst.append(li);
CHECK(dst.get_string()
== " \u2022 This is a long\n"
" line that needs to\n"
" be wrapped and\n"
" indented");
}

@ -186,6 +186,26 @@ string_fragment::consume_n(int amount) const
};
}
string_fragment::split_result
string_fragment::split_n(int amount) const
{
if (amount > this->length()) {
return nonstd::nullopt;
}
return std::make_pair(
string_fragment{
this->sf_string,
this->sf_begin,
this->sf_begin + amount,
},
string_fragment{
this->sf_string,
this->sf_begin + amount,
this->sf_end,
});
}
std::vector<string_fragment>
string_fragment::split_lines() const
{

@ -41,6 +41,7 @@
#include "fmt/format.h"
#include "optional.hpp"
#include "strnatcmp.h"
#include "ww898/cp_utf8.hpp"
struct string_fragment {
using iterator = const char*;
@ -60,40 +61,41 @@ struct string_fragment {
{
}
bool is_valid() const
{
return this->sf_begin != -1;
};
bool is_valid() const { return this->sf_begin != -1; }
int length() const
{
return this->sf_end - this->sf_begin;
};
int length() const { return this->sf_end - this->sf_begin; }
const char* data() const
Result<size_t, const char*> utf8_length() const
{
return &this->sf_string[this->sf_begin];
}
size_t retval = 0;
iterator begin() const
{
return &this->sf_string[this->sf_begin];
}
for (ssize_t byte_index = this->sf_begin; byte_index < this->sf_end;) {
auto ch_size
= TRY(ww898::utf::utf8::char_size([this, byte_index]() {
return std::make_pair(this->sf_string[byte_index],
this->sf_end - byte_index);
}));
byte_index += ch_size;
retval += 1;
}
iterator end() const
{
return &this->sf_string[this->sf_end];
return Ok(retval);
}
bool empty() const
{
return !this->is_valid() || length() == 0;
};
const char* data() const { return &this->sf_string[this->sf_begin]; }
char front() const { return this->sf_string[this->sf_begin]; }
iterator begin() const { return &this->sf_string[this->sf_begin]; }
iterator end() const { return &this->sf_string[this->sf_end]; }
bool empty() const { return !this->is_valid() || length() == 0; }
char operator[](int index) const
{
return this->sf_string[sf_begin + index];
};
}
bool operator==(const std::string& str) const
{
@ -104,7 +106,7 @@ struct string_fragment {
return memcmp(
&this->sf_string[this->sf_begin], str.c_str(), str.length())
== 0;
};
}
bool operator==(const string_fragment& sf) const
{
@ -113,7 +115,7 @@ struct string_fragment {
}
return memcmp(this->data(), sf.data(), sf.length()) == 0;
};
}
bool iequal(const string_fragment& sf) const
{
@ -124,7 +126,7 @@ struct string_fragment {
return strnatcasecmp(
this->length(), this->data(), sf.length(), sf.data())
== 0;
};
}
bool operator==(const char* str) const
{
@ -132,7 +134,7 @@ struct string_fragment {
return len == (size_t) this->length()
&& strncmp(this->data(), str, this->length()) == 0;
};
}
bool operator!=(const char* str) const
{
@ -240,6 +242,8 @@ struct string_fragment {
});
}
split_result split_n(int amount) const;
std::vector<string_fragment> split_lines() const;
struct tag1 {
@ -273,7 +277,7 @@ struct string_fragment {
buf[this->length()] = '\0';
return buf;
};
}
std::string to_string() const
{
@ -284,16 +288,43 @@ struct string_fragment {
{
this->sf_begin = 0;
this->sf_end = 0;
};
}
void invalidate()
{
this->sf_begin = -1;
this->sf_end = -1;
};
}
void trim(const char* tokens);
string_fragment prepend(const char* str, int amount) const
{
return string_fragment{
str,
this->sf_begin + amount,
this->sf_end + amount,
};
}
string_fragment erase_before(const char* str, int amount) const
{
return string_fragment{
str,
this->sf_begin - amount,
this->sf_end - amount,
};
}
string_fragment erase(const char* str, int amount) const
{
return string_fragment{
str,
this->sf_begin,
this->sf_end - amount,
};
}
const char* sf_string;
int sf_begin;
int sf_end;

@ -67,6 +67,8 @@ struct find {
T f_value;
};
struct second {};
template<typename F>
struct filter_in {
F f_func;
@ -115,6 +117,15 @@ struct skip {
struct unique {};
struct max_value {};
template<typename T>
struct max_with_init {
T m_init;
};
struct sum {};
} // namespace details
template<typename T>
@ -144,6 +155,12 @@ find(T value)
};
}
inline details::second
second()
{
return details::second{};
}
inline details::nth
nth(nonstd::optional<size_t> index)
{
@ -258,6 +275,25 @@ chain(const T& value1, const Args&... args)
return retval;
}
inline details::max_value
max()
{
return details::max_value{};
}
template<typename T>
inline details::max_with_init<T>
max(T init)
{
return details::max_with_init<T>{init};
}
inline details::sum
sum()
{
return details::sum{};
}
} // namespace itertools
} // namespace lnav
@ -309,6 +345,55 @@ operator|(const C& in, const lnav::itertools::details::nth indexer)
return nonstd::nullopt;
}
template<typename C>
nonstd::optional<typename C::value_type>
operator|(const C& in, const lnav::itertools::details::max_value maxer)
{
nonstd::optional<typename C::value_type> retval;
for (const auto& elem : in) {
if (!retval) {
retval = elem;
continue;
}
if (elem > retval.value()) {
retval = elem;
}
}
return retval;
}
template<typename C, typename T>
typename C::value_type
operator|(const C& in, const lnav::itertools::details::max_with_init<T> maxer)
{
typename C::value_type retval = (typename C::value_type) maxer.m_init;
for (const auto& elem : in) {
if (elem > retval) {
retval = elem;
}
}
return retval;
}
template<typename C>
typename C::value_type
operator|(const C& in, const lnav::itertools::details::sum summer)
{
typename C::value_type retval{0};
for (const auto& elem : in) {
retval += elem;
}
return retval;
}
template<typename C>
C
operator|(const C& in, const lnav::itertools::details::skip& skipper)

@ -154,7 +154,13 @@ user_message::to_attr_line(std::set<render_flags> flags) const
}
retval.append(header).append("\n");
if (!snip.s_content.blank()) {
for (const auto& line : snip.s_content.split_lines()) {
auto snippet_lines = snip.s_content.split_lines();
auto longest_line_length = snippet_lines
| lnav::itertools::map(&attr_line_t::utf8_length_or_length)
| lnav::itertools::max(40);
for (auto& line : snippet_lines) {
line.pad_to(longest_line_length);
retval.append(" | "_comment).append(line).append("\n");
}
}
@ -254,6 +260,8 @@ println(FILE* file, const attr_line_t& al)
nonstd::optional<int> last_point;
for (const auto& point : points) {
if (last_point) {
auto default_fg_style = fmt::text_style{};
auto default_bg_style = fmt::text_style{};
auto line_style = fmt::text_style{};
auto fg_style = fmt::text_style{};
auto start = last_point.value();
@ -330,6 +338,12 @@ println(FILE* file, const attr_line_t& al)
case role_t::VCR_LIST_GLYPH:
line_style |= fmt::fg(fmt::terminal_color::yellow);
break;
case role_t::VCR_QUOTED_CODE:
default_fg_style
= fmt::fg(fmt::terminal_color::white);
default_bg_style
= fmt::bg(fmt::terminal_color::black);
break;
default:
break;
}
@ -339,6 +353,14 @@ println(FILE* file, const attr_line_t& al)
if (!line_style.has_foreground() && fg_style.has_foreground()) {
line_style |= fg_style;
}
if (!line_style.has_foreground()
&& default_fg_style.has_foreground()) {
line_style |= default_fg_style;
}
if (!line_style.has_background()
&& default_bg_style.has_background()) {
line_style |= default_bg_style;
}
if (start < str.size()) {
auto actual_end
@ -357,7 +379,12 @@ println(FILE* file, const attr_line_t& al)
void
print(FILE* file, const user_message& um)
{
println(file, um.to_attr_line().rtrim());
auto al = um.to_attr_line();
if (endswith(al.get_string(), "\n")) {
al.erase(al.length() - 1);
}
println(file, al);
}
} // namespace console

@ -95,7 +95,7 @@
#include "opt_util.hh"
static const size_t BUFFER_SIZE = 256 * 1024;
static const size_t MAX_LOG_LINE_SIZE = 2048;
static const size_t MAX_LOG_LINE_SIZE = 2 * 1024;
static const char* CRASH_MSG
= "\n"

@ -36,6 +36,7 @@ string_attr_type<void> SA_BODY("body");
string_attr_type<void> SA_HIDDEN("hidden");
string_attr_type<const intern_string_t> SA_FORMAT("format");
string_attr_type<void> SA_REMOVED("removed");
string_attr_type<void> SA_PREFORMATTED("preformatted");
string_attr_type<std::string> SA_INVALID("invalid");
string_attr_type<std::string> SA_ERROR("error");
string_attr_type<int64_t> SA_LEVEL("level");

@ -83,6 +83,8 @@ enum class role_t : int32_t {
VCR_POPUP,
VCR_COLOR_HINT,
VCR_QUOTED_CODE,
VCR_CODE_BORDER,
VCR_KEYWORD,
VCR_STRING,
VCR_COMMENT,
@ -109,8 +111,16 @@ enum class role_t : int32_t {
VCR_H5,
VCR_H6,
VCR_HR,
VCR_HYPERLINK,
VCR_LIST_GLYPH,
VCR_BREADCRUMB,
VCR_TABLE_BORDER,
VCR_TABLE_HEADER,
VCR_QUOTE_BORDER,
VCR_QUOTED_TEXT,
VCR_FOOTNOTE_BORDER,
VCR_FOOTNOTE_TEXT,
VCR__MAX
};
@ -164,6 +174,7 @@ extern string_attr_type<void> SA_BODY;
extern string_attr_type<void> SA_HIDDEN;
extern string_attr_type<const intern_string_t> SA_FORMAT;
extern string_attr_type<void> SA_REMOVED;
extern string_attr_type<void> SA_PREFORMATTED;
extern string_attr_type<std::string> SA_INVALID;
extern string_attr_type<std::string> SA_ERROR;
extern string_attr_type<int64_t> SA_LEVEL;
@ -176,6 +187,20 @@ extern string_attr_type<int64_t> VC_FOREGROUND;
extern string_attr_type<int64_t> VC_BACKGROUND;
namespace lnav {
namespace string {
namespace attrs {
template<typename S>
inline std::pair<S, string_attr_pair>
preformatted(S str)
{
return std::make_pair(std::move(str), SA_PREFORMATTED.template value());
}
} // namespace attrs
} // namespace string
namespace roles {
template<typename S>
@ -282,6 +307,22 @@ identifier(S str)
VC_ROLE.template value(role_t::VCR_IDENTIFIER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
hr(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_HR));
}
template<typename S>
inline std::pair<S, string_attr_pair>
hyperlink(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_HYPERLINK));
}
template<typename S>
inline std::pair<S, string_attr_pair>
list_glyph(S str)
@ -298,6 +339,70 @@ breadcrumb(S str)
VC_ROLE.template value(role_t::VCR_BREADCRUMB));
}
template<typename S>
inline std::pair<S, string_attr_pair>
quoted_code(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_QUOTED_CODE));
}
template<typename S>
inline std::pair<S, string_attr_pair>
code_border(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_CODE_BORDER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
table_border(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_TABLE_BORDER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
table_header(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_TABLE_HEADER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
quote_border(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_QUOTE_BORDER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
quoted_text(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_QUOTED_TEXT));
}
template<typename S>
inline std::pair<S, string_attr_pair>
footnote_border(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_FOOTNOTE_BORDER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
footnote_text(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_FOOTNOTE_TEXT));
}
template<typename S>
inline std::pair<S, string_attr_pair>
h1(S str)
@ -397,6 +502,34 @@ inline std::pair<std::string, string_attr_pair> operator"" _h3(const char* str,
VC_ROLE.template value(role_t::VCR_H3));
}
inline std::pair<std::string, string_attr_pair> operator"" _h4(const char* str,
std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_H4));
}
inline std::pair<std::string, string_attr_pair> operator"" _h5(const char* str,
std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_H5));
}
inline std::pair<std::string, string_attr_pair> operator"" _hr(const char* str,
std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_HR));
}
inline std::pair<std::string, string_attr_pair> operator"" _hyperlink(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_HYPERLINK));
}
inline std::pair<std::string, string_attr_pair> operator"" _list_glyph(
const char* str, std::size_t len)
{
@ -411,6 +544,55 @@ inline std::pair<std::string, string_attr_pair> operator"" _breadcrumb(
VC_ROLE.template value(role_t::VCR_BREADCRUMB));
}
inline std::pair<std::string, string_attr_pair> operator"" _quoted_code(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_QUOTED_CODE));
}
inline std::pair<std::string, string_attr_pair> operator"" _code_border(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_CODE_BORDER));
}
inline std::pair<std::string, string_attr_pair> operator"" _table_border(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_TABLE_BORDER));
}
inline std::pair<std::string, string_attr_pair> operator"" _quote_border(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_QUOTE_BORDER));
}
inline std::pair<std::string, string_attr_pair> operator"" _quoted_text(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_QUOTED_TEXT));
}
inline std::pair<std::string, string_attr_pair> operator"" _footnote_border(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_FOOTNOTE_BORDER));
}
inline std::pair<std::string, string_attr_pair> operator"" _footnote_text(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_FOOTNOTE_BORDER));
}
} // namespace literals
} // namespace roles

@ -97,9 +97,14 @@ struct crumb {
crumb& with_possible_range(size_t count)
{
this->c_possible_range = count;
this->c_search_placeholder
= fmt::format(FMT_STRING("(Enter a number from 0 to {})"),
this->c_possible_range.value() - 1);
if (count == 0) {
this->c_search_placeholder = "(Array is empty)";
} else if (count == 1) {
this->c_search_placeholder = "(Array contains a single element)";
} else {
this->c_search_placeholder = fmt::format(
FMT_STRING("(Enter a number from 0 to {})"), count - 1);
}
return *this;
}

@ -111,7 +111,13 @@ breadcrumb_curses::reload_data()
auto& selected_crumb_ref
= this->bc_focused_crumbs[this->bc_selected_crumb.value()];
this->bc_possible_values = selected_crumb_ref.c_possibility_provider()
| lnav::itertools::sort_by(&breadcrumb::possibility::p_key);
| lnav::itertools::sort_with([](const auto& lhs, const auto& rhs) {
return strnatcasecmp(lhs.p_key.size(),
lhs.p_key.data(),
rhs.p_key.size(),
rhs.p_key.data())
< 0;
});
nonstd::optional<size_t> selected_value;
this->bc_similar_values = this->bc_possible_values
@ -119,7 +125,10 @@ breadcrumb_curses::reload_data()
[](const auto& elem) { return elem.p_key; },
this->bc_current_search,
INT_MAX);
if (selected_crumb_ref.c_key.is<std::string>()) {
if (selected_crumb_ref.c_key.is<std::string>()
&& selected_crumb_ref.c_expected_input
!= breadcrumb::crumb::expected_input_t::anything)
{
auto& selected_crumb_key = selected_crumb_ref.c_key.get<std::string>();
auto found_poss_opt = this->bc_similar_values
| lnav::itertools::find_if([&selected_crumb_key](const auto& elem) {
@ -157,10 +166,9 @@ breadcrumb_curses::reload_data()
std::min(this->bc_match_source.get_lines().size() + 1, size_t{4})));
this->bc_match_view.set_width(width + 3);
this->bc_match_view.set_needs_update();
if (selected_value) {
this->bc_match_view.set_selection(vis_line_t(selected_value.value()));
this->bc_match_view.scroll_selection_into_view();
}
this->bc_match_view.set_selection(
vis_line_t(selected_value.value_or(-1_vl)));
this->bc_match_view.scroll_selection_into_view();
this->bc_match_view.reload_data();
this->set_needs_update();
}
@ -186,6 +194,7 @@ breadcrumb_curses::blur()
this->bc_selected_crumb = nonstd::nullopt;
this->bc_current_search.clear();
this->bc_match_view.set_height(0_vl);
this->bc_match_view.set_selection(-1_vl);
this->bc_match_source.clear();
this->set_needs_update();
}
@ -214,17 +223,21 @@ breadcrumb_curses::handle_key(int ch)
case '\t':
case KEY_RIGHT:
if (this->bc_selected_crumb) {
this->perform_selection(perform_behavior_t::if_different);
this->blur();
this->focus();
this->reload_data();
if (this->bc_selected_crumb.value()
< this->bc_focused_crumbs.size() - 1) {
this->bc_selected_crumb
= this->bc_selected_crumb.value() + 1;
} else {
this->bc_selected_crumb = 0;
retval = true;
}
this->bc_current_search.clear();
this->reload_data();
} else {
retval = true;
}
retval = true;
break;
case KEY_HOME:
this->bc_match_view.set_selection(0_vl);
@ -261,33 +274,7 @@ breadcrumb_curses::handle_key(int ch)
break;
case KEY_ENTER:
case '\r':
if (this->bc_selected_crumb) {
auto& selected_crumb_ref
= this->bc_focused_crumbs[this->bc_selected_crumb.value()];
if (this->bc_match_view.get_selection() >= 0
&& this->bc_match_view.get_selection()
< this->bc_similar_values.size())
{
const auto& new_value
= this->bc_similar_values[this->bc_match_view
.get_selection()]
.p_key;
selected_crumb_ref.c_performer(new_value);
} else if (!this->bc_current_search.empty()) {
if (selected_crumb_ref.c_possible_range) {
size_t index;
if (sscanf(
this->bc_current_search.c_str(), "%zu", &index)
== 1) {
selected_crumb_ref.c_performer(index);
}
} else {
selected_crumb_ref.c_performer(this->bc_current_search);
}
}
}
this->perform_selection(perform_behavior_t::always);
break;
default:
if (isprint(ch)) {
@ -305,6 +292,52 @@ breadcrumb_curses::handle_key(int ch)
return retval;
}
void
breadcrumb_curses::perform_selection(
breadcrumb_curses::perform_behavior_t behavior)
{
if (!this->bc_selected_crumb) {
return;
}
auto& selected_crumb_ref
= this->bc_focused_crumbs[this->bc_selected_crumb.value()];
auto match_sel = this->bc_match_view.get_selection();
if (match_sel >= 0 && match_sel < this->bc_similar_values.size()) {
const auto& new_value = this->bc_similar_values[match_sel].p_key;
switch (behavior) {
case perform_behavior_t::if_different:
if (breadcrumb::crumb::key_t{new_value}
== selected_crumb_ref.c_key) {
return;
}
break;
case perform_behavior_t::always:
break;
}
selected_crumb_ref.c_performer(new_value);
} else if (!this->bc_current_search.empty()) {
switch (selected_crumb_ref.c_expected_input) {
case breadcrumb::crumb::expected_input_t::exact:
break;
case breadcrumb::crumb::expected_input_t::index:
case breadcrumb::crumb::expected_input_t::index_or_exact: {
size_t index;
if (sscanf(this->bc_current_search.c_str(), "%zu", &index) == 1)
{
selected_crumb_ref.c_performer(index);
}
break;
}
case breadcrumb::crumb::expected_input_t::anything:
selected_crumb_ref.c_performer(this->bc_current_search);
break;
}
}
}
bool
breadcrumb_curses::search_overlay_source::list_value_for_overlay(
const listview_curses& lv,

@ -82,6 +82,13 @@ private:
breadcrumb_curses* sos_parent{nullptr};
};
enum class perform_behavior_t {
always,
if_different,
};
void perform_selection(perform_behavior_t behavior);
WINDOW* bc_window{nullptr};
std::function<std::vector<breadcrumb::crumb>()> bc_line_source;
int bc_y{0};

@ -947,11 +947,23 @@ exec_context::add_error_context(lnav::console::user_message& um)
if (this->ec_current_help != nullptr) {
attr_line_t help;
format_help_text_for_term(*this->ec_current_help, -1, help, true);
format_help_text_for_term(*this->ec_current_help, 70, help, true);
um.with_help(help);
}
}
exec_context::source_guard
exec_context::enter_source(intern_string_t path,
int line_number,
const std::string& content)
{
attr_line_t content_al{content};
content_al.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
this->ec_source.emplace(
lnav::console::snippet::from(path, content_al).with_line(line_number));
return {this};
}
exec_context::output_guard::output_guard(exec_context& context,
std::string name,
const nonstd::optional<output_t>& file)

@ -152,12 +152,7 @@ struct exec_context {
source_guard enter_source(intern_string_t path,
int line_number,
const std::string& content)
{
this->ec_source.emplace(
lnav::console::snippet::from(path, content).with_line(line_number));
return {this};
}
const std::string& content);
struct error_cb_guard {
error_cb_guard(exec_context* context) : sg_context(context) {}

@ -122,6 +122,13 @@ public:
}
}
explicit data_scanner(string_fragment sf) : ds_pcre_input(sf)
{
if (!sf.empty() && sf[sf.length() - 1] == '.') {
this->ds_pcre_input.pi_length -= 1;
}
}
data_scanner(shared_buffer_ref& line,
size_t off = 0,
size_t len = (size_t) -1)

@ -0,0 +1,452 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <utility>
#include <vector>
#include "document.sections.hh"
#include "base/enum_util.hh"
#include "base/itertools.hh"
#include "base/lnav_log.hh"
#include "base/opt_util.hh"
#include "data_scanner.hh"
namespace lnav {
namespace document {
nonstd::optional<hier_node*>
hier_node::lookup_child(section_key_t key) const
{
return make_optional_from_nullable(key.match(
[this](const std::string& str) -> hier_node* {
auto iter = this->hn_named_children.find(str);
if (iter != this->hn_named_children.end()) {
return iter->second;
}
return nullptr;
},
[this](size_t index) -> hier_node* {
if (index < this->hn_children.size()) {
return this->hn_children[index].get();
}
return nullptr;
}));
}
nonstd::optional<const hier_node*>
hier_node::lookup_path(const hier_node* root,
const std::vector<section_key_t>& path)
{
auto retval = make_optional_from_nullable(root);
for (const auto& comp : path) {
if (!retval) {
break;
}
retval = retval.value()->lookup_child(comp);
}
if (!retval) {
return nonstd::nullopt;
}
return retval;
}
metadata
discover_metadata(const attr_line_t& al)
{
const auto& orig_attrs = al.get_attrs();
auto headers = orig_attrs
| lnav::itertools::filter_in([](const string_attr& attr) {
if (attr.sa_type != &VC_ROLE) {
return false;
}
auto role = attr.sa_value.get<role_t>();
switch (role) {
case role_t::VCR_H1:
case role_t::VCR_H2:
case role_t::VCR_H3:
case role_t::VCR_H4:
case role_t::VCR_H5:
case role_t::VCR_H6:
return true;
default:
return false;
}
})
| lnav::itertools::sort_by(&string_attr::sa_range);
// Remove headers from quoted text
for (const auto& orig_attr : orig_attrs) {
if (orig_attr.sa_type == &VC_ROLE
&& orig_attr.sa_value.get<role_t>() == role_t::VCR_QUOTED_TEXT)
{
remove_string_attr(headers, orig_attr.sa_range);
}
}
std::vector<section_interval_t> intervals;
struct open_interval_t {
open_interval_t(uint32_t level, file_off_t start, section_key_t id)
: oi_level(level), oi_start(start), oi_id(std::move(id))
{
}
uint32_t oi_level;
file_off_t oi_start;
section_key_t oi_id;
std::unique_ptr<hier_node> oi_node{std::make_unique<hier_node>()};
};
std::vector<open_interval_t> open_intervals;
auto root_node = std::make_unique<hier_node>();
for (const auto& hdr_attr : headers) {
auto role = hdr_attr.sa_value.get<role_t>();
auto role_num = lnav::enums::to_underlying(role)
- lnav::enums::to_underlying(role_t::VCR_H1);
std::vector<open_interval_t> new_open_intervals;
for (auto& oi : open_intervals) {
if (oi.oi_level >= role_num) {
// close out this section
intervals.emplace_back(
oi.oi_start, hdr_attr.sa_range.lr_start - 1, oi.oi_id);
auto* node_ptr = oi.oi_node.get();
auto* parent_node = oi.oi_node->hn_parent;
if (parent_node != nullptr) {
parent_node->hn_children.emplace_back(
std::move(oi.oi_node));
parent_node->hn_named_children.insert({
oi.oi_id.get<std::string>(),
node_ptr,
});
}
} else {
new_open_intervals.emplace_back(std::move(oi));
}
}
auto* parent_node = new_open_intervals.empty()
? root_node.get()
: new_open_intervals.back().oi_node.get();
new_open_intervals.emplace_back(role_num,
hdr_attr.sa_range.lr_start,
al.get_substring(hdr_attr.sa_range));
new_open_intervals.back().oi_node->hn_parent = parent_node;
new_open_intervals.back().oi_node->hn_start
= hdr_attr.sa_range.lr_start;
open_intervals = std::move(new_open_intervals);
}
for (auto& oi : open_intervals) {
// close out this section
intervals.emplace_back(oi.oi_start, al.length(), oi.oi_id);
auto* node_ptr = oi.oi_node.get();
auto* parent_node = oi.oi_node->hn_parent;
if (parent_node == nullptr) {
root_node = std::move(oi.oi_node);
} else {
parent_node->hn_children.emplace_back(std::move(oi.oi_node));
parent_node->hn_named_children.insert({
oi.oi_id.get<std::string>(),
node_ptr,
});
}
}
return {
sections_tree_t{std::move(intervals)},
std::move(root_node),
};
}
class structure_walker {
public:
explicit structure_walker(string_fragment content) : sw_scanner(content)
{
this->sw_interval_state.resize(1);
this->sw_hier_nodes.push_back(std::make_unique<hier_node>());
}
metadata walk()
{
pcre_context_static<30> pc;
data_token_t dt = DT_INVALID;
lnav::document::metadata retval;
auto& pi = this->sw_scanner.get_input();
while (this->sw_scanner.tokenize2(pc, dt)) {
element el(dt, pc);
switch (dt) {
case DT_XML_DECL_TAG:
case DT_XML_EMPTY_TAG:
this->sw_values.emplace_back(el);
break;
case DT_XML_OPEN_TAG:
this->flush_values();
this->sw_interval_state.back().is_start
= el.e_capture.c_begin;
this->sw_interval_state.back().is_line_number
= this->sw_line_number;
this->sw_interval_state.back().is_name
= pi.get_substr(&el.e_capture);
this->sw_depth += 1;
this->sw_interval_state.resize(this->sw_depth + 1);
this->sw_hier_nodes.push_back(
std::make_unique<hier_node>());
break;
case DT_XML_CLOSE_TAG: {
auto term = this->flush_values();
if (this->sw_depth > 0) {
this->sw_depth -= 1;
this->append_child_node(term);
this->sw_interval_state.pop_back();
this->sw_hier_stage
= std::move(this->sw_hier_nodes.back());
this->sw_hier_nodes.pop_back();
}
this->append_child_node(el.e_capture);
this->flush_values();
break;
}
case DT_LCURLY:
case DT_LSQUARE:
case DT_LPAREN:
this->flush_values();
this->sw_depth += 1;
if (!this->sw_interval_state.back().is_start) {
this->sw_interval_state.back().is_start
= el.e_capture.c_begin;
this->sw_interval_state.back().is_line_number
= this->sw_line_number;
}
this->sw_interval_state.resize(this->sw_depth + 1);
this->sw_hier_nodes.push_back(
std::make_unique<hier_node>());
break;
case DT_RCURLY:
case DT_RSQUARE:
case DT_RPAREN: {
auto term = this->flush_values();
if (this->sw_depth > 0) {
this->sw_depth -= 1;
this->append_child_node(term);
this->sw_interval_state.pop_back();
this->sw_hier_stage
= std::move(this->sw_hier_nodes.back());
this->sw_hier_nodes.pop_back();
}
this->sw_values.emplace_back(el);
break;
}
case DT_COMMA:
if (this->sw_depth > 0) {
auto term = this->flush_values();
this->append_child_node(term);
}
break;
case DT_LINE:
this->sw_line_number += 1;
break;
case DT_WHITE:
break;
default:
this->sw_values.emplace_back(el);
break;
}
}
this->flush_values();
if (this->sw_hier_stage != nullptr) {
this->sw_hier_stage->hn_parent = this->sw_hier_nodes.back().get();
this->sw_hier_nodes.back()->hn_children.push_back(
std::move(this->sw_hier_stage));
}
this->sw_hier_stage = std::move(this->sw_hier_nodes.back());
this->sw_hier_nodes.pop_back();
if (this->sw_hier_stage->hn_children.size() == 1
&& this->sw_hier_stage->hn_named_children.empty())
{
this->sw_hier_stage
= std::move(this->sw_hier_stage->hn_children.front());
this->sw_hier_stage->hn_parent = nullptr;
}
retval.m_sections_root = std::move(this->sw_hier_stage);
retval.m_sections_tree = sections_tree_t(std::move(this->sw_intervals));
return retval;
}
private:
struct element {
element(data_token_t token, pcre_context& pc)
: e_token(token), e_capture(*pc.all())
{
}
element(data_token_t token, pcre_context::capture_t& cap)
: e_token(token), e_capture(cap)
{
}
data_token_t e_token;
pcre_context::capture_t e_capture;
};
struct interval_state {
nonstd::optional<file_off_t> is_start;
size_t is_line_number{0};
std::string is_name;
};
nonstd::optional<pcre_context::capture_t> flush_values()
{
nonstd::optional<pcre_context::capture_t> last_key;
nonstd::optional<pcre_context::capture_t> retval;
auto& pi = this->sw_scanner.get_input();
if (!this->sw_values.empty()) {
if (!this->sw_interval_state.back().is_start) {
this->sw_interval_state.back().is_start
= this->sw_values.front().e_capture.c_begin;
this->sw_interval_state.back().is_line_number
= this->sw_line_number;
}
retval = this->sw_values.back().e_capture;
}
for (const auto& el : this->sw_values) {
switch (el.e_token) {
case DT_SYMBOL:
case DT_CONSTANT:
case DT_WORD:
case DT_QUOTED_STRING:
last_key = el.e_capture;
break;
case DT_COLON:
case DT_EQUALS:
if (last_key) {
this->sw_interval_state.back().is_name
= pi.get_substr(&last_key.value());
if (!this->sw_interval_state.back().is_name.empty()) {
this->sw_interval_state.back().is_start
= static_cast<ssize_t>(
last_key.value().c_begin);
this->sw_interval_state.back().is_line_number
= this->sw_line_number;
}
last_key = nonstd::nullopt;
}
break;
default:
break;
}
}
this->sw_values.clear();
return retval;
}
void append_child_node(nonstd::optional<pcre_context::capture_t> terminator)
{
auto& ivstate = this->sw_interval_state.back();
if (!ivstate.is_start || !terminator) {
return;
}
auto* top_node = this->sw_hier_nodes.back().get();
auto new_key = ivstate.is_name.empty()
? lnav::document::section_key_t{top_node->hn_children.size()}
: lnav::document::section_key_t{ivstate.is_name};
this->sw_intervals.emplace_back(
ivstate.is_start.value(),
static_cast<ssize_t>(terminator.value().c_end),
new_key);
auto new_node = this->sw_hier_stage != nullptr
? std::move(this->sw_hier_stage)
: std::make_unique<lnav::document::hier_node>();
auto* retval = new_node.get();
new_node->hn_parent = top_node;
new_node->hn_start = this->sw_intervals.back().start;
new_node->hn_line_number = ivstate.is_line_number;
if (!ivstate.is_name.empty()) {
top_node->hn_named_children.insert({
ivstate.is_name,
retval,
});
}
top_node->hn_children.emplace_back(std::move(new_node));
ivstate.is_start = nonstd::nullopt;
ivstate.is_line_number = 0;
ivstate.is_name.clear();
}
data_scanner sw_scanner;
int sw_depth{0};
size_t sw_line_number{0};
std::vector<element> sw_values{};
std::vector<interval_state> sw_interval_state;
std::vector<lnav::document::section_interval_t> sw_intervals;
std::vector<std::unique_ptr<lnav::document::hier_node>> sw_hier_nodes;
std::unique_ptr<lnav::document::hier_node> sw_hier_stage;
};
metadata
discover_structure(string_fragment content)
{
return structure_walker(content).walk();
}
std::vector<breadcrumb::possibility>
metadata::possibility_provider(const std::vector<section_key_t>& path)
{
std::vector<breadcrumb::possibility> retval;
auto curr_node = lnav::document::hier_node::lookup_path(
this->m_sections_root.get(), path);
if (curr_node) {
auto* parent_node = curr_node.value()->hn_parent;
if (parent_node != nullptr) {
for (const auto& sibling : parent_node->hn_named_children) {
retval.template emplace_back(sibling.first);
}
}
}
return retval;
}
} // namespace document
} // namespace lnav

@ -0,0 +1,112 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lnav_attr_line_breadcrumbs_hh
#define lnav_attr_line_breadcrumbs_hh
#include <map>
#include <string>
#include <vector>
#include "base/attr_line.hh"
#include "base/file_range.hh"
#include "breadcrumb.hh"
#include "intervaltree/IntervalTree.h"
#include "mapbox/variant.hpp"
#include "optional.hpp"
namespace lnav {
namespace document {
using section_key_t = mapbox::util::variant<std::string, size_t>;
using section_interval_t = interval_tree::Interval<file_off_t, section_key_t>;
using sections_tree_t = interval_tree::IntervalTree<file_off_t, section_key_t>;
struct hier_node {
hier_node* hn_parent{nullptr};
file_off_t hn_start{0};
size_t hn_line_number{0};
std::multimap<std::string, hier_node*> hn_named_children;
std::vector<std::unique_ptr<hier_node>> hn_children;
nonstd::optional<hier_node*> lookup_child(section_key_t key) const;
nonstd::optional<size_t> find_line_number(const std::string& str) const
{
auto iter = this->hn_named_children.find(str);
if (iter != this->hn_named_children.end()) {
return nonstd::make_optional(iter->second->hn_line_number);
}
return nonstd::nullopt;
}
nonstd::optional<size_t> find_line_number(size_t index) const
{
if (index < this->hn_children.size()) {
return nonstd::make_optional(
this->hn_children[index]->hn_line_number);
}
return nonstd::nullopt;
}
bool is_named_only() const
{
return this->hn_children.size() == this->hn_named_children.size();
}
static nonstd::optional<const hier_node*> lookup_path(
const hier_node* root, const std::vector<section_key_t>& path);
template<typename F>
static void depth_first(hier_node* root, F func)
{
for (auto& child : root->hn_children) {
depth_first(child.get(), func);
}
func(root);
}
};
struct metadata {
sections_tree_t m_sections_tree;
std::unique_ptr<hier_node> m_sections_root;
std::vector<breadcrumb::possibility> possibility_provider(
const std::vector<section_key_t>& path);
};
metadata discover_metadata(const attr_line_t& al);
metadata discover_structure(string_fragment sf);
} // namespace document
} // namespace lnav
#endif

File diff suppressed because it is too large Load Diff

@ -41,8 +41,8 @@ extern char** environ;
const char* const ENVIRON_CREATE_STMT = R"(
-- Access lnav's environment variables through this table.
CREATE TABLE environ (
name text PRIMARY KEY,
value text
name TEXT PRIMARY KEY,
value TEXT
);
)";

@ -302,7 +302,6 @@ files_sub_source::text_attrs_for_line(textview_curses& tc,
bool selected
= lnav_data.ld_mode == ln_mode_t::FILES && line == tc.get_selection();
const auto& fc = lnav_data.ld_active_files;
auto& vcolors = view_colors::singleton();
const auto dim = tc.get_dimensions();
auto filename_width
= std::min(fc.fc_largest_path_length,
@ -360,8 +359,7 @@ files_sub_source::text_attrs_for_line(textview_curses& tc,
VC_GRAPHIC.value(visible));
if (visible == ACS_DIAMOND) {
value_out.emplace_back(line_range{2, 3},
VC_FOREGROUND.value(
vcolors.ansi_to_theme_color(COLOR_GREEN)));
VC_FOREGROUND.value(COLOR_GREEN));
}
auto lr = line_range{
@ -372,9 +370,7 @@ files_sub_source::text_attrs_for_line(textview_curses& tc,
lr.lr_start = this->fss_last_line_len;
lr.lr_end = -1;
value_out.emplace_back(lr,
VC_FOREGROUND.value(
vcolors.ansi_to_theme_color(COLOR_YELLOW)));
value_out.emplace_back(lr, VC_FOREGROUND.value(COLOR_YELLOW));
}
size_t

@ -335,7 +335,6 @@ filter_sub_source::text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out)
{
auto& vcolors = view_colors::singleton();
textview_curses* top_view = *lnav_data.ld_view_stack.top();
text_sub_source* tss = top_view->get_sub_source();
filter_stack& fs = tss->get_filters();
@ -353,8 +352,7 @@ filter_sub_source::text_attrs_for_line(textview_curses& tc,
line_range lr{2, 3};
value_out.emplace_back(lr, VC_GRAPHIC.value(enabled));
if (tf->is_enabled()) {
value_out.emplace_back(
lr, VC_FOREGROUND.value(vcolors.ansi_to_theme_color(COLOR_GREEN)));
value_out.emplace_back(lr, VC_FOREGROUND.value(COLOR_GREEN));
}
role_t fg_role = tf->get_type() == text_filter::INCLUDE ? role_t::VCR_OK

@ -0,0 +1,525 @@
# lnav
A fancy log file viewer for the terminal.
## Overview
The Logfile Navigator, **lnav**, is an enhanced log file viewer that
takes advantage of any semantic information that can be gleaned from
the files being viewed, such as timestamps and log levels. Using this
extra semantic information, lnav can do things like interleaving
messages from different files, generate histograms of messages over
time, and providing hotkeys for navigating through the file. It is
hoped that these features will allow the user to quickly and
efficiently zero in on problems.
## Opening Paths/URLs
The main arguments to lnav are the files, directories, glob patterns,
or URLs to be viewed. If no arguments are given, the default syslog
file for your system will be opened. These arguments will be polled
periodically so that any new data or files will be automatically
loaded. If a previously loaded file is removed or replaced, it will
be closed and the replacement opened.
Note: When opening SFTP URLs, if the password is not provided for the
host, the SSH agent can be used to do authentication.
## Options
Lnav takes a list of files to view and/or you can use the flag
arguments to load well-known log files, such as the syslog log
files. The flag arguments are:
* `-a` Load all of the most recent log file types.
* `-r` Recursively load files from the given directory hierarchies.
* `-R` Load older rotated log files as well.
When using the flag arguments, lnav will look for the files relative
to the current directory and its parent directories. In other words,
if you are working within a directory that has the well-known log
files, those will be preferred over any others.
If you do not want the default syslog file to be loaded when
no files are specified, you can pass the `-N` flag.
Any files given on the command-line are scanned to determine their log
file format and to create an index for each line in the file. You do
not have to manually specify the log file format. The currently
supported formats are: syslog, apache, strace, tcsh history, and
generic log files with timestamps.
Lnav will also display data piped in on the standard input. The
following options are available when doing so:
* `-t` Prepend timestamps to the lines of data being read in
on the standard input.
* `-w file` Write the contents of the standard input to this file.
To automatically execute queries or lnav commands after the files
have been loaded, you can use the following options:
* `-c cmd` A command, query, or file to execute. The first character
determines the type of operation: a colon (`:`) is used for the
built-in commands; a semi-colon (`;`) for SQL queries; and a
pipe symbol (`|`) for executing a file containing other
commands. For example, to open the file "foo.log" and go
to the tenth line in the file, you can do:
```shell
lnav -c ':goto 10' foo.log
```
This option can be given multiple times to execute multiple
operations in sequence.
* `-f file` A file that contains commands, queries, or files to execute.
This option is a shortcut for `-c '|file'`. You can use a dash
(`-`) to execute commands from the standard input.
To execute commands/queries without opening the interactive text UI,
you can pass the `-n` option. This combination of options allows you to
write scripts for processing logs with lnav. For example, to get a list
of IP addresses that dhclient has bound to in CSV format:
```lnav
#! /usr/bin/lnav -nf
# Usage: dhcp_ip.lnav /var/log/messages
# Only include lines that look like:
# Apr 29 00:31:56 example-centos5 dhclient: bound to 10.1.10.103 -- renewal in 9938 seconds.
:filter-in dhclient: bound to
# The log message parser will extract the IP address
# as col_0, so we select that and alias it to "dhcp_ip".
;SELECT DISTINCT col_0 AS dhcp_ip FROM logline;
# Finally, write the results of the query to stdout.
:write-csv-to -
```
## Display
The main part of the display shows the log lines from the files interleaved
based on time-of-day. New lines are automatically loaded as they are appended
to the files and, if you are viewing the bottom of the files, lnav will scroll
down to display the new lines, much like `tail -f`.
On color displays, the lines will be highlighted as follows:
* Errors will be colored in ${ansi_red}red${ansi_norm};
* warnings will be ${ansi_yellow}yellow${ansi_norm};
* boundaries between days will be ${ansi_underline}underlined${ansi_norm}; and
* various color highlights will be applied to: IP addresses, SQL keywords,
XML tags, file and line numbers in Java backtraces, and quoted strings.
To give you an idea of where you are spatially, the right side of the
display has a proportionally sized 'scroll bar' that indicates your
current position in the files. The scroll bar will also show areas of
the file where warnings or errors are detected by coloring the bar
yellow or red, respectively. Tick marks will also be added to the
left and right hand side of the bar, for search hits and bookmarks.
A bar on the left side is color coded and broken up to indicate which
messages are from the same file. Pressing the left-arrow or `h` will
reveal the source file names for each message and pressing again will
show the full paths.
When at the bottom of the log view, a summary line will be displayed on the
right-hand-side to give you some more information about your logs, including:
how long ago the last message was generated, the number of log files, the
error rate, and how much time the logs cover. The error rate display shows
the errors-per-minute over the last five minutes. A bar chart is also
overlaid on the "Error rate" label to show the error rate over the past ten
seconds. For example, if there have not been many errors in the past five
minutes and there is a sudden spike, the bar chart will fill up completely.
But, if there has been a steady stream of errors, then the chart will only
partially fill based on the recent error frequency.
Above and below the main body are status lines that display:
* the current time;
* the name of the file the top line was pulled from;
* the log format for the top line;
* the current view;
* the line number for the top line in the display;
* the current search hit, the total number of hits, and the search term;
If the view supports filtering, there will be a status line showing the
following:
* the number of enabled filters and the total number of filters;
* the number of lines not displayed because of filtering.
To edit the filters, you can press TAB to change the focus from the main
view to the filter editor. The editor allows you to create, enable/disable,
and delete filters easily.
Finally, the last line on the display is where you can enter search
patterns and execute internal commands, such as converting a
unix-timestamp into a human-readable date. The command-line is
implemented using the readline library, so the usual set of keyboard
shortcuts are available. Most commands and searches also support
tab-completion.
The body of the display is also used to display other content, such
as: the help file, histograms of the log messages over time, and
SQL results. The views are organized into a stack so that any time
you activate a new view with a key press or command, the new view
is pushed onto the stack. Pressing the same key again will pop the
view off of the stack and return you to the previous view. Note
that you can always use `q` to pop the top view off of the stack.
## Default Key Bindings
### Views
| Key(s) | Action |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **?** | View/leave this help message. |
| **q** | Leave the current view or quit the program when in the log file view. |
| Q | Similar to `q`, except it will try to sync the top time between the current and former views. For example, when leaving the spectrogram view with `Q`, the top time in that view will be matched to the top time in the log view. |
| TAB | Toggle focusing on the filter editor or the main view. |
| a/A | Restore the view that was previously popped with `q`/`Q`. The `A` hotkey will try to match the top times between the two views. |
| X | Close the current text file or log file. |
### Spatial Navigation
| Key(s) | Action |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| g/Home | Move to the top of the file. |
| G/End | Move to the end of the file. If the view is already at the end, it will move to the last line. |
| SPACE/PgDn | Move down a page. |
| b/PgUp | Move up a page. |
| j/&DownArrow; | Move down a line. |
| k/&UpArrow; | Move up a line. |
| h/&LeftArrow; | Move to the left. In the log view, moving left will reveal the source log file names for each line. Pressing again will reveal the full path. |
| l/&RightArrow; | Move to the right. |
| H/Shift &LeftArrow; | Move to the left by a smaller increment. |
| L/Shift &RightArrow; | Move to the right by a smaller increment. |
| e/E | Move to the next/previous error. |
| w/W | Move to the next/previous warning. |
| n/N | Move to the next/previous search hit. When pressed repeatedly within a short time, the view will move at least a full page at a time instead of moving to the next hit. |
| f/F | Move to the next/previous file. In the log view, this moves to the next line from a different file. In the text view, this rotates the view to the next file. |
| &gt;/&lt; | Move horizontally to the next/previous search hit. |
| o/O | Move forward/backward to the log message with a matching 'operation ID' (opid) field. |
| u/U | Move forward/backward through any user bookmarks you have added using the 'm' key. This hotkey will also jump to the start of any log partitions that have been created with the 'partition-name' command. |
| s/S | Move to the next/previous "slow down" in the log message rate. A slow down is detected by measuring how quickly the message rate has changed over the previous several messages. For example, if one message is logged every second for five seconds and then the last message arrives five seconds later, the last message will be highlighted as a slow down. |
| {/} | Move to the previous/next location in history. Whenever you jump to a new location in the view, the location will be added to the history. The history is not updated when using only the arrow keys. |
### Chronological Navigation
| Key(s) | Action |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| d/D | Move forward/backward 24 hours from the current position in the log file. |
| 1-6/Shift 1-6 | Move to the next/previous n'th ten minute of the hour. For example, '4' would move to the first log line in the fortieth minute of the current hour in the log. And, '6' would move to the next hour boundary. |
| 7/8 | Move to the previous/next minute. |
| 0/Shift 0 | Move to the next/previous day boundary. |
| r/R | Move forward/backward based on the relative time that was last used with the 'goto' command. For example, executing ':goto a minute later' will move the log view forward a minute and then pressing 'r' will move it forward a minute again. Pressing 'R' will then move the view in the opposite direction, so backwards a minute. |
### Bookmarks
| Key(s) | Action |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| m | Mark/unmark the line at the top of the display. The line will be highlighted with reverse video to indicate that it is a user bookmark. You can use the `u` hotkey to iterate through marks you have added. |
| M | Mark/unmark all the lines between the top of the display and the last line marked/unmarked. |
| J | Mark/unmark the next line after the previously marked line. |
| K | Like `J` except it toggles the mark on the previous line. |
| c | Copy the marked text to the X11 selection buffer or OS X clipboard. |
| C | Clear all marked lines. |
### Display options
| Key(s) | Action |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P | Switch to/from the pretty-printed view of the log or text files currently displayed. In this view, structured data, such as XML, will be reformatted to make it easier to read. |
| t | Switch to/from the text file view. The text file view is for any files that are not recognized as log files. |
| = | Pause/unpause loading of new file data. |
| Ctrl-L | (Lo-fi mode) Exit screen-mode and write the displayed log lines in plain text to the terminal until a key is pressed. Useful for copying long lines from the terminal without picking up any of the extra decorations. |
| T | Toggle the display of the "elapsed time" column that shows the time elapsed since the beginning of the logs or the offset from the previous bookmark. Sharp changes in the message rate are highlighted by coloring the separator between the time column and the log message. A red highlight means the message rate has slowed down and green means it has sped up. You can use the "s/S" hotkeys to scan through the slow downs. |
| i | View/leave a histogram of the log messages over time. The histogram counts the number of displayed log lines for each bucket of time. The bars are layed out horizontally with colored segments representing the different log levels. You can use the `z` hotkey to change the size of the time buckets (e.g. ten minutes, one hour, one day). |
| I | Switch between the log and histogram views while keeping the time displayed at the top of each view in sync. For example, if the top line in the log view is "11:40", hitting `I` will switch to the histogram view and scrolled to display "11:00" at the top (if the zoom level is hours). |
| z/Shift Z | Zoom in or out one step in the histogram view. |
| v | Switch to/from the SQL result view. |
| V | Switch between the log and SQL result views while keeping the top line number in the log view in sync with the log_line column in the SQL view. For example, doing a query that selects for "log_idle_msecs" and "log_line", you can move the top of the SQL view to a line and hit 'V' to switch to the log view and move to the line number that was selected in the "log_line" column. If there is no "log_line" column, lnav will find the first column with a timestamp and move to corresponding time in the log view. |
| TAB/Shift TAB | In the SQL result view, cycle through the columns that are graphed. Initially, all number values are displayed in a stacked graph. Pressing TAB will change the display to only graph the first column. Repeatedly pressing TAB will cycle through the columns until they are all graphed again. |
| p | In the log view: enable or disable the display of the fields that the log message parser knows about or has discovered. This overlay is temporarily enabled when the semicolon key (;) is pressed so that it is easier to write queries. |
| | In the DB view: enable or disable the display of values in columns containing JSON-encoded values in the top row. The overlay will display the JSON-Pointer reference and value for all fields in the JSON data. |
| CTRL-W | Toggle word-wrapping. |
| CTRL-P | Show/hide the data preview panel that may be opened when entering commands or SQL queries. |
| CTRL-F | Toggle the enabled/disabled state of all filters in the current view. |
| x | Toggle the hiding of log message fields. The hidden fields will be replaced with three bullets and highlighted in yellow. |
| F2 | Toggle mouse support. |
### Query
| Key(s) | Action |
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **/**_regexp_ | Start a search for the given regular expression. The search is live, so when there is a pause in typing, the currently running search will be canceled and a new one started. The first ten lines that match the search will be displayed in the preview window at the bottom of the view. History is maintained for your searches so you can rerun them easily. Words that are currently displayed are also available for tab-completion, so you can easily search for values without needing to copy-and-paste the string. If there is an error encountered while trying to interpret the expression, the error will be displayed in red on the status line. While the search is active, the 'hits' field in the status line will be green, when finished it will turn back to black. |
| **:**&lt;command&gt; | Execute an internal command. The commands are listed below. History is also supported in this context as well as tab-completion for commands and some arguments. The result of the command replaces the command you typed. |
| **;**&lt;sql&gt; | Execute an SQL query. Most supported log file formats provide a sqlite virtual table backend that can be used in queries. See the SQL section below for more information. |
| **&VerticalLine;**&lt;script&gt; [arg1...] | Execute an lnav script contained in a format directory (e.g. \~/.lnav/formats/default). The script can contain lines starting with `:`, `;`, or `\|` to execute commands, SQL queries or execute other files in lnav. Any values after the script name are treated as arguments can be referenced in the script using `\$1`, `\$2`, and so on, like in a shell script. |
| CTRL+], ESCAPE | Abort command-line entry started with `/`, `:`, `;`, or `\|`. |
Note: The regular expression format used by is PCRE
(Perl-Compatible Regular Expressions). For example,
if you wanted to search for ethernet device names,
regardless of their ID number, you can type:
eth\\d+
You can find more information about Perl regular
expressions at:
http://perldoc.perl.org/perlre.html
If the search string is not valid PCRE, a search
is done for the exact string instead of doing a
regex search.
## Session
| Key(s) | Action |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| CTRL-R | Reset the session state. This will save the current session state (filters, highlights) and then reset the state to the factory default. |
## Filter Editor
The following hotkeys are only available when the focus is on the filter
editor. You can change the focus by pressing TAB.
| Key(s) | Action |
| ------------- | ------------------------------------------------------------------- |
| q | Switch the focus back to the main view. |
| j/&DownArrow; | Select the next filter. |
| k/&UpArrow; | Select the previous filter. |
| o | Create a new "out" filter. |
| i | Create a new "in" filter . |
| SPACE | Toggle the enabled/disabled state of the currently selected filter. |
| t | Toggle the type of filter between "in" and "out". |
| ENTER | Edit the selected filter. |
| D | Delete the selected filter. |
## Mouse Support (experimental)
If you are using Xterm, or a compatible terminal, you can use the mouse to
mark lines of text and move the view by grabbing the scrollbar.
NOTE: You need to manually enable this feature by setting the LNAV_EXP
environment variable to "mouse". F2 toggles mouse support.
## SQL Queries (experimental)
Lnav has support for performing SQL queries on log files using the
Sqlite3 "virtual" table feature. For all supported log file types,
lnav will create tables that can be queried using the subset of SQL
that is supported by Sqlite3. For example, to get the top ten URLs
being accessed in any loaded Apache log files, you can execute:
```lnav
;SELECT cs_uri_stem, count(*) AS total FROM access_log
GROUP BY cs_uri_stem ORDER BY total DESC LIMIT 10;
```
The query result view shows the results and graphs any numeric
values found in the result, much like the histogram view.
The builtin set of log tables are listed below. Note that only the
log messages that match a particular format can be queried by a
particular table. You can find the file format and table name for
the top log message by looking in the upper right hand corner of the
log file view.
Some commonly used format tables are:
| Name | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| access_log | Apache common access log format |
| syslog_log | Syslog format |
| strace_log | Strace log format |
| generic_log | 'Generic' log format. This table contains messages from files that have a very simple format with a leading timestamp followed by the message. |
NOTE: You can get a dump of the schema for the internal tables, and
any attached databases, by running the `.schema` SQL command.
The columns available for the top log line in the view will
automatically be displayed after pressing the semicolon (`;`) key.
All log tables contain at least the following columns:
| Column | Description |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| log_line | The line number in the file, starting at zero. |
| log_part | The name of the partition. You can change this column using an UPDATE SQL statement or with the 'partition-name' command. After a value is set, the following log messages will have the same partition name up until another name is set. |
| log_time | The time of the log entry. |
| log_idle_msecs | The amount of time, in milliseconds, between the current log message and the previous one. |
| log_level | The log level (e.g. info, error, etc...). |
| log_mark | The bookmark status for the line. This column can be written to using an UPDATE query. |
| log_path | The full path to the file. |
| log_text | The raw line of text. Note that this column is not included in the result of a 'select *', but it does exist. |
The following tables include the basic columns as listed above and
include a few more columns since the log file format is more
structured.
* `syslog_log`
| Column | Description |
| ------------ | ---------------------------------------------------- |
| log_hostname | The hostname the message was received from. |
| log_procname | The name of the process that sent the message. |
| log_pid | The process ID of the process that sent the message. |
* `access_log` (The column names are the same as those in the
Microsoft LogParser tool.)
| Column | Description |
| ------------- | ----------------------------------------- |
| c_ip | The client IP address. |
| cs_username | The client user name. |
| cs_method | The HTTP method. |
| cs_uri_stem | The stem portion of the URI. |
| cs_uri_query | The query portion of the URI. |
| cs_version | The HTTP version string. |
| sc_status | The status number returned to the client. |
| sc_bytes | The number of bytes sent to the client. |
| cs_referrer | The URL of the referring page. |
| cs_user_agent | The user agent string. |
* `strace_log` (Currently, you need to run strace with the `-tt -T`
options so there are timestamps for each function call.)
| Column | Description |
| ----------- | ---------------------------------------- |
| funcname | The name of the syscall. |
| result | The result code. |
| duration | The amount of time spent in the syscall. |
| arg0 - arg9 | The arguments passed to the syscall. |
These tables are created dynamically and not stored in memory or on
disk. If you would like to persist some information from the tables,
you can attach another database and create tables in that database.
For example, if you wanted to save the results from the earlier
example of a top ten query into the "/tmp/topten.db" file, you can do:
```lnav
;ATTACH DATABASE '/tmp/topten.db' AS topten;
;CREATE TABLE topten.foo AS SELECT cs_uri_stem, count(*) AS total
FROM access_log GROUP BY cs_uri_stem ORDER BY total DESC
LIMIT 10;
```
## Dynamic logline Table (experimental)
(NOTE: This feature is still very new and not completely reliable yet,
use with care.)
For log formats that lack message structure, lnav can parse the log
message and attempt to extract any data fields that it finds. This
feature is available through the `logline` log table. This table is
dynamically created and defined based on the message at the top of
the log view. For example, given the following log message from "sudo",
lnav will create the "logline" table with columns for "TTY", "PWD",
"USER", and "COMMAND":
```
May 24 06:48:38 Tim-Stacks-iMac.local sudo[76387]: stack : TTY=ttys003 ; PWD=/Users/stack/github/lbuild ; USER=root ; COMMAND=/bin/echo Hello, World!
```
Queries executed against this table will then only return results for
other log messages that have the same format. So, if you were to
execute the following query while viewing the above line, you might
get the following results:
```lnav
;SELECT USER,COMMAND FROM logline;
```
| USER | COMMAND |
| ---- | ------------------------- |
| root | /bin/echo Hello, World! |
| mal | /bin/echo Goodbye, World! |
The log parser works by examining each message for key/value pairs
separated by an equal sign (=) or a colon (:). For example, in the
previous example of a "sudo" message, the parser sees the "USER=root"
string as a pair where the key is "USER" and the value is "root".
If no pairs can be found, then anything that looks like a value is
extracted and assigned a numbered column. For example, the following
line is from "dhcpd":
```
Sep 16 22:35:57 drill dhcpd: DHCPDISCOVER from 00:16:ce:54:4e:f3 via hme3
```
In this case, the lnav parser recognizes that "DHCPDISCOVER", the MAC
address and the "hme3" device name are values and not normal words. So,
it builds a table with three columns for each of these values. The
regular words in the message, like "from" and "via", are then used to
find other messages with a similar format.
If you would like to execute queries against log messages of different
formats at the same time, you can use the 'create-logline-table' command
to permanently create a table using the top line of the log view as a
template.
## Other SQL Features
Environment variables can be used in SQL statements by prefixing the
variable name with a dollar-sign (\$). For example, to read the value of
the `HOME` variable, you can do:
```lnav
;SELECT \$HOME;
```
To select the syslog messages that have a hostname field that is equal
to the `HOSTNAME` variable:
```lnav
;SELECT * FROM syslog_log WHERE log_hostname = \$HOSTNAME;
```
NOTE: Variable substitution is done for fields in the query and is not
a plain text substitution. For example, the following statement
WILL NOT WORK:
```lnav
;SELECT * FROM \$TABLE_NAME; -- Syntax error
```
Access to lnav's environment variables is also available via the "environ"
table. The table has two columns (name, value) and can be read and written
to using SQL SELECT, INSERT, UPDATE, and DELETE statements. For example,
to set the "FOO" variable to the value "BAR":
```lnav
;INSERT INTO environ SELECT 'FOO', 'BAR';
```
As a more complex example, you can set the variable "LAST" to the last
syslog line number by doing:
```lnav
;INSERT INTO environ SELECT 'LAST', (SELECT max(log_line) FROM syslog_log);
```
A delete will unset the environment variable:
```lnav
;DELETE FROM environ WHERE name='LAST';
```
The table allows you to easily use the results of a SQL query in lnav
commands, which is especially useful when scripting lnav.
## Contact
For more information, visit the lnav website at:
http://lnav.org
For support questions, email:
lnav@googlegroups.com
support@lnav.org

@ -33,6 +33,7 @@
#include "help_text_formatter.hh"
#include "base/ansi_scrubber.hh"
#include "base/attr_line.builder.hh"
#include "base/string_util.hh"
#include "config.h"
#include "fmt/format.h"
@ -85,6 +86,7 @@ format_help_text_for_term(const help_text& ht,
{
static const size_t body_indent = 2;
attr_line_builder alb(out);
text_wrap_settings tws;
size_t start_index = out.get_string().length();
@ -92,11 +94,9 @@ format_help_text_for_term(const help_text& ht,
switch (ht.ht_context) {
case help_context_t::HC_COMMAND: {
out.append("Synopsis"_h2)
.append("\n")
.append(body_indent, ' ')
.append(":")
.append(lnav::roles::symbol(ht.ht_name));
auto line_start = out.al_string.length();
out.append(":").append(lnav::roles::symbol(ht.ht_name));
for (const auto& param : ht.ht_parameters) {
out.append(" ");
if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
@ -116,26 +116,27 @@ format_help_text_for_term(const help_text& ht,
out.append("]");
}
}
out.append(" - ")
out.with_attr(string_attr{
line_range{(int) line_start, (int) out.get_string().length()},
VC_ROLE.value(role_t::VCR_H3),
});
alb.append("\n")
.append(
lnav::roles::table_border(repeat("\u2550", tws.tws_width)))
.append("\n")
.indent(body_indent)
.append(attr_line_t::from_ansi_str(ht.ht_summary),
&tws.with_indent(body_indent + 2))
&tws.with_indent(body_indent))
.append("\n");
break;
}
case help_context_t::HC_SQL_FUNCTION:
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
size_t line_start = body_indent;
auto line_start = out.al_string.length();
bool break_all = false;
bool needs_comma = false;
if (!synopsis_only) {
out.append("Synopsis"_h2).append("\n");
}
line_start = out.length();
out.append(body_indent, ' ')
.append(lnav::roles::symbol(ht.ht_name))
.append("(");
out.append(lnav::roles::symbol(ht.ht_name)).append("(");
for (const auto& param : ht.ht_parameters) {
if (!param.ht_flag_name && needs_comma) {
out.append(", ");
@ -146,7 +147,7 @@ format_help_text_for_term(const help_text& ht,
{
out.append("\n");
line_start = out.get_string().length();
out.append(body_indent + strlen(ht.ht_name) + 1, ' ');
alb.indent(body_indent + strlen(ht.ht_name) + 1);
break_all = true;
}
if (param.ht_flag_name) {
@ -168,22 +169,33 @@ format_help_text_for_term(const help_text& ht,
}
needs_comma = true;
}
out.append(") -- ");
out.append(")");
out.with_attr(string_attr{
line_range{(int) line_start, (int) out.get_string().length()},
VC_ROLE.value(role_t::VCR_H3),
});
if (break_all) {
out.append("\n").append(body_indent + strlen(ht.ht_name) + 1,
' ');
alb.append("\n")
.append(lnav::roles::table_border(
repeat("\u2550", tws.tws_width)))
.append("\n")
.indent(body_indent + strlen(ht.ht_name) + 1);
} else {
alb.append("\n")
.append(lnav::roles::table_border(
repeat("\u2550", tws.tws_width)))
.append("\n")
.indent(body_indent);
}
out.append(attr_line_t::from_ansi_str(ht.ht_summary),
&tws.with_indent(body_indent + 2))
&tws.with_indent(body_indent))
.append("\n");
break;
}
case help_context_t::HC_SQL_COMMAND: {
out.append("Synopsis"_h2)
.append("\n")
.append(body_indent, ' ')
.append(";")
.append(lnav::roles::symbol(ht.ht_name));
auto line_start = out.al_string.length();
out.append(";").append(lnav::roles::symbol(ht.ht_name));
for (const auto& param : ht.ht_parameters) {
out.append(" ");
if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
@ -203,7 +215,15 @@ format_help_text_for_term(const help_text& ht,
out.append("]");
}
}
out.append(" - ")
out.with_attr(string_attr{
line_range{(int) line_start, (int) out.get_string().length()},
VC_ROLE.value(role_t::VCR_H3),
});
alb.append("\n")
.append(
lnav::roles::table_border(repeat("\u2550", tws.tws_width)))
.append("\n")
.indent(body_indent)
.append(attr_line_t::from_ansi_str(ht.ht_summary),
&tws.with_indent(body_indent + 2))
.append("\n");
@ -211,14 +231,10 @@ format_help_text_for_term(const help_text& ht,
}
case help_context_t::HC_SQL_INFIX:
case help_context_t::HC_SQL_KEYWORD: {
size_t line_start = body_indent;
size_t line_start = out.get_string().length();
bool break_all = false;
bool is_infix = ht.ht_context == help_context_t::HC_SQL_INFIX;
if (!synopsis_only) {
out.append("Synopsis"_h2).append("\n");
}
out.append(body_indent, ' ');
if (is_infix) {
out.append(ht.ht_name);
} else {
@ -232,7 +248,7 @@ format_help_text_for_term(const help_text& ht,
{
out.append("\n");
line_start = out.get_string().length();
out.append(body_indent + strlen(ht.ht_name) + 1, ' ');
alb.indent(body_indent + strlen(ht.ht_name) + 1);
break_all = true;
}
if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE
@ -312,13 +328,20 @@ format_help_text_for_term(const help_text& ht,
out.append("]");
}
}
if (!synopsis_only) {
out.append("\n\n")
.append(body_indent, ' ')
out.with_attr(string_attr{
line_range{(int) line_start, (int) out.get_string().length()},
VC_ROLE.value(role_t::VCR_H3),
});
if (synopsis_only) {
out.append("\n");
} else {
alb.append("\n")
.append(lnav::roles::table_border(
repeat("\u2550", tws.tws_width)))
.append("\n")
.indent(body_indent)
.append(ht.ht_summary, &tws)
.append("\n");
} else {
out.append("\n");
}
break;
}
@ -334,8 +357,8 @@ format_help_text_for_term(const help_text& ht,
= std::max(strlen(param.ht_name), max_param_name_width);
}
out.append(ht.ht_parameters.size() == 1 ? "Parameter"_h2
: "Parameters"_h2)
out.append(ht.ht_parameters.size() == 1 ? "Parameter"_h4
: "Parameters"_h4)
.append("\n");
for (const auto& param : ht.ht_parameters) {
@ -343,7 +366,7 @@ format_help_text_for_term(const help_text& ht,
continue;
}
out.append(body_indent, ' ')
alb.indent(body_indent)
.append(lnav::roles::variable(param.ht_name))
.append(max_param_name_width - strlen(param.ht_name), ' ')
.append(" ")
@ -360,7 +383,7 @@ format_help_text_for_term(const help_text& ht,
= std::max(strlen(result.ht_name), max_result_name_width);
}
out.append(ht.ht_results.size() == 1 ? "Result"_h2 : "Results"_h2)
out.append(ht.ht_results.size() == 1 ? "Result"_h4 : "Results"_h4)
.append("\n");
for (const auto& result : ht.ht_results) {
@ -368,7 +391,7 @@ format_help_text_for_term(const help_text& ht,
continue;
}
out.append(body_indent, ' ')
alb.indent(body_indent)
.append(lnav::roles::variable(result.ht_name))
.append(max_result_name_width - strlen(result.ht_name), ' ')
.append(" ")
@ -398,17 +421,18 @@ format_help_text_for_term(const help_text& ht,
}
stable_sort(related_refs.begin(), related_refs.end());
out.append("See Also"_h2).append("\n").append(body_indent, ' ');
alb.append("See Also"_h4).append("\n").indent(body_indent);
bool first = true;
size_t line_start = out.length();
size_t line_start = out.get_string().length();
for (const auto& ref : related_refs) {
if (!first) {
out.append(", ");
}
if ((out.length() - line_start + ref.length()) > width) {
out.append("\n").append(body_indent, ' ');
line_start = out.length();
if ((out.get_string().length() - line_start + ref.length()) > width)
{
alb.append("\n").indent(body_indent);
line_start = out.get_string().length();
}
out.append(lnav::roles::symbol(ref));
first = false;
@ -422,59 +446,58 @@ format_example_text_for_term(const help_text& ht,
size_t width,
attr_line_t& out)
{
text_wrap_settings tws;
if (ht.ht_example.empty()) {
return;
}
tws.with_width(width);
attr_line_builder alb(out);
int count = 1;
if (!ht.ht_example.empty()) {
int count = 1;
out.append(ht.ht_example.size() == 1 ? "Example"_h4 : "Examples"_h4)
.append("\n");
for (const auto& ex : ht.ht_example) {
attr_line_t ex_line(ex.he_cmd);
const char* prompt = "";
text_wrap_settings tws;
out.append(ht.ht_example.size() == 1 ? "Example"_h2 : "Examples"_h2)
.append("\n");
for (const auto& ex : ht.ht_example) {
attr_line_t ex_line(ex.he_cmd);
size_t keyword_offset = 0;
const char* space = strchr(ex.he_cmd, ' ');
const char* prompt = "";
if (space) {
keyword_offset = space - ex.he_cmd;
}
if (count > 1) {
out.append("\n");
}
switch (ht.ht_context) {
case help_context_t::HC_COMMAND:
ex_line.insert(0, 1, ' ');
ex_line.insert(0, 1, ':');
ex_line.insert(1, ht.ht_name);
readline_command_highlighter(ex_line, 0);
break;
case help_context_t::HC_SQL_INFIX:
case help_context_t::HC_SQL_KEYWORD:
case help_context_t::HC_SQL_FUNCTION:
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
readline_sqlite_highlighter(ex_line, 0);
prompt = ";";
break;
default:
break;
}
tws.with_width(width);
if (count > 1) {
out.append("\n");
}
switch (ht.ht_context) {
case help_context_t::HC_COMMAND:
ex_line.insert(0, 1, ' ');
ex_line.insert(0, 1, ':');
ex_line.insert(1, ht.ht_name);
readline_command_highlighter(ex_line, 0);
break;
case help_context_t::HC_SQL_INFIX:
case help_context_t::HC_SQL_KEYWORD:
case help_context_t::HC_SQL_FUNCTION:
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
readline_sqlite_highlighter(ex_line, 0);
prompt = ";";
break;
default:
break;
}
out.append("#")
.append(fmt::to_string(count))
.append(" ")
.append(ex.he_description, &tws.with_indent(3))
.append(":\n ")
.append(prompt)
.append(ex_line, &tws.with_indent(3 + keyword_offset + 1))
.append("\n")
.append(3, ' ')
.append(eval(ht, ex), &tws.with_indent(3))
.append("\n");
ex_line.pad_to(50).with_attr_for_all(
VC_ROLE.value(role_t::VCR_QUOTED_CODE));
alb.append("#")
.append(fmt::to_string(count))
.append(" ")
.append(ex.he_description, &tws.with_indent(3))
.append(":\n")
.indent(3)
.append(prompt, VC_ROLE.value(role_t::VCR_QUOTED_CODE))
.append(ex_line, &tws.with_indent(3).with_padding_indent(3))
.append("\n")
.indent(3)
.append(eval(ht, ex), &tws.with_indent(3))
.append("\n");
count += 1;
}
count += 1;
}
}

@ -66,24 +66,24 @@ INSERT INTO http_status_codes VALUES (510, 'Not Extended');
INSERT INTO http_status_codes VALUES (511, 'Network Authentication Required');
CREATE TABLE lnav_example_log (
log_line integer PRIMARY KEY,
log_part text collate naturalnocase,
log_line INTEGER PRIMARY KEY,
log_part TEXT collate naturalnocase,
log_time datetime,
log_actual_time datetime hidden,
log_idle_msecs int,
log_level text collate loglevel,
log_level TEXT collate loglevel,
log_mark boolean,
log_comment TEXT,
log_tags TEXT,
log_filters TEXT,
ex_procname text collate 'BINARY',
ex_duration integer,
ex_procname TEXT collate 'BINARY',
ex_duration INTEGER,
log_time_msecs int hidden,
log_path text hidden collate naturalnocase,
log_text text hidden,
log_body text hidden
log_path TEXT hidden collate naturalnocase,
log_text TEXT hidden,
log_body TEXT hidden
);
CREATE VIEW lnav_top_view AS

@ -3370,7 +3370,8 @@ timeslice(*time*, *slice*)
.. code-block:: custsqlite
;SELECT timeslice(log_time_msecs, '5m') AS slice, count(1) FROM lnav_example_log GROUP BY slice
;SELECT timeslice(log_time_msecs, '5m') AS slice, count(1)
FROM lnav_example_log GROUP BY slice
slice count(1)
2017-02-03 04:05:00.000 2
2017-02-03 04:25:00.000 1

@ -475,7 +475,8 @@ listview_curses::set_top(vis_line_t top, bool suppress_flash)
} else if (this->lv_top != top) {
this->lv_top = top;
if (this->lv_selectable) {
if (this->lv_selection < top) {
if (this->lv_selection < 0_vl) {
} else if (this->lv_selection < top) {
this->lv_selection = top;
} else {
auto bot = this->get_bottom();
@ -545,3 +546,22 @@ listview_curses::rows_available(vis_line_t line,
return retval;
}
void
listview_curses::scroll_selection_into_view()
{
unsigned long width;
vis_line_t height;
this->get_dimensions(height, width);
if (height <= 0) {
return;
}
if (this->lv_selection < 0_vl) {
this->set_top(0_vl);
} else if (this->lv_selection >= (this->lv_top + height - 1)) {
this->set_top(this->lv_selection - height + 2_vl, true);
} else if (this->lv_selection < this->lv_top) {
this->set_top(this->lv_selection, true);
}
}

@ -203,21 +203,7 @@ public:
}
}
void scroll_selection_into_view()
{
unsigned long width;
vis_line_t height;
this->get_dimensions(height, width);
if (height <= 0) {
return;
}
if (this->lv_selection >= (this->lv_top + height - 1)) {
this->set_top(this->lv_selection - height + 2_vl, true);
} else if (this->lv_selection < this->lv_top) {
this->set_top(this->lv_selection, true);
}
}
void scroll_selection_into_view();
void shift_selection(int offset);

@ -508,11 +508,8 @@ usage()
ex1_term.append(lnav::roles::ok("$"))
.append(" ")
.append(lnav::roles::file("lnav"))
.pad_to(40);
ex1_term.get_attrs().emplace_back(line_range{0, (int) ex1_term.length()},
VC_BACKGROUND.value(COLOR_BLACK));
ex1_term.get_attrs().emplace_back(line_range{0, (int) ex1_term.length()},
VC_FOREGROUND.value(COLOR_WHITE));
.pad_to(40)
.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
attr_line_t ex2_term;
@ -521,11 +518,8 @@ usage()
.append(lnav::roles::file("lnav"))
.append(" ")
.append(lnav::roles::file("/var/log"))
.pad_to(40);
ex2_term.get_attrs().emplace_back(line_range{0, (int) ex2_term.length()},
VC_BACKGROUND.value(COLOR_BLACK));
ex2_term.get_attrs().emplace_back(line_range{0, (int) ex2_term.length()},
VC_FOREGROUND.value(COLOR_WHITE));
.pad_to(40)
.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
attr_line_t ex3_term;
@ -536,11 +530,8 @@ usage()
.append(lnav::roles::file("lnav"))
.append(" ")
.append("-t"_symbol)
.pad_to(40);
ex3_term.get_attrs().emplace_back(line_range{0, (int) ex3_term.length()},
VC_BACKGROUND.value(COLOR_BLACK));
ex3_term.get_attrs().emplace_back(line_range{0, (int) ex3_term.length()},
VC_FOREGROUND.value(COLOR_WHITE));
.pad_to(40)
.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
attr_line_t usage_al;
@ -1964,7 +1955,8 @@ main(int argc, char* argv[])
auto cmd_appender
= [](std::string cmd) { lnav_data.ld_commands.emplace_back(cmd); };
auto cmd_validator = [&arg_errors](std::string cmd) -> std::string {
static const auto ARG_SRC = intern_string::lookup("arg");
static const auto ARG_SRC
= intern_string::lookup("command-line argument");
if (cmd.empty()) {
return "empty commands are not allowed";
@ -1977,6 +1969,7 @@ main(int argc, char* argv[])
case '|':
break;
default:
cmd.push_back(' ');
arg_errors.emplace_back(
lnav::console::user_message::error(
attr_line_t("invalid value for ")
@ -1984,8 +1977,9 @@ main(int argc, char* argv[])
.append(" option"))
.with_snippet(lnav::console::snippet::from(
ARG_SRC,
attr_line_t(" -c ")
.append(cmd)
attr_line_t()
.append(" -c "_quoted_code)
.append(lnav::roles::quoted_code(cmd))
.append("\n")
.append(4, ' ')
.append(lnav::roles::error(
@ -2261,7 +2255,7 @@ main(int argc, char* argv[])
lnav_data.ld_log_source.set_exec_context(&lnav_data.ld_exec_context);
lnav_data.ld_views[LNV_HELP]
.set_sub_source(&lnav_data.ld_help_source)
.set_word_wrap(true);
.set_word_wrap(false);
auto log_fos = new field_overlay_source(lnav_data.ld_log_source,
lnav_data.ld_text_source);
if (lnav_data.ld_flags & LNF_HEADLESS) {

@ -110,9 +110,10 @@ rebuild_hist()
lss.reload_index_delegate();
}
class textfile_callback {
class textfile_callback : public textfile_sub_source::scan_callback {
public:
void closed_files(const std::vector<std::shared_ptr<logfile>>& files)
void closed_files(
const std::vector<std::shared_ptr<logfile>>& files) override
{
for (const auto& lf : files) {
log_info("closed text files: %s", lf->get_filename().c_str());
@ -120,7 +121,7 @@ public:
lnav_data.ld_active_files.close_files(files);
}
void promote_file(const std::shared_ptr<logfile>& lf)
void promote_file(const std::shared_ptr<logfile>& lf) override
{
if (lnav_data.ld_log_source.insert_file(lf)) {
this->did_promotion = true;
@ -150,7 +151,7 @@ public:
}
}
void scanned_file(const std::shared_ptr<logfile>& lf)
void scanned_file(const std::shared_ptr<logfile>& lf) override
{
if (!lnav_data.ld_files_to_front.empty()
&& lnav_data.ld_files_to_front.front().first == lf->get_filename())

@ -640,6 +640,20 @@ static const struct json_path_container theme_styles_handlers = {
return &root->lt_style_header[5];
})
.with_children(style_config_handlers),
yajlpp::property_handler("hr")
.with_description("Styling for horizontal rules")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_hr;
})
.with_children(style_config_handlers),
yajlpp::property_handler("hyperlink")
.with_description("Styling for hyperlinks")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_hyperlink;
})
.with_children(style_config_handlers),
yajlpp::property_handler("list-glyph")
.with_description("Styling for glyphs that prefix a list item")
.with_obj_provider<style_config, lnav_theme>(
@ -654,9 +668,65 @@ static const struct json_path_container theme_styles_handlers = {
return &root->lt_style_breadcrumb;
})
.with_children(style_config_handlers),
yajlpp::property_handler("table-border")
.with_description("Styling for table borders")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_table_border;
})
.with_children(style_config_handlers),
yajlpp::property_handler("table-header")
.with_description("Styling for table headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_table_header;
})
.with_children(style_config_handlers),
yajlpp::property_handler("quote-border")
.with_description("Styling for quoted-block borders")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_quote_border;
})
.with_children(style_config_handlers),
yajlpp::property_handler("quoted-text")
.with_description("Styling for quoted text blocks")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_quoted_text;
})
.with_children(style_config_handlers),
yajlpp::property_handler("footnote-border")
.with_description("Styling for footnote borders")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_footnote_border;
})
.with_children(style_config_handlers),
yajlpp::property_handler("footnote-text")
.with_description("Styling for footnote text")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_footnote_text;
})
.with_children(style_config_handlers),
};
static const struct json_path_container theme_syntax_styles_handlers = {
yajlpp::property_handler("quoted-code")
.with_description("Styling for quoted code blocks")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_quoted_code;
})
.with_children(style_config_handlers),
yajlpp::property_handler("code-border")
.with_description("Styling for quoted-code borders")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_code_border;
})
.with_children(style_config_handlers),
yajlpp::property_handler("keyword")
.with_description("Styling for keywords in source files")
.with_obj_provider<style_config, lnav_theme>(

@ -495,10 +495,11 @@ logfile::rebuild_index(nonstd::optional<ui_clock::time_point> deadline)
this->lf_text_format
= avail_data
.map([](const shared_buffer_ref& avail_sbr)
.map([path = this->get_path()](
const shared_buffer_ref& avail_sbr)
-> text_format_t {
return detect_text_format(avail_sbr.get_data(),
avail_sbr.length());
return detect_text_format(
avail_sbr.to_string_fragment(), path);
})
.unwrapOr(text_format_t::TF_UNKNOWN);
log_debug("setting text format to %d", this->lf_text_format);
@ -647,6 +648,19 @@ logfile::read_line(logfile::iterator ll)
}
}
Result<std::string, std::string>
logfile::read_file()
{
if (this->lf_stat.st_size > line_buffer::MAX_LINE_BUFFER_SIZE) {
return Err(std::string("file is too large to read"));
}
auto retval
= TRY(this->lf_line_buffer.read_range({0, this->lf_stat.st_size}));
return Ok(to_string(retval));
}
void
logfile::read_full_message(logfile::const_iterator ll,
shared_buffer_ref& msg_out,
@ -842,3 +856,39 @@ logfile::original_line_time(logfile::iterator ll)
return ll->get_timeval();
}
nonstd::optional<logfile::const_iterator>
logfile::line_for_offset(file_off_t off) const
{
struct cmper {
bool operator()(const file_off_t& lhs, const logline& rhs) const
{
return lhs < rhs.get_offset();
}
bool operator()(const logline& lhs, const file_off_t& rhs) const
{
return lhs.get_offset() < rhs;
}
};
if (this->lf_index.empty()) {
return nonstd::nullopt;
}
auto iter = std::lower_bound(
this->lf_index.begin(), this->lf_index.end(), off, cmper{});
if (iter == this->lf_index.end()) {
if (this->lf_index.back().get_offset() <= off
&& off < this->lf_index_size) {
return nonstd::make_optional(iter);
}
return nonstd::nullopt;
}
if (off < iter->get_offset() && iter != this->lf_index.begin()) {
--iter;
}
return nonstd::make_optional(iter);
}

@ -144,6 +144,8 @@ public:
file_off_t get_index_size() const { return this->lf_index_size; }
nonstd::optional<const_iterator> line_for_offset(file_off_t off) const;
/**
* @return The detected format, rebuild_index() must be called before this
* will return a value other than NULL.
@ -249,6 +251,8 @@ public:
Result<shared_buffer_ref, std::string> read_line(iterator ll);
Result<std::string, std::string> read_file();
iterator line_base(iterator ll)
{
auto retval = ll;

@ -380,9 +380,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
continue;
}
value_out.emplace_back(
token_attr.sa_range,
VC_ROLE.value(role_t::VCR_INVALID_MSG));
value_out.emplace_back(token_attr.sa_range,
VC_ROLE.value(role_t::VCR_INVALID_MSG));
}
}
@ -410,9 +409,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
this->lss_token_value.c_str(), this->lss_token_value.length());
}
value_out.emplace_back(
ident_range,
VC_ROLE.value(role_t::VCR_IDENTIFIER));
value_out.emplace_back(ident_range,
VC_ROLE.value(role_t::VCR_IDENTIFIER));
}
if (this->lss_token_shift_size) {
@ -453,8 +451,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
vis_line_t(row))) {
lr.lr_start = 0;
lr.lr_end = 1;
value_out.emplace_back(lr,
VC_STYLE.value(A_REVERSE));
value_out.emplace_back(lr, VC_STYLE.value(A_REVERSE));
}
}
}
@ -484,10 +481,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
shift_string_attrs(value_out, 0, time_offset_end);
value_out.emplace_back(
lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME));
value_out.emplace_back(line_range(12, 13),
VC_GRAPHIC.value(ACS_VLINE));
value_out.emplace_back(lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME));
value_out.emplace_back(line_range(12, 13), VC_GRAPHIC.value(ACS_VLINE));
role_t bar_role = role_t::VCR_NONE;
@ -502,8 +497,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
break;
}
if (bar_role != role_t::VCR_NONE) {
value_out.emplace_back(line_range(12, 13),
VC_ROLE.value(bar_role));
value_out.emplace_back(line_range(12, 13), VC_ROLE.value(bar_role));
}
}
@ -549,9 +543,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
= find_string_attr_range(value_out, &logline::L_TIMESTAMP);
if (time_range.lr_end != -1) {
value_out.emplace_back(
time_range,
VC_ROLE.value(role_t::VCR_ADJUSTED_TIME));
value_out.emplace_back(time_range,
VC_ROLE.value(role_t::VCR_ADJUSTED_TIME));
}
}
@ -560,9 +553,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
= find_string_attr_range(value_out, &logline::L_TIMESTAMP);
if (time_range.lr_end != -1) {
value_out.emplace_back(
time_range,
VC_ROLE.value(role_t::VCR_SKEWED_TIME));
value_out.emplace_back(time_range,
VC_ROLE.value(role_t::VCR_SKEWED_TIME));
}
}
@ -584,8 +576,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
color = COLOR_GREEN;
} else {
color = COLOR_RED;
value_out.emplace_back(
line_range{0, 1}, VC_STYLE.value(A_BLINK));
value_out.emplace_back(line_range{0, 1},
VC_STYLE.value(A_BLINK));
}
}
value_out.emplace_back(line_range{0, 1},
@ -2039,133 +2031,243 @@ logfile_sub_source::text_crumbs_for_line(int line,
}
auto line_pair_opt = this->find_line_with_file(vis_line_t(line));
if (line_pair_opt) {
auto line_pair = line_pair_opt.value();
auto& lf = line_pair.first;
auto format = lf->get_format();
char ts[64];
sql_strftime(ts, sizeof(ts), line_pair.second->get_timeval(), 'T');
crumbs.emplace_back(
std::string(ts),
timestamp_poss,
[ec = this->lss_exec_context](const auto& ts) {
ec->execute(fmt::format(FMT_STRING(":goto {}"),
ts.template get<std::string>()));
});
crumbs.back().c_expected_input
= breadcrumb::crumb::expected_input_t::anything;
crumbs.back().c_search_placeholder
= "(Enter an absolute or relative time)";
if (!line_pair_opt) {
return;
}
auto line_pair = line_pair_opt.value();
auto& lf = line_pair.first;
auto format = lf->get_format();
char ts[64];
sql_strftime(ts, sizeof(ts), line_pair.second->get_timeval(), 'T');
crumbs.emplace_back(
std::string(ts),
timestamp_poss,
[ec = this->lss_exec_context](const auto& ts) {
ec->execute(fmt::format(FMT_STRING(":goto {}"),
ts.template get<std::string>()));
});
crumbs.back().c_expected_input
= breadcrumb::crumb::expected_input_t::anything;
crumbs.back().c_search_placeholder = "(Enter an absolute or relative time)";
auto format_name = format->get_name().to_string();
crumbs.emplace_back(
format_name,
attr_line_t().append(lnav::roles::identifier(format_name)),
[this]() -> std::vector<breadcrumb::possibility> {
return this->lss_files
| lnav::itertools::filter_in([](const auto& file_data) {
return file_data->is_visible();
})
| lnav::itertools::map(&logfile_data::get_file_ptr)
| lnav::itertools::map(&logfile::get_format_name)
| lnav::itertools::unique()
| lnav::itertools::map([](const auto& elem) {
return breadcrumb::possibility{
elem.to_string(),
};
});
},
[ec = this->lss_exec_context](const auto& format_name) {
static const std::string MOVE_STMT = R"(;UPDATE lnav_views
SET top = ifnull((SELECT log_line FROM all_logs WHERE log_format = $format_name LIMIT 1), top)
WHERE name = 'log'
)";
auto format_name = format->get_name().to_string();
crumbs.emplace_back(
format_name,
attr_line_t().append(lnav::roles::identifier(format_name)),
[this]() -> std::vector<breadcrumb::possibility> {
return this->lss_files
| lnav::itertools::filter_in([](const auto& file_data) {
return file_data->is_visible();
})
| lnav::itertools::map(&logfile_data::get_file_ptr)
| lnav::itertools::map(&logfile::get_format_name)
| lnav::itertools::unique()
| lnav::itertools::map([](const auto& elem) {
return breadcrumb::possibility{
elem.to_string(),
};
});
},
[ec = this->lss_exec_context](const auto& format_name) {
static const std::string MOVE_STMT = R"(;UPDATE lnav_views
SET top = (SELECT log_line FROM all_logs WHERE log_format = $format_name LIMIT 1)
ec->execute_with(
MOVE_STMT,
std::make_pair("format_name",
format_name.template get<std::string>()));
});
auto msg_start_iter = lf->message_start(line_pair.second);
auto file_line_number = std::distance(lf->begin(), msg_start_iter);
crumbs.emplace_back(
lf->get_unique_path(),
attr_line_t()
.append(lnav::roles::identifier(lf->get_unique_path()))
.append(FMT_STRING("[{:L}]"), file_line_number),
[this]() -> std::vector<breadcrumb::possibility> {
return this->lss_files
| lnav::itertools::filter_in([](const auto& file_data) {
return file_data->is_visible();
})
| lnav::itertools::map([](const auto& file_data) {
return breadcrumb::possibility{
file_data->get_file_ptr()->get_unique_path(),
attr_line_t(
file_data->get_file_ptr()->get_unique_path()),
};
});
},
[ec = this->lss_exec_context](const auto& uniq_path) {
static const std::string MOVE_STMT = R"(;UPDATE lnav_views
SET top = ifnull((SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1), top)
WHERE name = 'log'
)";
ec->execute_with(
MOVE_STMT,
std::make_pair("format_name",
format_name.template get<std::string>()));
});
ec->execute_with(
MOVE_STMT,
std::make_pair("uniq_path",
uniq_path.template get<std::string>()));
});
shared_buffer_ref sbr;
string_attrs_t sa;
std::vector<logline_value> values;
auto msg_start_iter = lf->message_start(line_pair.second);
auto file_line_number = std::distance(lf->begin(), msg_start_iter);
lf->read_full_message(msg_start_iter, sbr);
format->annotate(file_line_number, sbr, sa, values);
auto opid_opt = get_string_attr(sa, logline::L_OPID);
if (opid_opt) {
const auto& opid_range = opid_opt.value().saw_string_attr->sa_range;
const auto opid_str
= sbr.to_string_fragment(opid_range.lr_start, opid_range.length())
.to_string();
crumbs.emplace_back(
lf->get_unique_path(),
attr_line_t()
.append(lnav::roles::identifier(lf->get_unique_path()))
.append(FMT_STRING("[{:L}]"), file_line_number),
opid_str,
attr_line_t().append(lnav::roles::identifier(opid_str)),
[this]() -> std::vector<breadcrumb::possibility> {
return this->lss_files
| lnav::itertools::filter_in([](const auto& file_data) {
return file_data->is_visible();
})
| lnav::itertools::map([](const auto& file_data) {
std::set<std::string> opids;
for (const auto& file_data : this->lss_files) {
safe::ReadAccess<logfile::safe_opid_map> r_opid_map(
file_data->get_file_ptr()->get_opids());
for (const auto& pair : *r_opid_map) {
opids.insert(pair.first);
}
}
return opids | lnav::itertools::map([](const auto& elem) {
return breadcrumb::possibility{
file_data->get_file_ptr()->get_unique_path(),
attr_line_t(file_data->get_file_ptr()
->get_unique_path()),
elem,
};
});
},
[ec = this->lss_exec_context](const auto& uniq_path) {
[ec = this->lss_exec_context](const auto& opid) {
static const std::string MOVE_STMT = R"(;UPDATE lnav_views
SET top = (SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1)
WHERE name = 'log'
)";
SET top = ifnull((SELECT log_line FROM all_logs WHERE log_opid = $opid LIMIT 1), top)
WHERE name = 'log'
)";
ec->execute_with(
MOVE_STMT,
std::make_pair("uniq_path",
uniq_path.template get<std::string>()));
std::make_pair("opid", opid.template get<std::string>()));
});
}
shared_buffer_ref sbr;
string_attrs_t sa;
std::vector<logline_value> values;
lf->read_full_message(msg_start_iter, sbr);
format->annotate(file_line_number, sbr, sa, values);
auto opid_opt = get_string_attr(sa, logline::L_OPID);
if (opid_opt) {
const auto& opid_range = opid_opt.value().saw_string_attr->sa_range;
const auto opid_str = sbr.to_string_fragment(opid_range.lr_start,
opid_range.length())
.to_string();
crumbs.emplace_back(
opid_str,
attr_line_t().append(lnav::roles::identifier(opid_str)),
[this]() -> std::vector<breadcrumb::possibility> {
std::set<std::string> opids;
for (const auto& file_data : this->lss_files) {
safe::ReadAccess<logfile::safe_opid_map> r_opid_map(
file_data->get_file_ptr()->get_opids());
for (const auto& pair : *r_opid_map) {
opids.insert(pair.first);
auto sf = sbr.to_string_fragment();
auto sf_lines = sf.split_lines();
auto msg_line_number = std::distance(msg_start_iter, line_pair.second);
auto line_from_top = line - msg_line_number;
if (sf_lines.size() > 1) {
this->lss_token_meta = lnav::document::discover_structure(sf);
const auto initial_size = crumbs.size();
file_off_t line_offset = 0;
file_off_t line_end_offset = sf.length();
size_t line_number = 0;
for (const auto& sf_line : sf_lines) {
if (line_number >= msg_line_number) {
line_end_offset = line_offset + sf_line.length();
break;
}
line_number += 1;
line_offset += sf_line.length();
}
this->lss_token_meta.m_sections_tree.visit_overlapping(
line_offset,
line_end_offset,
[this,
initial_size,
meta = &this->lss_token_meta,
&crumbs,
line_from_top](const auto& iv) {
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key)
| lnav::itertools::append(iv.value);
auto curr_node = lnav::document::hier_node::lookup_path(
meta->m_sections_root.get(), path);
crumbs.template emplace_back(
iv.value,
[meta, path]() { return meta->possibility_provider(path); },
[this, curr_node, path, line_from_top](const auto& key) {
if (!curr_node) {
return;
}
}
auto* parent_node = curr_node.value()->hn_parent;
if (parent_node == nullptr) {
return;
}
key.template match(
[parent_node](const std::string& str) {
return parent_node->find_line_number(str);
},
[parent_node](size_t index) {
return parent_node->find_line_number(index);
})
| [this, line_from_top](auto line_number) {
this->tss_view->set_top(
vis_line_t(line_from_top + line_number));
};
});
if (curr_node && curr_node.value()->hn_parent->is_named_only())
{
auto node = lnav::document::hier_node::lookup_path(
meta->m_sections_root.get(), path);
crumbs.back().c_expected_input
= curr_node.value()
->hn_parent->hn_named_children.empty()
? breadcrumb::crumb::expected_input_t::index
: breadcrumb::crumb::expected_input_t::index_or_exact;
crumbs.back().with_possible_range(
node | lnav::itertools::map([](const auto hn) {
return hn->hn_parent->hn_children.size();
})
| lnav::itertools::unwrap_or(size_t{0}));
}
});
return opids | lnav::itertools::map([](const auto& elem) {
return breadcrumb::possibility{
elem,
};
});
},
[ec = this->lss_exec_context](const auto& opid) {
static const std::string MOVE_STMT = R"(;UPDATE lnav_views
SET top = (SELECT log_line FROM all_logs WHERE log_opid = $opid LIMIT 1)
WHERE name = 'log'
)";
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key);
auto node = lnav::document::hier_node::lookup_path(
this->lss_token_meta.m_sections_root.get(), path);
ec->execute_with(
MOVE_STMT,
std::make_pair("opid",
opid.template get<std::string>()));
});
if (node && !node.value()->hn_children.empty()) {
auto poss_provider = [curr_node = node.value()]() {
std::vector<breadcrumb::possibility> retval;
for (const auto& child : curr_node->hn_named_children) {
retval.template emplace_back(child.first);
}
return retval;
};
auto path_performer
= [this, curr_node = node.value(), line_from_top](
const breadcrumb::crumb::key_t& value) {
value.template match(
[curr_node](const std::string& str) {
return curr_node->find_line_number(str);
},
[curr_node](size_t index) {
return curr_node->find_line_number(index);
})
| [this, line_from_top](size_t line_number) {
this->tss_view->set_top(
vis_line_t(line_from_top + line_number));
};
};
crumbs.emplace_back("", "\u22ef", poss_provider, path_performer);
crumbs.back().c_expected_input
= node.value()->hn_named_children.empty()
? breadcrumb::crumb::expected_input_t::index
: breadcrumb::crumb::expected_input_t::index_or_exact;
}
}
}

@ -45,6 +45,7 @@
#include "base/time_util.hh"
#include "big_array.hh"
#include "bookmarks.hh"
#include "document.sections.hh"
#include "filter_observer.hh"
#include "log_accel.hh"
#include "log_format.hh"
@ -958,6 +959,7 @@ private:
std::shared_ptr<logfile> lss_token_file;
std::string lss_token_value;
string_attrs_t lss_token_attrs;
lnav::document::metadata lss_token_meta;
std::vector<logline_value> lss_token_values;
int lss_token_shift_start{0};
int lss_token_shift_size{0};

@ -0,0 +1,523 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "md2attr_line.hh"
#include "base/attr_line.builder.hh"
#include "base/itertools.hh"
#include "base/lnav_log.hh"
#include "pcrepp/pcrepp.hh"
#include "readline_highlighters.hh"
#include "view_curses.hh"
using namespace lnav::roles::literals;
void
md2attr_line::flush_footnotes()
{
if (this->ml_footnotes.empty()) {
return;
}
auto& block_text = this->ml_blocks.back();
auto longest_foot = this->ml_footnotes
| lnav::itertools::map(&attr_line_t::utf8_length_or_length)
| lnav::itertools::max(0);
size_t index = 1;
block_text.append("\n");
for (auto& foot : this->ml_footnotes) {
block_text.append(lnav::string::attrs::preformatted(" "))
.append("\u258c"_footnote_border)
.append(lnav::roles::footnote_text(
index < 10 && this->ml_footnotes.size() >= 10 ? " " : ""))
.append(lnav::roles::footnote_text(
fmt::format(FMT_STRING("[{}] - "), index)))
.append(foot.pad_to(longest_foot))
.append("\n");
index += 1;
}
this->ml_footnotes.clear();
}
Result<void, std::string>
md2attr_line::enter_block(const md4cpp::event_handler::block& bl)
{
if (this->ml_list_stack.empty()
&& (bl.is<MD_BLOCK_H_DETAIL*>() || bl.is<block_hr>()
|| bl.is<block_p>()))
{
this->flush_footnotes();
}
this->ml_blocks.resize(this->ml_blocks.size() + 1);
if (bl.is<MD_BLOCK_OL_DETAIL*>()) {
auto* ol_detail = bl.get<MD_BLOCK_OL_DETAIL*>();
this->ml_list_stack.emplace_back(*ol_detail);
} else if (bl.is<MD_BLOCK_UL_DETAIL*>()) {
this->ml_list_stack.emplace_back(bl.get<MD_BLOCK_UL_DETAIL*>());
} else if (bl.is<MD_BLOCK_TABLE_DETAIL*>()) {
this->ml_tables.resize(this->ml_tables.size() + 1);
} else if (bl.is<block_tr>()) {
this->ml_tables.back().t_rows.resize(
this->ml_tables.back().t_rows.size() + 1);
} else if (bl.is<MD_BLOCK_CODE_DETAIL*>()) {
this->ml_code_depth += 1;
}
return Ok();
}
Result<void, std::string>
md2attr_line::leave_block(const md4cpp::event_handler::block& bl)
{
auto block_text = std::move(this->ml_blocks.back());
this->ml_blocks.pop_back();
auto& last_block = this->ml_blocks.back();
if (!endswith(block_text.get_string(), "\n")) {
block_text.append("\n");
}
if (bl.is<MD_BLOCK_H_DETAIL*>()) {
auto* hbl = bl.get<MD_BLOCK_H_DETAIL*>();
auto role = role_t::VCR_TEXT;
switch (hbl->level) {
case 1:
role = role_t::VCR_H1;
break;
case 2:
role = role_t::VCR_H2;
break;
case 3:
role = role_t::VCR_H3;
break;
case 4:
role = role_t::VCR_H4;
break;
case 5:
role = role_t::VCR_H5;
break;
case 6:
role = role_t::VCR_H6;
break;
}
block_text.rtrim().with_attr_for_all(VC_ROLE.value(role));
last_block.append("\n").append(block_text).append("\n");
} else if (bl.is<block_hr>()) {
block_text = attr_line_t()
.append(lnav::roles::hr(repeat("\u2501", 70)))
.with_attr_for_all(SA_PREFORMATTED.value());
last_block.append("\n").append(block_text).append("\n");
} else if (bl.is<MD_BLOCK_UL_DETAIL*>() || bl.is<MD_BLOCK_OL_DETAIL*>()) {
this->ml_list_stack.pop_back();
if (last_block.empty()) {
last_block.append("\n");
} else {
if (!endswith(last_block.get_string(), "\n")) {
last_block.append("\n");
}
if (this->ml_list_stack.empty()
&& !endswith(last_block.get_string(), "\n\n")) {
last_block.append("\n");
}
}
last_block.append(block_text);
} else if (bl.is<MD_BLOCK_LI_DETAIL*>()) {
auto last_list_block = this->ml_list_stack.back();
text_wrap_settings tws = {0, 60};
attr_line_builder alb(last_block);
{
auto prefix = alb.with_attr(SA_PREFORMATTED.value());
alb.append(" ")
.append(last_list_block.match(
[this, &tws](const MD_BLOCK_UL_DETAIL*) {
tws.tws_indent = 3;
return this->ml_list_stack.size() % 2 == 1
? "\u2022"_list_glyph
: "\u2014"_list_glyph;
},
[this, &tws](MD_BLOCK_OL_DETAIL ol_detail) {
auto retval = lnav::roles::list_glyph(
fmt::format(FMT_STRING("{}{}"),
ol_detail.start,
ol_detail.mark_delimiter));
tws.tws_indent = retval.first.length() + 2;
this->ml_list_stack.pop_back();
ol_detail.start += 1;
this->ml_list_stack.emplace_back(ol_detail);
return retval;
}))
.append(" ");
}
alb.append(block_text, &tws);
} else if (bl.is<MD_BLOCK_CODE_DETAIL*>()) {
auto* code_detail = bl.get<MD_BLOCK_CODE_DETAIL*>();
this->ml_code_depth -= 1;
auto lang_sf = string_fragment{
code_detail->lang.text,
0,
(int) code_detail->lang.size,
};
if (lang_sf == "lnav") {
readline_lnav_highlighter(block_text, block_text.length());
}
auto code_lines = block_text.rtrim().split_lines();
auto max_width = code_lines
| lnav::itertools::map(&attr_line_t::utf8_length_or_length)
| lnav::itertools::max(0);
attr_line_t padded_text;
for (auto& line : code_lines) {
line.pad_to(std::max(max_width + 4, ssize_t{40}))
.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
padded_text.append(lnav::string::attrs::preformatted(" "))
.append("\u258c"_code_border)
.append(line)
.append("\n");
}
padded_text.with_attr_for_all(SA_PREFORMATTED.value());
last_block.append("\n").append(padded_text);
} else if (bl.is<block_quote>()) {
text_wrap_settings tws = {0, 60};
attr_line_t wrapped_text;
wrapped_text.append(block_text.rtrim(), &tws);
auto quoted_lines = wrapped_text.split_lines();
auto max_width = quoted_lines
| lnav::itertools::map(&attr_line_t::utf8_length_or_length)
| lnav::itertools::max(0);
attr_line_t padded_text;
for (auto& line : quoted_lines) {
line.pad_to(max_width + 1)
.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_TEXT));
padded_text.append(" ")
.append("\u258c"_quote_border)
.append(line)
.append("\n");
}
padded_text.with_attr_for_all(SA_PREFORMATTED.value());
last_block.append("\n").append(padded_text);
} else if (bl.is<MD_BLOCK_TABLE_DETAIL*>()) {
auto* table_detail = bl.get<MD_BLOCK_TABLE_DETAIL*>();
auto tab = std::move(this->ml_tables.back());
this->ml_tables.pop_back();
std::vector<ssize_t> max_col_sizes;
block_text.clear();
block_text.append("\n");
max_col_sizes.resize(table_detail->col_count);
for (size_t lpc = 0; lpc < table_detail->col_count; lpc++) {
if (lpc < tab.t_headers.size()) {
max_col_sizes[lpc] = tab.t_headers[lpc].utf8_length_or_length();
tab.t_headers[lpc].with_attr_for_all(
VC_ROLE.value(role_t::VCR_TABLE_HEADER));
}
}
for (const auto& row : tab.t_rows) {
for (size_t lpc = 0; lpc < table_detail->col_count; lpc++) {
if (lpc >= row.r_columns.size()) {
continue;
}
auto col_len = row.r_columns[lpc].utf8_length_or_length();
if (col_len > max_col_sizes[lpc]) {
max_col_sizes[lpc] = col_len;
}
}
}
auto col_sizes
= max_col_sizes | lnav::itertools::map([](const auto& elem) {
return std::min(elem, ssize_t{50});
});
auto full_width = col_sizes | lnav::itertools::sum();
text_wrap_settings tws = {0, 50};
std::vector<cell_lines> cells;
size_t max_cell_lines = 0;
for (size_t lpc = 0; lpc < tab.t_headers.size(); lpc++) {
tws.with_width(col_sizes[lpc]);
attr_line_t td_block;
td_block.append(tab.t_headers[lpc], &tws);
cells.emplace_back(td_block.rtrim().split_lines());
if (cells.back().cl_lines.size() > max_cell_lines) {
max_cell_lines = cells.back().cl_lines.size();
}
}
for (size_t line_index = 0; line_index < max_cell_lines; line_index++) {
size_t col = 0;
for (const auto& cell : cells) {
block_text.append(" ");
if (line_index < cell.cl_lines.size()) {
block_text.append(cell.cl_lines[line_index]);
block_text.append(
col_sizes[col]
- cell.cl_lines[line_index].utf8_length_or_length(),
' ');
} else {
block_text.append(col_sizes[col], ' ');
}
col += 1;
}
block_text.append("\n")
.append(lnav::roles::table_border(
repeat("\u2550", full_width + col_sizes.size())))
.append("\n");
}
for (const auto& row : tab.t_rows) {
cells.clear();
max_cell_lines = 0;
for (size_t lpc = 0; lpc < row.r_columns.size(); lpc++) {
tws.with_width(col_sizes[lpc]);
attr_line_t td_block;
td_block.append(row.r_columns[lpc], &tws);
cells.emplace_back(td_block.rtrim().split_lines());
if (cells.back().cl_lines.size() > max_cell_lines) {
max_cell_lines = cells.back().cl_lines.size();
}
}
for (size_t line_index = 0; line_index < max_cell_lines;
line_index++) {
size_t col = 0;
for (const auto& cell : cells) {
block_text.append(" ");
if (line_index < cell.cl_lines.size()) {
block_text.append(cell.cl_lines[line_index]);
if (col < col_sizes.size() - 1) {
block_text.append(
col_sizes[col]
- cell.cl_lines[line_index]
.utf8_length_or_length(),
' ');
}
} else if (col < col_sizes.size() - 1) {
block_text.append(col_sizes[col], ' ');
}
col += 1;
}
block_text.append("\n");
}
}
block_text.with_attr_for_all(SA_PREFORMATTED.value());
last_block.append(block_text);
} else if (bl.is<block_th>()) {
this->ml_tables.back().t_headers.push_back(block_text);
} else if (bl.is<MD_BLOCK_TD_DETAIL*>()) {
this->ml_tables.back().t_rows.back().r_columns.push_back(block_text);
} else {
text_wrap_settings tws = {0, this->ml_blocks.size() == 1 ? 70 : 10000};
if (!last_block.empty()) {
last_block.append("\n");
}
last_block.append(block_text, &tws);
}
if (bl.is<block_doc>()) {
this->flush_footnotes();
}
return Ok();
}
Result<void, std::string>
md2attr_line::enter_span(const md4cpp::event_handler::span& sp)
{
auto& last_block = this->ml_blocks.back();
this->ml_span_starts.push_back(last_block.length());
if (sp.is<span_code>()) {
last_block.append(" ");
this->ml_code_depth += 1;
}
return Ok();
}
Result<void, std::string>
md2attr_line::leave_span(const md4cpp::event_handler::span& sp)
{
auto& last_block = this->ml_blocks.back();
if (sp.is<span_code>()) {
this->ml_code_depth -= 1;
last_block.append(" ");
line_range lr{
static_cast<int>(this->ml_span_starts.back()),
static_cast<int>(last_block.length()),
};
last_block.with_attr({
lr,
VC_ROLE.value(role_t::VCR_QUOTED_CODE),
});
last_block.with_attr({
lr,
SA_PREFORMATTED.value(),
});
} else if (sp.is<span_em>()) {
line_range lr{
static_cast<int>(this->ml_span_starts.back()),
static_cast<int>(last_block.length()),
};
last_block.with_attr({
lr,
VC_STYLE.value(A_ITALIC),
});
} else if (sp.is<span_strong>()) {
line_range lr{
static_cast<int>(this->ml_span_starts.back()),
static_cast<int>(last_block.length()),
};
last_block.with_attr({
lr,
VC_STYLE.value(A_BOLD),
});
} else if (sp.is<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);
this->append_url_footnote(href_str);
} else if (sp.is<MD_SPAN_IMG_DETAIL*>()) {
auto* img_detail = sp.get<MD_SPAN_IMG_DETAIL*>();
auto src_str = std::string(img_detail->src.text, img_detail->src.size);
this->append_url_footnote(src_str);
}
this->ml_span_starts.pop_back();
return Ok();
}
Result<void, std::string>
md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
{
static const auto& entity_map = md4cpp::get_xml_entity_map();
auto& last_block = this->ml_blocks.back();
switch (tt) {
case MD_TEXT_BR:
last_block.append("\n");
break;
case MD_TEXT_SOFTBR: {
if (!last_block.empty() && !isspace(last_block.get_string().back()))
{
last_block.append(" ");
}
break;
}
case MD_TEXT_ENTITY: {
auto xe_iter = entity_map.xem_entities.find(sf.to_string());
if (xe_iter != entity_map.xem_entities.end()) {
last_block.append(xe_iter->second.xe_chars);
}
break;
}
default: {
static const pcrepp REPL_RE(R"(-{2,3}|:[^:\s]*(?:::[^:\s]*)*:)");
static const auto& emojis = md4cpp::get_emoji_map();
if (this->ml_code_depth > 0) {
last_block.append(sf);
return Ok();
}
pcre_input pi(sf);
pcre_context_static<30> pc;
while (REPL_RE.match(pc, pi)) {
auto prev = string_fragment{
sf.sf_string,
(int) pi.pi_offset,
pc.all()->c_begin,
};
last_block.append(prev);
auto matched = pi.get_string_fragment(pc.all());
if (matched == "--") {
last_block.append("\u2013");
} else if (matched == "---") {
last_block.append("\u2014");
} else if (matched.startswith(":")) {
auto em_iter
= emojis.em_shortname2emoji.find(matched.to_string());
if (em_iter == emojis.em_shortname2emoji.end()) {
last_block.append(matched);
} else {
last_block.append(em_iter->second.get().e_value);
}
}
}
this->ml_blocks.back().append(string_fragment{
sf.sf_string,
(int) pi.pi_offset,
sf.sf_end,
});
break;
}
}
return Ok();
}
void
md2attr_line::append_url_footnote(std::string href_str)
{
if (startswith(href_str, "#")) {
return;
}
auto& last_block = this->ml_blocks.back();
last_block.append(FMT_STRING("[{}]"), this->ml_footnotes.size() + 1);
last_block.with_attr(string_attr{
line_range{
(int) this->ml_span_starts.back(),
(int) last_block.length(),
},
VC_STYLE.value(A_UNDERLINE),
});
if (this->ml_source_path && href_str.find(":") == std::string::npos) {
auto link_path = ghc::filesystem::absolute(
this->ml_source_path.value().parent_path() / href_str);
href_str = fmt::format(FMT_STRING("file://{}"), link_path.string());
}
auto href
= attr_line_t().append(lnav::roles::hyperlink(href_str)).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);
}

@ -0,0 +1,90 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lnav_md2attr_line_hh
#define lnav_md2attr_line_hh
#include "base/attr_line.hh"
#include "ghc/filesystem.hpp"
#include "md4cpp.hh"
class md2attr_line : public md4cpp::typed_event_handler<attr_line_t> {
public:
md2attr_line() { this->ml_blocks.resize(1); }
md2attr_line& with_source_path(nonstd::optional<ghc::filesystem::path> path)
{
this->ml_source_path = path;
return *this;
}
Result<void, std::string> enter_block(const block& bl) override;
Result<void, std::string> leave_block(const block& bl) override;
Result<void, std::string> enter_span(const span& bl) override;
Result<void, std::string> leave_span(const span& sp) override;
Result<void, std::string> text(MD_TEXTTYPE tt,
const string_fragment& sf) override;
attr_line_t get_result() override { return this->ml_blocks.back(); }
private:
struct table_t {
struct row_t {
std::vector<attr_line_t> r_columns;
};
std::vector<attr_line_t> t_headers;
std::vector<row_t> t_rows;
};
struct cell_lines {
cell_lines(std::vector<attr_line_t> lines) : cl_lines(std::move(lines))
{
}
std::vector<attr_line_t> cl_lines;
};
using list_block_t
= mapbox::util::variant<MD_BLOCK_UL_DETAIL*, MD_BLOCK_OL_DETAIL>;
void append_url_footnote(std::string href);
void flush_footnotes();
nonstd::optional<ghc::filesystem::path> ml_source_path;
std::vector<attr_line_t> ml_blocks;
std::vector<list_block_t> ml_list_stack;
std::vector<table_t> ml_tables;
std::vector<size_t> ml_span_starts;
std::vector<attr_line_t> ml_footnotes;
int32_t ml_code_depth{0};
};
#endif

@ -0,0 +1,287 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "md4cpp.hh"
#include "base/lnav_log.hh"
#include "emojis-json.h"
#include "xml-entities-json.h"
#include "yajlpp/yajlpp_def.hh"
namespace md4cpp {
static const typed_json_path_container<xml_entity> xml_entity_handlers = {
yajlpp::property_handler("characters").for_field(&xml_entity::xe_chars),
};
static const typed_json_path_container<xml_entity_map> xml_entity_map_handlers
= {
yajlpp::pattern_property_handler("(?<var_name>\\&\\w+;?)")
.with_synopsis("<name>")
.with_path_provider<xml_entity_map>(
[](struct xml_entity_map* xem,
std::vector<std::string>& paths_out) {
for (const auto& iter : xem->xem_entities) {
paths_out.emplace_back(iter.first);
}
})
.with_obj_provider<xml_entity, xml_entity_map>(
[](const yajlpp_provider_context& ypc, xml_entity_map* xem) {
auto entity_name = ypc.get_substr(0);
return &xem->xem_entities[entity_name];
})
.with_children(xml_entity_handlers),
};
static const typed_json_path_container<emoji> emoji_handlers = {
yajlpp::property_handler("emoji").for_field(&emoji::e_value),
yajlpp::property_handler("shortname").for_field(&emoji::e_shortname),
};
static const typed_json_path_container<emoji_map> emoji_map_handlers = {
yajlpp::property_handler("emojis#")
.for_field(&emoji_map::em_emojis)
.with_children(emoji_handlers),
};
static xml_entity_map
load_xml_entity_map()
{
static const intern_string_t name
= intern_string::lookup(xml_entities_json.get_name());
auto parse_res
= xml_entity_map_handlers.parser_for(name).with_ignore_unused(true).of(
xml_entities_json.to_string_fragment());
assert(parse_res.isOk());
return parse_res.unwrap();
}
const xml_entity_map&
get_xml_entity_map()
{
static const auto retval = load_xml_entity_map();
return retval;
}
static emoji_map
load_emoji_map()
{
static const intern_string_t name
= intern_string::lookup(emojis_json.get_name());
auto parse_res
= emoji_map_handlers.parser_for(name).with_ignore_unused(true).of(
emojis_json.to_string_fragment());
assert(parse_res.isOk());
auto retval = parse_res.unwrap();
for (auto& em : retval.em_emojis) {
retval.em_shortname2emoji.emplace(em.e_shortname, em);
}
return retval;
}
const emoji_map&
get_emoji_map()
{
static const auto retval = load_emoji_map();
return retval;
}
struct parse_userdata {
event_handler& pu_handler;
std::string pu_error_msg;
};
static event_handler::block
build_block(MD_BLOCKTYPE type, void* detail)
{
switch (type) {
case MD_BLOCK_DOC:
return event_handler::block_doc{};
case MD_BLOCK_QUOTE:
return event_handler::block_quote{};
case MD_BLOCK_UL:
return static_cast<MD_BLOCK_UL_DETAIL*>(detail);
case MD_BLOCK_OL:
return static_cast<MD_BLOCK_OL_DETAIL*>(detail);
case MD_BLOCK_LI:
return static_cast<MD_BLOCK_LI_DETAIL*>(detail);
case MD_BLOCK_HR:
return event_handler::block_hr{};
case MD_BLOCK_H:
return static_cast<MD_BLOCK_H_DETAIL*>(detail);
case MD_BLOCK_CODE:
return static_cast<MD_BLOCK_CODE_DETAIL*>(detail);
case MD_BLOCK_HTML:
return event_handler::block_html{};
case MD_BLOCK_P:
return event_handler::block_p{};
case MD_BLOCK_TABLE:
return static_cast<MD_BLOCK_TABLE_DETAIL*>(detail);
case MD_BLOCK_THEAD:
return event_handler::block_thead{};
case MD_BLOCK_TBODY:
return event_handler::block_tbody{};
case MD_BLOCK_TR:
return event_handler::block_tr{};
case MD_BLOCK_TH:
return event_handler::block_th{};
case MD_BLOCK_TD:
return static_cast<MD_BLOCK_TD_DETAIL*>(detail);
}
return {};
}
static event_handler::span
build_span(MD_SPANTYPE type, void* detail)
{
switch (type) {
case MD_SPAN_EM:
return event_handler::span_em{};
case MD_SPAN_STRONG:
return event_handler::span_strong{};
case MD_SPAN_A:
return static_cast<MD_SPAN_A_DETAIL*>(detail);
case MD_SPAN_IMG:
return static_cast<MD_SPAN_IMG_DETAIL*>(detail);
case MD_SPAN_CODE:
return event_handler::span_code{};
case MD_SPAN_DEL:
return event_handler::span_del{};
case MD_SPAN_U:
return event_handler::span_u{};
default:
break;
}
return {};
}
static int
md4cpp_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto enter_res = pu->pu_handler.enter_block(build_block(type, detail));
if (enter_res.isErr()) {
pu->pu_error_msg = enter_res.unwrapErr();
return 1;
}
return 0;
}
static int
md4cpp_leave_block(MD_BLOCKTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto leave_res = pu->pu_handler.leave_block(build_block(type, detail));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
}
return 0;
}
static int
md4cpp_enter_span(MD_SPANTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto enter_res = pu->pu_handler.enter_span(build_span(type, detail));
if (enter_res.isErr()) {
pu->pu_error_msg = enter_res.unwrapErr();
return 1;
}
return 0;
}
static int
md4cpp_leave_span(MD_SPANTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto leave_res = pu->pu_handler.leave_span(build_span(type, detail));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
}
return 0;
}
static int
md4cpp_text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto leave_res = pu->pu_handler.text(type, string_fragment(text, 0, size));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
}
return 0;
}
namespace details {
Result<void, std::string>
parse(const string_fragment& sf, event_handler& eh)
{
MD_PARSER parser = {0};
auto pu = parse_userdata{eh};
parser.abi_version = 0;
parser.flags = MD_DIALECT_GITHUB;
parser.enter_block = md4cpp_enter_block;
parser.leave_block = md4cpp_leave_block;
parser.enter_span = md4cpp_enter_span;
parser.leave_span = md4cpp_leave_span;
parser.text = md4cpp_text;
auto rc = md_parse(sf.data(), sf.length(), &parser, &pu);
if (rc == 0) {
return Ok();
}
return Err(pu.pu_error_msg);
}
} // namespace details
} // namespace md4cpp

@ -0,0 +1,144 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lnav_md4cpp_hh
#define lnav_md4cpp_hh
#include <map>
#include <string>
#include <unordered_map>
#include "base/intern_string.hh"
#include "base/result.h"
#include "mapbox/variant.hpp"
#include "md4c/md4c.h"
namespace md4cpp {
struct xml_entity {
std::string xe_chars;
};
struct xml_entity_map {
std::map<std::string, xml_entity> xem_entities;
};
struct emoji {
std::string e_shortname;
std::string e_value;
};
struct emoji_map {
std::vector<emoji> em_emojis;
std::unordered_map<std::string, std::reference_wrapper<emoji>>
em_shortname2emoji;
};
class event_handler {
public:
virtual ~event_handler() = default;
struct block_doc {};
struct block_quote {};
struct block_hr {};
struct block_html {};
struct block_p {};
struct block_thead {};
struct block_tbody {};
struct block_tr {};
struct block_th {};
using block = mapbox::util::variant<block_doc,
block_quote,
MD_BLOCK_UL_DETAIL*,
MD_BLOCK_OL_DETAIL*,
MD_BLOCK_LI_DETAIL*,
block_hr,
MD_BLOCK_H_DETAIL*,
MD_BLOCK_CODE_DETAIL*,
block_html,
block_p,
MD_BLOCK_TABLE_DETAIL*,
block_thead,
block_tbody,
block_tr,
block_th,
MD_BLOCK_TD_DETAIL*>;
virtual Result<void, std::string> enter_block(const block& bl) = 0;
virtual Result<void, std::string> leave_block(const block& bl) = 0;
struct span_em {};
struct span_strong {};
struct span_code {};
struct span_del {};
struct span_u {};
using span = mapbox::util::variant<span_em,
span_strong,
MD_SPAN_A_DETAIL*,
MD_SPAN_IMG_DETAIL*,
span_code,
span_del,
span_u>;
virtual Result<void, std::string> enter_span(const span& bl) = 0;
virtual Result<void, std::string> leave_span(const span& bl) = 0;
virtual Result<void, std::string> text(MD_TEXTTYPE tt,
const string_fragment& sf)
= 0;
};
namespace details {
Result<void, std::string> parse(const string_fragment& sf, event_handler& eh);
}
template<typename T>
class typed_event_handler : public event_handler {
public:
virtual T get_result() = 0;
};
template<typename T>
Result<T, std::string>
parse(const string_fragment& sf, typed_event_handler<T>& eh)
{
TRY(details::parse(sf, eh));
return Ok(eh.get_result());
}
const xml_entity_map& get_xml_entity_map();
const emoji_map& get_emoji_map();
} // namespace md4cpp
#endif

@ -189,17 +189,23 @@ public:
pcre_input(const string_fragment& s)
: pi_offset(0), pi_next_offset(0), pi_length(s.length()),
pi_string(s.data()){};
pi_string(s.data())
{
}
pcre_input(const intern_string_t& s)
: pi_offset(0), pi_next_offset(0), pi_length(s.size()),
pi_string(s.get()){};
pi_string(s.get())
{
}
pcre_input(const string_fragment&&) = delete;
pcre_input(const std::string& str, size_t off = 0)
: pi_offset(off), pi_next_offset(off), pi_length(str.length()),
pi_string(str.c_str()){};
pi_string(str.c_str())
{
}
pcre_input(const std::string&&, size_t off = 0) = delete;
@ -229,6 +235,15 @@ public:
iter->length());
}
string_fragment get_string_fragment(pcre_context::const_iterator iter) const
{
return string_fragment{
this->pi_string,
iter->c_begin,
iter->c_end,
};
}
nonstd::optional<std::string> get_substr_opt(
pcre_context::const_iterator iter) const
{
@ -276,9 +291,11 @@ struct pcre_named_capture {
class iterator {
public:
iterator(pcre_named_capture* pnc, size_t name_len)
: i_named_capture(pnc), i_name_len(name_len){};
: i_named_capture(pnc), i_name_len(name_len)
{
}
iterator() : i_named_capture(nullptr), i_name_len(0){};
iterator() : i_named_capture(nullptr), i_name_len(0) {}
const pcre_named_capture& operator*() const
{
@ -341,12 +358,14 @@ public:
class error : public std::exception {
public:
error(std::string msg, int offset = 0)
: e_msg(std::move(msg)), e_offset(offset){};
: e_msg(std::move(msg)), e_offset(offset)
{
}
const char* what() const noexcept override
{
return this->e_msg.c_str();
};
}
const std::string e_msg;
int e_offset;

@ -78,6 +78,7 @@ plain_text_source&
plain_text_source::replace_with(const attr_line_t& text_lines)
{
this->tds_lines.clear();
this->tds_doc_sections = lnav::document::discover_metadata(text_lines);
this->tds_lines = to_text_line(text_lines.split_lines());
this->tds_longest_line = this->compute_longest_line();
return *this;
@ -165,7 +166,7 @@ plain_text_source::compute_longest_line()
}
nonstd::optional<vis_line_t>
plain_text_source::line_for_offset(file_off_t off)
plain_text_source::line_for_offset(file_off_t off) const
{
struct cmper {
bool operator()(const file_off_t& lhs, const text_line& rhs)
@ -200,3 +201,98 @@ plain_text_source::line_for_offset(file_off_t off)
return nonstd::make_optional(
vis_line_t(std::distance(this->tds_lines.begin(), iter)));
}
void
plain_text_source::text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs)
{
const auto initial_size = crumbs.size();
const auto& tl = this->tds_lines[line];
this->tds_doc_sections.m_sections_tree.visit_overlapping(
tl.tl_offset,
[&crumbs, initial_size, meta = &this->tds_doc_sections, this](
const auto& iv) {
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key)
| lnav::itertools::append(iv.value);
crumbs.template emplace_back(
iv.value,
[meta, path]() { return meta->possibility_provider(path); },
[this, meta, path](const auto& key) {
auto curr_node = lnav::document::hier_node::lookup_path(
meta->m_sections_root.get(), path);
if (!curr_node) {
return;
}
auto* parent_node = curr_node.value()->hn_parent;
if (parent_node == nullptr) {
return;
}
key.template match(
[this, parent_node](const std::string& str) {
auto sib_iter
= parent_node->hn_named_children.find(str);
if (sib_iter
== parent_node->hn_named_children.end()) {
return;
}
this->line_for_offset(sib_iter->second->hn_start) |
[this](const auto new_top) {
this->tss_view->set_top(new_top);
};
},
[this, parent_node](size_t index) {
if (index >= parent_node->hn_children.size()) {
return;
}
auto sib = parent_node->hn_children[index].get();
this->line_for_offset(sib->hn_start) |
[this](const auto new_top) {
this->tss_view->set_top(new_top);
};
});
});
});
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key);
auto node = lnav::document::hier_node::lookup_path(
this->tds_doc_sections.m_sections_root.get(), path);
if (node && !node.value()->hn_children.empty()) {
auto poss_provider = [curr_node = node.value()]() {
std::vector<breadcrumb::possibility> retval;
for (const auto& child : curr_node->hn_named_children) {
retval.template emplace_back(child.first);
}
return retval;
};
auto path_performer = [this, curr_node = node.value()](
const breadcrumb::crumb::key_t& value) {
value.template match(
[this, curr_node](const std::string& str) {
auto child_iter = curr_node->hn_named_children.find(str);
if (child_iter != curr_node->hn_named_children.end()) {
this->line_for_offset(child_iter->second->hn_start) |
[this](const auto new_top) {
this->tss_view->set_top(new_top);
};
}
},
[this, curr_node](size_t index) {
auto* child = curr_node->hn_children[index].get();
this->line_for_offset(child->hn_start) |
[this](const auto new_top) {
this->tss_view->set_top(new_top);
};
});
};
crumbs.emplace_back(
"", "\u22ef", std::move(poss_provider), std::move(path_performer));
crumbs.back().c_expected_input = node.value()->hn_named_children.empty()
? breadcrumb::crumb::expected_input_t::index
: breadcrumb::crumb::expected_input_t::index_or_exact;
}
}

@ -35,6 +35,7 @@
#include "base/attr_line.hh"
#include "base/file_range.hh"
#include "document.sections.hh"
#include "textview_curses.hh"
class plain_text_source
@ -116,15 +117,19 @@ public:
return this;
}
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override;
protected:
size_t compute_longest_line();
nonstd::optional<vis_line_t> line_for_offset(file_off_t off);
nonstd::optional<vis_line_t> line_for_offset(file_off_t off) const;
std::vector<text_line> tds_lines;
text_format_t tds_text_format{text_format_t::TF_UNKNOWN};
size_t tds_longest_line{0};
bool tds_reverse_selection{false};
lnav::document::metadata tds_doc_sections;
};
#endif // LNAV_PLAIN_TEXT_SOURCE_HH

@ -106,7 +106,9 @@ pretty_printer::append_to(attr_line_t& al)
case DT_COMMA:
if (this->pp_depth > 0) {
this->flush_values(true);
this->append_child_node();
if (!this->pp_is_xml) {
this->append_child_node();
}
this->write_element(el);
this->start_new_line();
this->pp_interval_state.back().is_start
@ -315,7 +317,9 @@ pretty_printer::ascend()
this->pp_body_lines.pop();
this->pp_body_lines.top() += lines;
this->append_child_node();
if (!this->pp_is_xml) {
this->append_child_node();
}
this->pp_interval_state.pop_back();
this->pp_hier_stage = std::move(this->pp_hier_nodes.back());
this->pp_hier_nodes.pop_back();
@ -330,7 +334,8 @@ pretty_printer::descend()
this->pp_depth += 1;
this->pp_body_lines.push(0);
this->pp_interval_state.resize(this->pp_depth + 1);
this->pp_hier_nodes.push_back(std::make_unique<hier_node>());
this->pp_hier_nodes.push_back(
std::make_unique<lnav::document::hier_node>());
}
void
@ -342,15 +347,16 @@ pretty_printer::append_child_node()
}
auto* top_node = this->pp_hier_nodes.back().get();
auto new_key = ivstate.is_name.empty() ? key_t{top_node->hn_children.size()}
: key_t{ivstate.is_name};
auto new_key = ivstate.is_name.empty()
? lnav::document::section_key_t{top_node->hn_children.size()}
: lnav::document::section_key_t{ivstate.is_name};
this->pp_intervals.emplace_back(
ivstate.is_start.value(),
static_cast<ssize_t>(this->pp_stream.tellp()),
new_key);
auto new_node = this->pp_hier_stage != nullptr
? std::move(this->pp_hier_stage)
: std::make_unique<hier_node>();
: std::make_unique<lnav::document::hier_node>();
auto* retval = new_node.get();
new_node->hn_parent = top_node;
new_node->hn_start = this->pp_intervals.back().start;
@ -364,43 +370,3 @@ pretty_printer::append_child_node()
ivstate.is_start = nonstd::nullopt;
ivstate.is_name.clear();
}
nonstd::optional<pretty_printer::hier_node*>
pretty_printer::hier_node::lookup_child(pretty_printer::key_t key) const
{
return make_optional_from_nullable(key.match(
[this](const std::string& str) -> pretty_printer::hier_node* {
auto iter = this->hn_named_children.find(str);
if (iter != this->hn_named_children.end()) {
return iter->second;
}
return nullptr;
},
[this](size_t index) -> pretty_printer::hier_node* {
if (index < this->hn_children.size()) {
return this->hn_children[index].get();
}
return nullptr;
}));
}
nonstd::optional<const pretty_printer::hier_node*>
pretty_printer::hier_node::lookup_path(const pretty_printer::hier_node* root,
const std::vector<key_t>& path)
{
auto retval = nonstd::make_optional(root);
for (const auto& comp : path) {
if (!retval) {
break;
}
retval = retval.value()->lookup_child(comp);
}
if (!retval) {
return nonstd::nullopt;
}
return retval;
}

@ -43,14 +43,10 @@
#include "base/file_range.hh"
#include "base/opt_util.hh"
#include "data_scanner.hh"
#include "intervaltree/IntervalTree.h"
#include "document.sections.hh"
class pretty_printer {
public:
using key_t = mapbox::util::variant<std::string, size_t>;
using pretty_interval = interval_tree::Interval<file_off_t, key_t>;
using pretty_tree = interval_tree::IntervalTree<file_off_t, key_t>;
struct element {
element(data_token_t token, pcre_context& pc)
: e_token(token), e_capture(*pc.all())
@ -66,27 +62,6 @@ public:
pcre_context::capture_t e_capture;
};
struct hier_node {
hier_node* hn_parent{nullptr};
file_off_t hn_start{0};
std::multimap<std::string, hier_node*> hn_named_children;
std::vector<std::unique_ptr<hier_node>> hn_children;
nonstd::optional<hier_node*> lookup_child(key_t key) const;
static nonstd::optional<const hier_node*> lookup_path(
const hier_node* root, const std::vector<key_t>& path);
template<typename F>
static void depth_first(hier_node* root, F func)
{
for (auto& child : root->hn_children) {
depth_first(child.get(), func);
}
func(root);
}
};
pretty_printer(data_scanner* ds, string_attrs_t sa, int leading_indent = 0)
: pp_leading_indent(leading_indent), pp_scanner(ds),
pp_attrs(std::move(sa))
@ -105,17 +80,18 @@ public:
}
this->pp_interval_state.resize(1);
this->pp_hier_nodes.push_back(std::make_unique<hier_node>());
this->pp_hier_nodes.push_back(
std::make_unique<lnav::document::hier_node>());
}
void append_to(attr_line_t& al);
std::vector<pretty_interval> take_intervals()
std::vector<lnav::document::section_interval_t> take_intervals()
{
return std::move(this->pp_intervals);
}
std::unique_ptr<hier_node> take_hier_root()
std::unique_ptr<lnav::document::hier_node> take_hier_root()
{
return std::move(this->pp_hier_stage);
}
@ -152,9 +128,9 @@ private:
int pp_shift_accum{0};
bool pp_is_xml{false};
std::vector<interval_state> pp_interval_state;
std::vector<pretty_interval> pp_intervals;
std::vector<std::unique_ptr<hier_node>> pp_hier_nodes;
std::unique_ptr<hier_node> pp_hier_stage;
std::vector<lnav::document::section_interval_t> pp_intervals;
std::vector<std::unique_ptr<lnav::document::hier_node>> pp_hier_nodes;
std::unique_ptr<lnav::document::hier_node> pp_hier_stage;
};
#endif

@ -31,6 +31,7 @@
#include "readline_highlighters.hh"
#include "base/attr_line.builder.hh"
#include "base/string_util.hh"
#include "config.h"
#include "pcrepp/pcrepp.hh"
@ -39,7 +40,13 @@
#include "sql_util.hh"
#include "view_curses.hh"
static void readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip);
static void readline_sqlite_highlighter_int(attr_line_t& al,
int x,
line_range sub);
static void readline_shlex_highlighter_int(attr_line_t& al,
int x,
line_range sub);
static bool
check_re_prev(const std::string& line, int x)
@ -69,80 +76,79 @@ is_bracket(const std::string& str, int index, bool is_lit)
}
static void
find_matching_bracket(attr_line_t& al, int x, char left, char right)
find_matching_bracket(
attr_line_t& al, int x, line_range sub, char left, char right)
{
view_colors& vc = view_colors::singleton();
int matching_bracket_attrs
= A_BOLD | A_REVERSE | vc.attrs_for_role(role_t::VCR_OK);
int missing_bracket_attrs
= A_BOLD | A_REVERSE | vc.attrs_for_role(role_t::VCR_ERROR);
bool is_lit = (left == 'Q');
const std::string& line = al.get_string();
attr_line_builder alb(al);
const auto& line = al.get_string();
int depth = 0;
if (x < 0 || x > (int) line.length()) {
if (x < sub.lr_start || x > sub.lr_end) {
return;
}
if (line[x] == right && is_bracket(line, x, is_lit)) {
for (int lpc = x - 1; lpc > 0; lpc--) {
for (int lpc = x - 1; lpc >= sub.lr_start; lpc--) {
if (line[lpc] == right && is_bracket(line, lpc, is_lit)) {
depth += 1;
} else if (line[lpc] == left && is_bracket(line, lpc, is_lit)) {
if (depth == 0) {
al.get_attrs().emplace_back(
line_range(lpc, lpc + 1),
VC_STYLE.value(matching_bracket_attrs));
alb.overlay_attr_for_char(
lpc, VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr_for_char(lpc,
VC_ROLE.value(role_t::VCR_OK));
break;
} else {
depth -= 1;
}
depth -= 1;
}
}
}
if (line[x] == left && is_bracket(line, x, is_lit)) {
for (size_t lpc = x + 1; lpc < line.length(); lpc++) {
for (size_t lpc = x + 1; lpc < sub.lr_end; lpc++) {
if (line[lpc] == left && is_bracket(line, lpc, is_lit)) {
depth += 1;
} else if (line[lpc] == right && is_bracket(line, lpc, is_lit)) {
if (depth == 0) {
al.get_attrs().emplace_back(
line_range(lpc, lpc + 1),
VC_STYLE.value(matching_bracket_attrs));
alb.overlay_attr_for_char(
lpc, VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr_for_char(lpc,
VC_ROLE.value(role_t::VCR_OK));
break;
} else {
depth -= 1;
}
depth -= 1;
}
}
}
int first_left = -1;
nonstd::optional<int> first_left;
depth = 0;
for (size_t lpc = 1; lpc < line.length(); lpc++) {
for (size_t lpc = sub.lr_start; lpc < sub.lr_end; lpc++) {
if (line[lpc] == left && is_bracket(line, lpc, is_lit)) {
depth += 1;
if (first_left == -1) {
if (!first_left) {
first_left = lpc;
}
} else if (line[lpc] == right && is_bracket(line, lpc, is_lit)) {
if (depth > 0) {
depth -= 1;
} else {
al.get_attrs().emplace_back(
line_range(is_lit ? lpc - 1 : lpc, lpc + 1),
VC_STYLE.value(missing_bracket_attrs));
auto lr = line_range(is_lit ? lpc - 1 : lpc, lpc + 1);
alb.overlay_attr(lr, VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_ERROR));
}
}
}
if (depth > 0) {
al.get_attrs().emplace_back(
line_range(is_lit ? first_left - 1 : first_left, first_left + 1),
VC_STYLE.value(missing_bracket_attrs));
auto lr
= line_range(is_lit ? first_left.value() - 1 : first_left.value(),
first_left.value() + 1);
alb.overlay_attr(lr, VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_ERROR));
}
}
@ -157,16 +163,8 @@ safe_read(const std::string& str, std::string::size_type index)
}
static void
readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
readline_regex_highlighter_int(attr_line_t& al, int x, line_range sub)
{
view_colors& vc = view_colors::singleton();
int special_char = (A_BOLD | vc.attrs_for_role(role_t::VCR_RE_SPECIAL));
int class_attrs = (A_BOLD | vc.attrs_for_role(role_t::VCR_SYMBOL));
int repeated_char_attrs = vc.attrs_for_role(role_t::VCR_RE_REPEAT);
int bracket_attrs = vc.attrs_for_role(role_t::VCR_OK);
int error_attrs
= (A_BOLD | A_REVERSE | vc.attrs_for_role(role_t::VCR_ERROR));
static const char* brackets[] = {
"[]",
"{}",
@ -176,10 +174,11 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
nullptr,
};
auto& line = al.get_string();
const auto& line = al.get_string();
attr_line_builder alb(al);
bool backslash_is_quoted = false;
for (size_t lpc = skip; lpc < line.length(); lpc++) {
for (size_t lpc = sub.lr_start; lpc < sub.lr_end; lpc++) {
if (line[lpc - 1] != '\\') {
switch (line[lpc]) {
case '^':
@ -188,20 +187,24 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
case '+':
case '|':
case '.':
al.get_attrs().emplace_back(line_range(lpc, lpc + 1),
VC_STYLE.value(special_char));
alb.overlay_attr_for_char(
lpc, VC_ROLE.value(role_t::VCR_RE_SPECIAL));
if ((line[lpc] == '*' || line[lpc] == '+')
&& check_re_prev(line, lpc)) {
al.get_attrs().emplace_back(
line_range(lpc - 1, lpc),
VC_STYLE.value(repeated_char_attrs));
alb.overlay_attr_for_char(
lpc - 1, VC_ROLE.value(role_t::VCR_RE_REPEAT));
}
break;
case '?': {
struct line_range lr(lpc, lpc + 1);
if (line[lpc - 1] == '(') {
if (lpc == sub.lr_start || (lpc - sub.lr_start) == 0) {
alb.overlay_attr_for_char(
lpc, VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr_for_char(
lpc, VC_ROLE.value(role_t::VCR_ERROR));
} else if (line[lpc - 1] == '(') {
switch (line[lpc + 1]) {
case ':':
case '!':
@ -211,16 +214,14 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
lr.lr_end += 1;
break;
}
al.get_attrs().emplace_back(
lr, VC_STYLE.value(bracket_attrs));
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_OK));
} else {
al.get_attrs().emplace_back(
lr, VC_STYLE.value(special_char));
alb.overlay_attr(lr,
VC_ROLE.value(role_t::VCR_RE_SPECIAL));
if (check_re_prev(line, lpc)) {
al.get_attrs().emplace_back(
line_range(lpc - 1, lpc),
VC_STYLE.value(repeated_char_attrs));
alb.overlay_attr_for_char(
lpc - 1, VC_ROLE.value(role_t::VCR_RE_REPEAT));
}
}
break;
@ -232,8 +233,8 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
case '}':
case '[':
case ']':
al.get_attrs().emplace_back(line_range(lpc, lpc + 1),
VC_STYLE.value(bracket_attrs));
alb.overlay_attr_for_char(lpc,
VC_ROLE.value(role_t::VCR_OK));
break;
}
}
@ -245,8 +246,8 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
switch (line[lpc]) {
case '\\':
backslash_is_quoted = true;
al.with_attr(string_attr(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(special_char)));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_RE_SPECIAL));
break;
case 'd':
case 'D':
@ -268,38 +269,41 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
case 'G':
case 'Z':
case 'z':
al.get_attrs().emplace_back(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(class_attrs));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_SYMBOL));
break;
case ' ':
al.get_attrs().emplace_back(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(error_attrs));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_ERROR));
break;
case '0':
case 'x':
if (safe_read(line, lpc + 1) == '{') {
al.with_attr(string_attr(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(special_char)));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_RE_SPECIAL));
} else if (isdigit(safe_read(line, lpc + 1))
&& isdigit(safe_read(line, lpc + 2)))
{
al.with_attr(string_attr(line_range(lpc - 1, lpc + 3),
VC_STYLE.value(special_char)));
alb.overlay_attr(line_range(lpc - 1, lpc + 3),
VC_ROLE.value(role_t::VCR_RE_SPECIAL));
} else {
al.with_attr(string_attr(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(error_attrs)));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(A_BOLD | A_REVERSE));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_ERROR));
}
break;
case 'Q':
case 'E':
al.with_attr(string_attr(line_range(lpc - 1, lpc + 1),
VC_STYLE.value(bracket_attrs)));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_OK));
break;
default:
if (isdigit(line[lpc])) {
al.get_attrs().emplace_back(
line_range(lpc - 1, lpc + 1),
VC_STYLE.value(special_char));
alb.overlay_attr(line_range(lpc - 1, lpc + 1),
VC_ROLE.value(role_t::VCR_RE_SPECIAL));
}
break;
}
@ -307,18 +311,19 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip)
}
for (int lpc = 0; brackets[lpc]; lpc++) {
find_matching_bracket(al, x, brackets[lpc][0], brackets[lpc][1]);
find_matching_bracket(al, x, sub, brackets[lpc][0], brackets[lpc][1]);
}
}
void
readline_regex_highlighter(attr_line_t& al, int x)
{
readline_regex_highlighter_int(al, x, 1);
readline_regex_highlighter_int(
al, x, line_range{1, (int) al.get_string().size()});
}
void
readline_command_highlighter(attr_line_t& al, int x)
readline_command_highlighter_int(attr_line_t& al, int x, line_range sub)
{
static const pcrepp RE_PREFIXES(
R"(^:(filter-in|filter-out|delete-filter|enable-filter|disable-filter|highlight|clear-highlight|create-search-table\s+[^\s]+\s+))");
@ -329,54 +334,56 @@ readline_command_highlighter(attr_line_t& al, int x)
static const pcrepp COLOR_PREFIXES("^:(config)");
static const pcrepp COLOR_RE("(#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3}))");
view_colors& vc = view_colors::singleton();
int keyword_attrs = (A_BOLD | vc.attrs_for_role(role_t::VCR_KEYWORD));
attr_line_builder alb(al);
const auto& line = al.get_string();
pcre_context_static<30> pc;
pcre_input pi(line);
pcre_input pi(&line[sub.lr_start], 0, sub.length());
size_t ws_index;
ws_index = line.find(' ');
auto command = line.substr(0, ws_index);
ws_index = line.find(' ', sub.lr_start);
auto command = line.substr(sub.lr_start, ws_index);
if (ws_index != std::string::npos) {
al.get_attrs().emplace_back(line_range(1, ws_index),
VC_STYLE.value(keyword_attrs));
alb.overlay_attr(line_range(sub.lr_start + 1, ws_index),
VC_ROLE.value(role_t::VCR_KEYWORD));
}
if (RE_PREFIXES.match(pc, pi)) {
readline_regex_highlighter_int(al, x, 1 + pc[0]->length());
readline_regex_highlighter_int(
al, x, line_range{(int) ws_index, sub.lr_end});
}
pi.reset(line);
pi.reset(&line[sub.lr_start], 0, sub.length());
if (SH_PREFIXES.match(pc, pi)) {
readline_shlex_highlighter(al, x);
readline_shlex_highlighter_int(
al, x, line_range{(int) ws_index, sub.lr_end});
}
pi.reset(line);
pi.reset(&line[sub.lr_start], 0, sub.length());
if (SQL_PREFIXES.match(pc, pi)) {
readline_sqlite_highlighter_int(al, x, 1 + pc[0]->length());
readline_sqlite_highlighter_int(
al, x, line_range{(int) ws_index, sub.lr_end});
}
pi.reset(line);
pi.reset(&line[sub.lr_start], 0, sub.length());
if (COLOR_PREFIXES.match(pc, pi)) {
pi.reset(line);
pi.reset(&line[sub.lr_start], 0, sub.length());
if (COLOR_RE.match(pc, pi)) {
pcre_context::capture_t* cap = pc[0];
auto hash_color = pi.get_substr(cap);
styling::color_unit::from_str(hash_color)
.then([&](const auto& rgb_fg) {
al.get_attrs().emplace_back(
line_range{cap->c_begin, cap->c_begin + 1},
alb.template overlay_attr(
line_range{sub.lr_start + cap->c_begin,
sub.lr_start + cap->c_begin + 1},
VC_ROLE.value(role_t::VCR_COLOR_HINT));
});
}
}
pi.reset(line);
pi.reset(&line[sub.lr_start], 0, sub.length());
if (IDENT_PREFIXES.match(pc, pi) && ws_index != std::string::npos) {
size_t start = ws_index, last;
do {
for (; start < line.length() && isspace(line[start]); start++)
for (; start < sub.length() && isspace(line[start]); start++)
;
for (last = start; last < line.length() && !isspace(line[last]);
for (last = start; last < sub.length() && !isspace(line[last]);
last++)
;
struct line_range lr {
@ -392,169 +399,139 @@ readline_command_highlighter(attr_line_t& al, int x)
{
value = "#" + value;
}
al.get_attrs().emplace_back(
lr, VC_STYLE.value(vc.attrs_for_ident(value)));
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_IDENTIFIER));
}
start = last;
} while (start < line.length());
} while (start < sub.length());
}
}
static void
readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip)
void
readline_command_highlighter(attr_line_t& al, int x)
{
static const auto keyword_re_str = sql_keyword_re();
static pcrepp keyword_pcre(keyword_re_str.c_str(), PCRE_CASELESS);
static pcrepp string_literal_pcre("'[^']*('(?:'[^']*')*|$)");
static pcrepp ident_pcre(
"(?:\\$|:)?(\\b[a-z_]\\w*)|\"([^\"]+)\"|\\[([^\\]]+)]", PCRE_CASELESS);
static const char* brackets[] = {"[]",
"()",
readline_command_highlighter_int(
al, x, line_range{0, (int) al.get_string().length()});
}
nullptr};
static void
readline_sqlite_highlighter_int(attr_line_t& al, int x, line_range sub)
{
static const char* brackets[] = {
"[]",
"()",
auto& vc = view_colors::singleton();
nullptr,
};
int keyword_attrs = vc.attrs_for_role(role_t::VCR_KEYWORD);
int symbol_attrs = vc.attrs_for_role(role_t::VCR_SYMBOL);
int string_attrs = vc.attrs_for_role(role_t::VCR_STRING);
int error_attrs = vc.attrs_for_role(role_t::VCR_ERROR) | A_REVERSE;
attr_line_builder alb(al);
const auto& line = al.get_string();
pcre_context_static<30> pc;
pcre_input pi(al.get_string(), skip);
auto& line = al.get_string();
auto anno_sql = al.subline(sub.lr_start, sub.length());
anno_sql.get_attrs().clear();
annotate_sql_statement(anno_sql);
if (startswith(line, ";.")) {
auto space = line.find(' ');
struct line_range lr {
2, -1
for (const auto& attr : anno_sql.al_attrs) {
line_range lr{
sub.lr_start + attr.sa_range.lr_start,
sub.lr_start + attr.sa_range.lr_end,
};
if (space != std::string::npos) {
lr.lr_end = space;
}
al.get_attrs().emplace_back(lr, VC_STYLE.value(keyword_attrs));
return;
}
while (ident_pcre.match(pc, pi)) {
pcre_context::capture_t* cap = pc.first_valid();
int attrs = vc.attrs_for_ident(pi.get_substr_start(cap), cap->length());
struct line_range lr(cap->c_begin, cap->c_end);
if (line[cap->c_end] == '(') {
} else if (!lr.contains(x) && !lr.contains(x - 1)) {
al.get_attrs().emplace_back(lr, VC_STYLE.value(attrs));
}
}
pi.reset(line, skip);
while (keyword_pcre.match(pc, pi)) {
pcre_context::capture_t* cap = pc.all();
al.get_attrs().emplace_back(line_range(cap->c_begin, cap->c_end),
VC_STYLE.value(keyword_attrs));
}
for (size_t lpc = skip; lpc < line.length(); lpc++) {
switch (line[lpc]) {
case '*':
case '<':
case '>':
case '=':
case '!':
case '-':
case '+':
al.get_attrs().emplace_back(line_range(lpc, lpc + 1),
VC_STYLE.value(symbol_attrs));
break;
}
}
pi.reset(line, skip);
while (string_literal_pcre.match(pc, pi)) {
pcre_context::capture_t* cap = pc.all();
struct line_range lr(cap->c_begin, cap->c_end);
string_attrs_t& sa = al.get_attrs();
remove_string_attr(sa, lr);
if (line[cap->c_end - 1] != '\'') {
sa.emplace_back(line_range(cap->c_begin, cap->c_begin + 1),
VC_STYLE.value(error_attrs));
lr.lr_start += 1;
if (attr.sa_type == &SQL_COMMAND_ATTR
|| attr.sa_type == &SQL_KEYWORD_ATTR) {
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_KEYWORD));
} else if (attr.sa_type == &SQL_IDENTIFIER_ATTR) {
if (!attr.sa_range.contains(x) && attr.sa_range.lr_end != x) {
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_IDENTIFIER));
}
} else if (attr.sa_type == &SQL_FUNCTION_ATTR) {
alb.overlay_attr(
line_range{lr.lr_start, (int) line.find('(', lr.lr_start)},
VC_ROLE.value(role_t::VCR_SYMBOL));
} else if (attr.sa_type == &SQL_STRING_ATTR) {
if (lr.length() > 1 && al.al_string[lr.lr_end - 1] == '\'') {
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_STRING));
} else {
alb.overlay_attr_for_char(lr.lr_start,
VC_STYLE.value(A_REVERSE));
alb.overlay_attr_for_char(lr.lr_start,
VC_ROLE.value(role_t::VCR_ERROR));
}
} else if (attr.sa_type == &SQL_OPERATOR_ATTR) {
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_SYMBOL));
} else if (attr.sa_type == &SQL_COMMENT_ATTR) {
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_COMMENT));
}
sa.emplace_back(lr, VC_STYLE.value(string_attrs));
}
for (int lpc = 0; brackets[lpc]; lpc++) {
find_matching_bracket(al, x, brackets[lpc][0], brackets[lpc][1]);
find_matching_bracket(al, x, sub, brackets[lpc][0], brackets[lpc][1]);
}
}
void
readline_sqlite_highlighter(attr_line_t& al, int x)
{
readline_sqlite_highlighter_int(al, x, 0);
readline_sqlite_highlighter_int(
al, x, line_range{0, (int) al.get_string().length()});
}
void
readline_shlex_highlighter(attr_line_t& al, int x)
static void
readline_shlex_highlighter_int(attr_line_t& al, int x, line_range sub)
{
view_colors& vc = view_colors::singleton();
int special_char = (A_BOLD | vc.attrs_for_role(role_t::VCR_SYMBOL));
int error_attrs = vc.attrs_for_role(role_t::VCR_ERROR) | A_REVERSE;
int string_attrs = vc.attrs_for_role(role_t::VCR_STRING);
attr_line_builder alb(al);
const auto& str = al.get_string();
pcre_context::capture_t cap;
shlex_token_t token;
int quote_start = -1;
shlex lexer(str);
nonstd::optional<int> quote_start;
shlex lexer(string_fragment{al.al_string.data(), sub.lr_start, sub.lr_end});
while (lexer.tokenize(cap, token)) {
switch (token) {
case shlex_token_t::ST_ERROR:
al.with_attr(string_attr(line_range(cap.c_begin, cap.c_end),
VC_STYLE.value(error_attrs)));
alb.overlay_attr(line_range(sub.lr_start + cap.c_begin,
sub.lr_start + cap.c_end),
VC_STYLE.value(A_REVERSE));
alb.overlay_attr(line_range(sub.lr_start + cap.c_begin,
sub.lr_start + cap.c_end),
VC_ROLE.value(role_t::VCR_ERROR));
break;
case shlex_token_t::ST_TILDE:
case shlex_token_t::ST_ESCAPE:
al.with_attr(string_attr(line_range(cap.c_begin, cap.c_end),
VC_STYLE.value(special_char)));
alb.overlay_attr(line_range(sub.lr_start + cap.c_begin,
sub.lr_start + cap.c_end),
VC_ROLE.value(role_t::VCR_SYMBOL));
break;
case shlex_token_t::ST_DOUBLE_QUOTE_START:
case shlex_token_t::ST_SINGLE_QUOTE_START:
quote_start = cap.c_begin;
quote_start = sub.lr_start + cap.c_begin;
break;
case shlex_token_t::ST_DOUBLE_QUOTE_END:
case shlex_token_t::ST_SINGLE_QUOTE_END:
al.with_attr(string_attr(line_range(quote_start, cap.c_end),
VC_STYLE.value(string_attrs)));
quote_start = -1;
alb.overlay_attr(
line_range(quote_start.value(), sub.lr_start + cap.c_end),
VC_ROLE.value(role_t::VCR_STRING));
quote_start = nonstd::nullopt;
break;
case shlex_token_t::ST_VARIABLE_REF:
case shlex_token_t::ST_QUOTED_VARIABLE_REF: {
int extra = token == shlex_token_t::ST_VARIABLE_REF ? 0 : 1;
auto ident = str.substr(cap.c_begin + 1 + extra,
auto ident = str.substr(sub.lr_start + cap.c_begin + 1 + extra,
cap.length() - 1 - extra * 2);
int attrs = vc.attrs_for_ident(ident);
al.with_attr(string_attr(
line_range(cap.c_begin, cap.c_begin + 1 + extra),
VC_STYLE.value(special_char)));
al.with_attr(string_attr(
line_range(cap.c_begin + 1 + extra, cap.c_end - extra),
VC_STYLE.value(x == cap.c_end || cap.contains(x)
? special_char
: attrs)));
alb.overlay_attr(
line_range(sub.lr_start + cap.c_begin,
sub.lr_start + cap.c_begin + 1 + extra),
VC_ROLE.value(role_t::VCR_SYMBOL));
alb.overlay_attr(
line_range(sub.lr_start + cap.c_begin + 1 + extra,
sub.lr_start + cap.c_end - extra),
VC_ROLE.value(x == sub.lr_start + cap.c_end
|| cap.contains(x)
? role_t::VCR_SYMBOL
: role_t::VCR_IDENTIFIER));
if (extra) {
al.with_attr(
string_attr(line_range(cap.c_end - 1, cap.c_end),
VC_STYLE.value(special_char)));
alb.overlay_attr_for_char(
sub.lr_start + cap.c_end - 1,
VC_ROLE.value(role_t::VCR_SYMBOL));
}
break;
}
@ -563,8 +540,111 @@ readline_shlex_highlighter(attr_line_t& al, int x)
}
}
if (quote_start != -1) {
al.with_attr(string_attr(line_range(quote_start, quote_start + 1),
VC_STYLE.value(error_attrs)));
if (quote_start) {
alb.overlay_attr_for_char(quote_start.value(),
VC_ROLE.value(role_t::VCR_ERROR));
}
}
void
readline_shlex_highlighter(attr_line_t& al, int x)
{
readline_shlex_highlighter_int(
al, x, line_range{0, (int) al.al_string.length()});
}
static void
readline_lnav_highlighter_int(attr_line_t& al, int x, line_range sub)
{
switch (al.al_string[sub.lr_start]) {
case ':':
readline_command_highlighter_int(al, x, sub);
break;
case ';':
readline_sqlite_highlighter_int(al,
x,
line_range{
sub.lr_start + 1,
sub.lr_end,
});
break;
case '|':
break;
case '/':
readline_regex_highlighter_int(al,
x,
line_range{
sub.lr_start + 1,
sub.lr_end,
});
break;
}
}
void
readline_lnav_highlighter(attr_line_t& al, int x)
{
static const pcrepp COMMENT_RE{R"(^\s*#)"};
attr_line_builder alb(al);
size_t start = 0, lf_pos;
nonstd::optional<size_t> section_start;
while ((lf_pos = al.get_string().find('\n', start)) != std::string::npos) {
line_range line{(int) start, (int) lf_pos};
if (line.empty()) {
start = lf_pos + 1;
continue;
}
pcre_input pi(&al.al_string[line.lr_start], 0, line.length());
pcre_context_static<30> pc;
if (COMMENT_RE.match(pc, pi)) {
if (section_start) {
readline_lnav_highlighter_int(al,
x,
line_range{
(int) section_start.value(),
line.lr_start,
});
section_start = nonstd::nullopt;
}
const auto* cap = pc.all();
alb.overlay_attr(
line_range{line.lr_start + cap->c_begin, (int) lf_pos},
VC_ROLE.value(role_t::VCR_COMMENT));
} else {
switch (al.al_string[line.lr_start]) {
case ':':
case ';':
case '|':
case '/':
if (section_start) {
readline_lnav_highlighter_int(
al,
x,
line_range{
(int) section_start.value(),
line.lr_start,
});
}
section_start = line.lr_start;
break;
}
}
start = lf_pos;
}
if (section_start) {
readline_lnav_highlighter_int(al,
x,
line_range{
(int) section_start.value(),
(int) al.al_string.length(),
});
}
}

@ -42,4 +42,6 @@ void readline_sqlite_highlighter(attr_line_t& line, int x);
void readline_shlex_highlighter(attr_line_t& line, int x);
void readline_lnav_highlighter(attr_line_t& line, int x);
#endif

@ -52,15 +52,15 @@ struct regexp_capture {
-- The regexp_capture() table-valued function allows you to execute a regular-
-- expression over a given string and get the captured data as rows in a table.
CREATE TABLE regexp_capture (
match_index integer,
capture_index integer,
capture_name text,
capture_count integer,
range_start integer,
range_stop integer,
content text,
value text HIDDEN,
pattern text HIDDEN
match_index INTEGER,
capture_index INTEGER,
capture_name TEXT,
capture_count INTEGER,
range_start INTEGER,
range_stop INTEGER,
content TEXT,
value TEXT HIDDEN,
pattern TEXT HIDDEN
);
)";

@ -115,12 +115,17 @@ public:
return nullptr;
}
string_fragment to_string_fragment(off_t offset, size_t len)
string_fragment to_string_fragment(off_t offset, size_t len) const
{
return string_fragment{
this->sb_data, (int) offset, (int) (offset + len)};
}
string_fragment to_string_fragment() const
{
return string_fragment{this->sb_data, 0, (int) this->length()};
}
void share(shared_buffer& sb, char* data, size_t len);
bool subset(shared_buffer_ref& other, off_t offset, size_t len);

@ -45,6 +45,7 @@ extern string_attr_type<void> SQL_STRING_ATTR;
extern string_attr_type<void> SQL_OPERATOR_ATTR;
extern string_attr_type<void> SQL_PAREN_ATTR;
extern string_attr_type<void> SQL_GARBAGE_ATTR;
extern string_attr_type<void> SQL_COMMENT_ATTR;
void annotate_sql_statement(attr_line_t& al_inout);

@ -685,6 +685,8 @@ sql_compile_script(sqlite3* db,
} else {
sql_content.append(script);
}
sql_content.with_attr_for_all(
VC_ROLE.value(role_t::VCR_QUOTED_CODE));
errors.emplace_back(
lnav::console::user_message::error(
"failed to compile SQL statement")
@ -766,16 +768,18 @@ sql_execute_script(sqlite3* db,
}
default: {
attr_line_t sql_content(sqlite3_sql(stmt));
const char* errmsg;
errmsg = sqlite3_errmsg(db);
sql_content.with_attr_for_all(
VC_ROLE.value(role_t::VCR_QUOTED_CODE));
errors.emplace_back(
lnav::console::user_message::error(
"failed to execute SQL statement")
.with_reason(errmsg)
.with_snippet(lnav::console::snippet::from(
intern_string::lookup(src_name),
sqlite3_sql(stmt))));
intern_string::lookup(src_name), sql_content)));
done = true;
break;
}
@ -897,10 +901,12 @@ string_attr_type<void> SQL_KEYWORD_ATTR("sql_keyword");
string_attr_type<void> SQL_IDENTIFIER_ATTR("sql_ident");
string_attr_type<void> SQL_FUNCTION_ATTR("sql_func");
string_attr_type<void> SQL_STRING_ATTR("sql_string");
string_attr_type<void> SQL_UNTERMINATED_STRING_ATTR("sql_unstring");
string_attr_type<void> SQL_OPERATOR_ATTR("sql_oper");
string_attr_type<void> SQL_PAREN_ATTR("sql_paren");
string_attr_type<void> SQL_COMMA_ATTR("sql_comma");
string_attr_type<void> SQL_GARBAGE_ATTR("sql_garbage");
string_attr_type<void> SQL_COMMENT_ATTR("sql_comment");
void
annotate_sql_statement(attr_line_t& al)
@ -919,6 +925,7 @@ annotate_sql_statement(attr_line_t& al)
{pcrepp{R"(\A(\$?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)])",
PCRE_CASELESS},
&SQL_IDENTIFIER_ATTR},
{pcrepp{R"(\A--.*)"}, &SQL_COMMENT_ATTR},
{pcrepp{R"(\A(\*|<|>|=|!|\-|\+|\|\|))"}, &SQL_OPERATOR_ATTR},
{pcrepp{R"(\A.)"}, &SQL_GARBAGE_ATTR},
};

@ -184,6 +184,8 @@ struct lnav_theme {
style_config lt_style_status_disabled_title;
style_config lt_style_status_subtitle;
style_config lt_style_status_hotkey;
style_config lt_style_quoted_code;
style_config lt_style_code_border;
style_config lt_style_keyword;
style_config lt_style_string;
style_config lt_style_comment;
@ -207,8 +209,16 @@ struct lnav_theme {
style_config lt_style_inactive_alert_status;
style_config lt_style_file;
style_config lt_style_header[6];
style_config lt_style_hr;
style_config lt_style_hyperlink;
style_config lt_style_list_glyph;
style_config lt_style_breadcrumb;
style_config lt_style_table_border;
style_config lt_style_table_header;
style_config lt_style_quote_border;
style_config lt_style_quoted_text;
style_config lt_style_footnote_border;
style_config lt_style_footnote_text;
std::map<log_level_t, style_config> lt_level_styles;
std::map<std::string, highlighter_config> lt_highlights;
};

@ -40,7 +40,8 @@ libtailercommon_a_SOURCES = \
libtailerpp_a_CPPFLAGS = \
$(AM_CPPFLAGS) \
-I$(srcdir)/.. \
-I$(srcdir)/../fmtlib
-I$(srcdir)/../fmtlib \
-I$(srcdir)/../third-party
libtailerpp_a_SOURCES = \
tailerpp.cc
@ -51,7 +52,8 @@ tailerbin.h tailerbin.cc: tailer tailer.ape ../tools/bin2c$(BUILD_EXEEXT)
libtailerservice_a_CPPFLAGS = \
$(AM_CPPFLAGS) \
-I$(srcdir)/.. \
-I$(srcdir)/../fmtlib
-I$(srcdir)/../fmtlib \
-I$(srcdir)/../third-party
libtailerservice_a_LIBADD = \
libtailercommon.a \

@ -36,8 +36,13 @@
#include "yajl/api/yajl_parse.h"
text_format_t
detect_text_format(const char* str, size_t len)
detect_text_format(string_fragment sf,
nonstd::optional<ghc::filesystem::path> path)
{
static const auto GZ_EXT = ghc::filesystem::path(".gz");
static const auto BZ2_EXT = ghc::filesystem::path(".bz2");
static const auto MD_EXT = ghc::filesystem::path(".md");
// XXX This is a pretty crude way of detecting format...
static const pcrepp PYTHON_MATCHERS = pcrepp(
"(?:"
@ -87,14 +92,30 @@ detect_text_format(const char* str, size_t len)
PCRE_MULTILINE | PCRE_CASELESS);
text_format_t retval = text_format_t::TF_UNKNOWN;
pcre_input pi(str, 0, len);
if (path) {
if (path->extension() == GZ_EXT) {
path = path->stem();
}
if (path->extension() == BZ2_EXT) {
path = path->stem();
}
if (path->extension() == MD_EXT) {
return text_format_t::TF_MARKDOWN;
}
}
pcre_input pi(sf);
pcre_context_static<30> pc;
{
auto_mem<yajl_handle_t> jhandle(yajl_free);
jhandle = yajl_alloc(nullptr, nullptr, nullptr);
if (yajl_parse(jhandle, (unsigned char*) str, len) == yajl_status_ok) {
if (yajl_parse(jhandle, (unsigned char*) sf.data(), sf.length())
== yajl_status_ok)
{
return text_format_t::TF_JSON;
}
}

@ -36,7 +36,9 @@
#include <sys/types.h>
#include "base/intern_string.hh"
#include "fmt/format.h"
#include "ghc/filesystem.hpp"
enum class text_format_t {
TF_UNKNOWN,
@ -48,6 +50,7 @@ enum class text_format_t {
TF_SQL,
TF_XML,
TF_JSON,
TF_MARKDOWN,
};
namespace fmt {
@ -85,6 +88,9 @@ struct formatter<text_format_t> : formatter<string_view> {
case text_format_t::TF_JSON:
name = "application/json";
break;
case text_format_t::TF_MARKDOWN:
name = "text/markdown";
break;
}
return formatter<string_view>::format(name, ctx);
}
@ -94,16 +100,10 @@ struct formatter<text_format_t> : formatter<string_view> {
/**
* Try to detect the format of the given text file fragment.
*
* @param str The text to scan.
* @param len The length of the 'str' buffer.
* @return The detected format.
*/
text_format_t detect_text_format(const char* str, size_t len);
inline text_format_t
detect_text_format(const std::string& str)
{
return detect_text_format(str.c_str(), str.length());
}
text_format_t detect_text_format(string_fragment sf,
nonstd::optional<ghc::filesystem::path> path
= nonstd::nullopt);
#endif

@ -29,8 +29,10 @@
#include "textfile_sub_source.hh"
#include "base/fs_util.hh"
#include "base/itertools.hh"
#include "config.h"
#include "md2attr_line.hh"
size_t
textfile_sub_source::text_line_count()
@ -39,8 +41,13 @@ textfile_sub_source::text_line_count()
if (!this->tss_files.empty()) {
std::shared_ptr<logfile> lf = this->current_file();
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
retval = lfo->lfo_filter_state.tfs_index.size();
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter == this->tss_rendered_files.end()) {
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
retval = lfo->lfo_filter_state.tfs_index.size();
} else {
retval = rend_iter->second.rf_text_source->text_line_count();
}
}
return retval;
@ -54,11 +61,18 @@ textfile_sub_source::text_value_for_line(textview_curses& tc,
{
if (!this->tss_files.empty()) {
std::shared_ptr<logfile> lf = this->current_file();
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
auto read_result = lf->read_line(
lf->begin() + lfo->lfo_filter_state.tfs_index[line]);
if (read_result.isOk()) {
value_out = to_string(read_result.unwrap());
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter == this->tss_rendered_files.end()) {
auto* lfo = dynamic_cast<line_filter_observer*>(
lf->get_logline_observer());
auto read_result = lf->read_line(
lf->begin() + lfo->lfo_filter_state.tfs_index[line]);
if (read_result.isOk()) {
value_out = to_string(read_result.unwrap());
}
} else {
rend_iter->second.rf_text_source->text_value_for_line(
tc, line, value_out, flags);
}
} else {
value_out.clear();
@ -70,10 +84,17 @@ textfile_sub_source::text_attrs_for_line(textview_curses& tc,
int row,
string_attrs_t& value_out)
{
if (this->current_file() == nullptr) {
auto lf = this->current_file();
if (lf == nullptr) {
return;
}
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
rend_iter->second.rf_text_source->text_attrs_for_line(
tc, row, value_out);
}
struct line_range lr;
lr.lr_start = 0;
@ -90,9 +111,16 @@ textfile_sub_source::text_size_for_line(textview_curses& tc,
if (!this->tss_files.empty()) {
std::shared_ptr<logfile> lf = this->current_file();
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
retval = lf->line_length(lf->begin()
+ lfo->lfo_filter_state.tfs_index[line]);
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter == this->tss_rendered_files.end()) {
auto* lfo = dynamic_cast<line_filter_observer*>(
lf->get_logline_observer());
retval = lf->line_length(lf->begin()
+ lfo->lfo_filter_state.tfs_index[line]);
} else {
retval = rend_iter->second.rf_text_source->text_size_for_line(
tc, line, flags);
}
}
return retval;
@ -208,8 +236,11 @@ textfile_sub_source::get_filtered_count() const
int retval = 0;
if (lf != nullptr) {
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
retval = lf->size() - lfo->lfo_filter_state.tfs_index.size();
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter == this->tss_rendered_files.end()) {
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
retval = lf->size() - lfo->lfo_filter_state.tfs_index.size();
}
}
return retval;
}
@ -223,7 +254,7 @@ textfile_sub_source::get_filtered_count_for(size_t filter_index) const
return 0;
}
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
auto* lfo = dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
return lfo->lfo_filter_state.tfs_filter_hits[filter_index];
}
@ -274,4 +305,312 @@ textfile_sub_source::text_crumbs_for_line(
this->to_front(lf_opt.value());
this->tss_view->reload_data();
});
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
rend_iter->second.rf_text_source->text_crumbs_for_line(line, crumbs);
}
auto meta_iter = this->tss_doc_metadata.find(lf->get_filename());
if (meta_iter != this->tss_doc_metadata.end()) {
auto* lfo
= dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[line];
auto ll_next_iter = ll_iter + 1;
auto end_offset = (ll_next_iter == lf->end())
? lf->get_index_size()
: ll_next_iter->get_offset();
const auto initial_size = crumbs.size();
meta_iter->second.ms_metadata.m_sections_tree.visit_overlapping(
ll_iter->get_offset(),
end_offset,
[&crumbs,
initial_size,
meta = &meta_iter->second.ms_metadata,
this,
lf](const auto& iv) {
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key)
| lnav::itertools::append(iv.value);
auto curr_node = lnav::document::hier_node::lookup_path(
meta->m_sections_root.get(), path);
crumbs.template emplace_back(
iv.value,
[meta, path]() { return meta->possibility_provider(path); },
[this, curr_node, path, lf](const auto& key) {
if (!curr_node) {
return;
}
auto* parent_node = curr_node.value()->hn_parent;
if (parent_node == nullptr) {
return;
}
key.template match(
[this, parent_node](const std::string& str) {
auto sib_iter
= parent_node->hn_named_children.find(str);
if (sib_iter
== parent_node->hn_named_children.end()) {
return;
}
this->set_top_from_off(
sib_iter->second->hn_start);
},
[this, parent_node](size_t index) {
if (index >= parent_node->hn_children.size()) {
return;
}
auto sib
= parent_node->hn_children[index].get();
this->set_top_from_off(sib->hn_start);
});
});
if (curr_node
&& curr_node.value()->hn_parent->hn_children.size()
!= curr_node.value()
->hn_parent->hn_named_children.size())
{
auto node = lnav::document::hier_node::lookup_path(
meta->m_sections_root.get(), path);
crumbs.back().c_expected_input
= curr_node.value()
->hn_parent->hn_named_children.empty()
? breadcrumb::crumb::expected_input_t::index
: breadcrumb::crumb::expected_input_t::index_or_exact;
crumbs.back().with_possible_range(
node | lnav::itertools::map([](const auto hn) {
return hn->hn_parent->hn_children.size();
})
| lnav::itertools::unwrap_or(size_t{0}));
}
});
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key);
auto node = lnav::document::hier_node::lookup_path(
meta_iter->second.ms_metadata.m_sections_root.get(), path);
if (node && !node.value()->hn_children.empty()) {
auto poss_provider = [curr_node = node.value()]() {
std::vector<breadcrumb::possibility> retval;
for (const auto& child : curr_node->hn_named_children) {
retval.template emplace_back(child.first);
}
return retval;
};
auto path_performer = [this, curr_node = node.value()](
const breadcrumb::crumb::key_t& value) {
value.template match(
[this, curr_node](const std::string& str) {
auto child_iter
= curr_node->hn_named_children.find(str);
if (child_iter != curr_node->hn_named_children.end()) {
this->set_top_from_off(
child_iter->second->hn_start);
}
},
[this, curr_node](size_t index) {
if (index >= curr_node->hn_children.size()) {
return;
}
auto* child = curr_node->hn_children[index].get();
this->set_top_from_off(child->hn_start);
});
};
crumbs.emplace_back("", "\u22ef", poss_provider, path_performer);
crumbs.back().c_expected_input
= node.value()->hn_named_children.empty()
? breadcrumb::crumb::expected_input_t::index
: breadcrumb::crumb::expected_input_t::index_or_exact;
}
}
}
bool
textfile_sub_source::rescan_files(
textfile_sub_source::scan_callback& callback,
nonstd::optional<ui_clock::time_point> deadline)
{
file_iterator iter;
bool retval = false;
if (this->tss_view->is_paused()) {
return retval;
}
std::vector<std::shared_ptr<logfile>> closed_files;
for (iter = this->tss_files.begin(); iter != this->tss_files.end();) {
std::shared_ptr<logfile> lf = (*iter);
if (lf->is_closed()) {
iter = this->tss_files.erase(iter);
this->tss_rendered_files.erase(lf->get_filename());
this->tss_doc_metadata.erase(lf->get_filename());
this->detach_observer(lf);
closed_files.template emplace_back(lf);
continue;
}
try {
const auto& st = lf->get_stat();
uint32_t old_size = lf->size();
auto new_text_data = lf->rebuild_index(deadline);
if (lf->get_format() != nullptr) {
iter = this->tss_files.erase(iter);
this->tss_rendered_files.erase(lf->get_filename());
this->tss_doc_metadata.erase(lf->get_filename());
this->detach_observer(lf);
callback.promote_file(lf);
continue;
}
switch (new_text_data) {
case logfile::rebuild_result_t::NEW_LINES:
case logfile::rebuild_result_t::NEW_ORDER:
retval = true;
break;
default:
break;
}
callback.scanned_file(lf);
if (lf->get_text_format() == text_format_t::TF_MARKDOWN) {
auto rend_iter
= this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
if (rend_iter->second.rf_file_size == st.st_size
&& rend_iter->second.rf_mtime == st.st_mtime)
{
++iter;
continue;
}
log_info("markdown file has been updated, re-rendering: %s",
lf->get_filename().c_str());
this->tss_rendered_files.erase(rend_iter);
}
auto read_res = lf->read_file();
if (read_res.isOk()) {
auto content = read_res.unwrap();
auto content_sf = string_fragment{content};
auto front_matter_terminator = content.length() > 8
? content.find("\n---\n", 4)
: std::string::npos;
if (startswith(content, "---\n")
&& front_matter_terminator != std::string::npos)
{
content_sf
= content_sf.substr(front_matter_terminator + 4);
}
md2attr_line mdal;
mdal.with_source_path(lf->get_actual_path());
auto parse_res = md4cpp::parse(content_sf, mdal);
if (parse_res.isOk()) {
auto& rf = this->tss_rendered_files[lf->get_filename()];
rf.rf_mtime = st.st_mtime;
rf.rf_file_size = st.st_size;
rf.rf_text_source
= std::make_unique<plain_text_source>();
rf.rf_text_source->register_view(this->tss_view);
rf.rf_text_source->replace_with(parse_res.unwrap());
log_info("successfully rendered markdown file: %s",
lf->get_filename().c_str());
} else {
log_error("unable to parse markdown file: %s -- %s",
lf->get_filename().c_str(),
parse_res.unwrapErr().c_str());
}
} else {
log_error("unable to read markdown file: %s -- %s",
lf->get_filename().c_str(),
read_res.unwrapErr().c_str());
}
++iter;
continue;
}
if (!retval) {
auto ms_iter = this->tss_doc_metadata.find(lf->get_filename());
if (ms_iter != this->tss_doc_metadata.end()) {
if (st.st_mtime != ms_iter->second.ms_mtime
|| st.st_size != ms_iter->second.ms_file_size)
{
this->tss_doc_metadata.erase(ms_iter);
ms_iter = this->tss_doc_metadata.end();
}
}
if (ms_iter == this->tss_doc_metadata.end()) {
auto read_res = lf->read_file();
if (read_res.isOk()) {
auto content = read_res.unwrap();
this->tss_doc_metadata[lf->get_filename()]
= metadata_state{
st.st_mtime,
static_cast<file_size_t>(st.st_size),
lnav::document::discover_structure(content),
};
}
}
}
uint32_t filter_in_mask, filter_out_mask;
this->get_filters().get_enabled_mask(filter_in_mask,
filter_out_mask);
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
for (uint32_t lpc = old_size; lpc < lf->size(); lpc++) {
if (this->tss_apply_filters
&& lfo->excluded(filter_in_mask, filter_out_mask, lpc))
{
continue;
}
lfo->lfo_filter_state.tfs_index.push_back(lpc);
}
} catch (const line_buffer::error& e) {
iter = this->tss_files.erase(iter);
this->tss_rendered_files.erase(lf->get_filename());
this->tss_doc_metadata.erase(lf->get_filename());
lf->close();
this->detach_observer(lf);
closed_files.template emplace_back(lf);
continue;
}
++iter;
}
if (!closed_files.empty()) {
callback.closed_files(closed_files);
}
if (retval) {
this->tss_view->search_new_data();
}
return retval;
}
void
textfile_sub_source::set_top_from_off(file_off_t off)
{
auto lf = this->current_file();
lf->line_for_offset(off) | [this, lf](auto new_top_iter) {
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
auto new_top_opt = lfo->lfo_filter_state.content_line_to_vis_line(
std::distance(lf->cbegin(), new_top_iter));
if (new_top_opt) {
this->tss_view->set_top(vis_line_t(new_top_opt.value()));
}
};
}

@ -34,6 +34,7 @@
#include "filter_observer.hh"
#include "logfile.hh"
#include "plain_text_source.hh"
#include "textview_curses.hh"
class textfile_sub_source
@ -92,6 +93,8 @@ public:
void to_front(const std::shared_ptr<logfile>& lf);
void set_top_from_off(file_off_t off);
void rotate_left();
void rotate_right();
@ -100,84 +103,18 @@ public:
void push_back(const std::shared_ptr<logfile>& lf);
template<class T>
bool rescan_files(T& callback,
nonstd::optional<ui_clock::time_point> deadline
= nonstd::nullopt)
{
file_iterator iter;
bool retval = false;
if (this->tss_view->is_paused()) {
return retval;
}
std::vector<std::shared_ptr<logfile>> closed_files;
for (iter = this->tss_files.begin(); iter != this->tss_files.end();) {
std::shared_ptr<logfile> lf = (*iter);
if (lf->is_closed()) {
iter = this->tss_files.erase(iter);
this->detach_observer(lf);
closed_files.template emplace_back(lf);
continue;
}
try {
uint32_t old_size = lf->size();
logfile::rebuild_result_t new_text_data
= lf->rebuild_index(deadline);
if (lf->get_format() != nullptr) {
iter = this->tss_files.erase(iter);
this->detach_observer(lf);
callback.promote_file(lf);
continue;
}
switch (new_text_data) {
case logfile::rebuild_result_t::NEW_LINES:
case logfile::rebuild_result_t::NEW_ORDER:
retval = true;
break;
default:
break;
}
callback.scanned_file(lf);
uint32_t filter_in_mask, filter_out_mask;
this->get_filters().get_enabled_mask(filter_in_mask,
filter_out_mask);
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
for (uint32_t lpc = old_size; lpc < lf->size(); lpc++) {
if (this->tss_apply_filters
&& lfo->excluded(filter_in_mask, filter_out_mask, lpc))
{
continue;
}
lfo->lfo_filter_state.tfs_index.push_back(lpc);
}
} catch (const line_buffer::error& e) {
iter = this->tss_files.erase(iter);
lf->close();
this->detach_observer(lf);
closed_files.template emplace_back(lf);
continue;
}
++iter;
}
if (!closed_files.empty()) {
callback.closed_files(closed_files);
}
if (retval) {
this->tss_view->search_new_data();
}
class scan_callback {
public:
virtual void closed_files(
const std::vector<std::shared_ptr<logfile>>& files)
= 0;
virtual void promote_file(const std::shared_ptr<logfile>& lf) = 0;
virtual void scanned_file(const std::shared_ptr<logfile>& lf) = 0;
};
return retval;
}
bool rescan_files(scan_callback& callback,
nonstd::optional<ui_clock::time_point> deadline
= nonstd::nullopt);
void text_filters_changed() override;
@ -203,8 +140,22 @@ private:
delete lfo;
}
struct rendered_file {
time_t rf_mtime;
file_size_t rf_file_size;
std::unique_ptr<plain_text_source> rf_text_source;
};
struct metadata_state {
time_t ms_mtime;
file_size_t ms_file_size;
lnav::document::metadata ms_metadata;
};
std::deque<std::shared_ptr<logfile>> tss_files;
std::deque<std::shared_ptr<logfile>> tss_hidden_files;
std::unordered_map<std::string, rendered_file> tss_rendered_files;
std::unordered_map<std::string, metadata_state> tss_doc_metadata;
};
#endif

@ -985,3 +985,91 @@ text_sub_source::text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs)
{
}
logfile_filter_state::logfile_filter_state(std::shared_ptr<logfile> lf)
: tfs_logfile(std::move(lf))
{
memset(this->tfs_filter_count, 0, sizeof(this->tfs_filter_count));
memset(this->tfs_filter_hits, 0, sizeof(this->tfs_filter_hits));
memset(this->tfs_message_matched, 0, sizeof(this->tfs_message_matched));
memset(this->tfs_lines_for_message, 0, sizeof(this->tfs_lines_for_message));
memset(this->tfs_last_message_matched,
0,
sizeof(this->tfs_last_message_matched));
memset(this->tfs_last_lines_for_message,
0,
sizeof(this->tfs_last_lines_for_message));
this->tfs_mask.reserve(64 * 1024);
}
void
logfile_filter_state::clear()
{
this->tfs_logfile = nullptr;
memset(this->tfs_filter_count, 0, sizeof(this->tfs_filter_count));
memset(this->tfs_filter_hits, 0, sizeof(this->tfs_filter_hits));
memset(this->tfs_message_matched, 0, sizeof(this->tfs_message_matched));
memset(this->tfs_lines_for_message, 0, sizeof(this->tfs_lines_for_message));
memset(this->tfs_last_message_matched,
0,
sizeof(this->tfs_last_message_matched));
memset(this->tfs_last_lines_for_message,
0,
sizeof(this->tfs_last_lines_for_message));
this->tfs_mask.clear();
this->tfs_index.clear();
}
void
logfile_filter_state::clear_filter_state(size_t index)
{
this->tfs_filter_count[index] = 0;
this->tfs_filter_hits[index] = 0;
this->tfs_message_matched[index] = false;
this->tfs_lines_for_message[index] = 0;
this->tfs_last_message_matched[index] = false;
this->tfs_last_lines_for_message[index] = 0;
}
void
logfile_filter_state::clear_deleted_filter_state(uint32_t used_mask)
{
for (int lpc = 0; lpc < MAX_FILTERS; lpc++) {
if (!(used_mask & (1L << lpc))) {
this->clear_filter_state(lpc);
}
}
for (size_t lpc = 0; lpc < this->tfs_mask.size(); lpc++) {
this->tfs_mask[lpc] &= used_mask;
}
}
void
logfile_filter_state::resize(size_t newsize)
{
size_t old_mask_size = this->tfs_mask.size();
this->tfs_mask.resize(newsize);
if (newsize > old_mask_size) {
memset(&this->tfs_mask[old_mask_size],
0,
sizeof(uint32_t) * (newsize - old_mask_size));
}
}
nonstd::optional<size_t>
logfile_filter_state::content_line_to_vis_line(uint32_t line)
{
if (this->tfs_index.empty()) {
return nonstd::nullopt;
}
auto iter = std::lower_bound(
this->tfs_index.begin(), this->tfs_index.end(), line);
if (iter == this->tfs_index.end() || *iter != line) {
return nonstd::nullopt;
}
return nonstd::make_optional(std::distance(this->tfs_index.begin(), iter));
}

@ -54,76 +54,17 @@ using vis_bookmarks = bookmarks<vis_line_t>::type;
class logfile_filter_state {
public:
logfile_filter_state(std::shared_ptr<logfile> lf = nullptr)
: tfs_logfile(std::move(lf))
{
memset(this->tfs_filter_count, 0, sizeof(this->tfs_filter_count));
memset(this->tfs_filter_hits, 0, sizeof(this->tfs_filter_hits));
memset(this->tfs_message_matched, 0, sizeof(this->tfs_message_matched));
memset(this->tfs_lines_for_message,
0,
sizeof(this->tfs_lines_for_message));
memset(this->tfs_last_message_matched,
0,
sizeof(this->tfs_last_message_matched));
memset(this->tfs_last_lines_for_message,
0,
sizeof(this->tfs_last_lines_for_message));
this->tfs_mask.reserve(64 * 1024);
}
void clear()
{
this->tfs_logfile = nullptr;
memset(this->tfs_filter_count, 0, sizeof(this->tfs_filter_count));
memset(this->tfs_filter_hits, 0, sizeof(this->tfs_filter_hits));
memset(this->tfs_message_matched, 0, sizeof(this->tfs_message_matched));
memset(this->tfs_lines_for_message,
0,
sizeof(this->tfs_lines_for_message));
memset(this->tfs_last_message_matched,
0,
sizeof(this->tfs_last_message_matched));
memset(this->tfs_last_lines_for_message,
0,
sizeof(this->tfs_last_lines_for_message));
this->tfs_mask.clear();
this->tfs_index.clear();
}
void clear_filter_state(size_t index)
{
this->tfs_filter_count[index] = 0;
this->tfs_filter_hits[index] = 0;
this->tfs_message_matched[index] = false;
this->tfs_lines_for_message[index] = 0;
this->tfs_last_message_matched[index] = false;
this->tfs_last_lines_for_message[index] = 0;
}
void clear_deleted_filter_state(uint32_t used_mask)
{
for (int lpc = 0; lpc < MAX_FILTERS; lpc++) {
if (!(used_mask & (1L << lpc))) {
this->clear_filter_state(lpc);
}
}
for (size_t lpc = 0; lpc < this->tfs_mask.size(); lpc++) {
this->tfs_mask[lpc] &= used_mask;
}
}
logfile_filter_state(std::shared_ptr<logfile> lf = nullptr);
void resize(size_t newsize)
{
size_t old_mask_size = this->tfs_mask.size();
void clear();
this->tfs_mask.resize(newsize);
if (newsize > old_mask_size) {
memset(&this->tfs_mask[old_mask_size],
0,
sizeof(uint32_t) * (newsize - old_mask_size));
}
}
void clear_filter_state(size_t index);
void clear_deleted_filter_state(uint32_t used_mask);
void resize(size_t newsize);
nonstd::optional<size_t> content_line_to_vis_line(uint32_t line);
const static int MAX_FILTERS = 32;

@ -72,13 +72,15 @@
"background-color": "#888"
},
"h1": {
"underline": true
"color": "$magenta",
"bold": true
},
"h2": {
"color": "$magenta",
"underline": true
},
"h3": {
"underline": true
"color": "$magenta"
},
"h4": {
"underline": true
@ -89,14 +91,49 @@
"h6": {
"underline": true
},
"hr": {
"color": "#444"
},
"hyperlink": {
"underline": true
},
"list-glyph": {
"color": "$yellow"
},
"breadcrumb": {
"color": "#99a"
},
"table-border": {
"color": "#444"
},
"table-header": {
"bold": true
},
"quote-border": {
"color": "#666",
"background-color": "#444"
},
"quoted-text": {
"background-color": "#444"
},
"footnote-border": {
"color": "$blue",
"background-color": "#444"
},
"footnote-text": {
"color": "#eee",
"background-color": "#444"
}
},
"syntax-styles": {
"quoted-code": {
"color": "#eee",
"background-color": "#121212"
},
"code-border": {
"color": "#444",
"background-color": "#121212"
},
"keyword": {
"color": "#ff6188",
"bold": true
@ -106,7 +143,7 @@
"bold": true
},
"comment": {
"color": "#727072"
"color": "#949194"
},
"doc-directive": {
"color": "#a9dc76"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,405 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2020 Martin Mitas
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MD4C_H
#define MD4C_H
#ifdef __cplusplus
extern "C" {
#endif
#if defined MD4C_USE_UTF16
/* Magic to support UTF-16. Note that in order to use it, you have to define
* the macro MD4C_USE_UTF16 both when building MD4C as well as when
* including this header in your code. */
#ifdef _WIN32
#include <windows.h>
typedef WCHAR MD_CHAR;
#else
#error MD4C_USE_UTF16 is only supported on Windows.
#endif
#else
typedef char MD_CHAR;
#endif
typedef unsigned MD_SIZE;
typedef unsigned MD_OFFSET;
/* Block represents a part of document hierarchy structure like a paragraph
* or list item.
*/
typedef enum MD_BLOCKTYPE {
/* <body>...</body> */
MD_BLOCK_DOC = 0,
/* <blockquote>...</blockquote> */
MD_BLOCK_QUOTE,
/* <ul>...</ul>
* Detail: Structure MD_BLOCK_UL_DETAIL. */
MD_BLOCK_UL,
/* <ol>...</ol>
* Detail: Structure MD_BLOCK_OL_DETAIL. */
MD_BLOCK_OL,
/* <li>...</li>
* Detail: Structure MD_BLOCK_LI_DETAIL. */
MD_BLOCK_LI,
/* <hr> */
MD_BLOCK_HR,
/* <h1>...</h1> (for levels up to 6)
* Detail: Structure MD_BLOCK_H_DETAIL. */
MD_BLOCK_H,
/* <pre><code>...</code></pre>
* Note the text lines within code blocks are terminated with '\n'
* instead of explicit MD_TEXT_BR. */
MD_BLOCK_CODE,
/* Raw HTML block. This itself does not correspond to any particular HTML
* tag. The contents of it _is_ raw HTML source intended to be put
* in verbatim form to the HTML output. */
MD_BLOCK_HTML,
/* <p>...</p> */
MD_BLOCK_P,
/* <table>...</table> and its contents.
* Detail: Structure MD_BLOCK_TABLE_DETAIL (for MD_BLOCK_TABLE),
* structure MD_BLOCK_TD_DETAIL (for MD_BLOCK_TH and MD_BLOCK_TD)
* Note all of these are used only if extension MD_FLAG_TABLES is enabled. */
MD_BLOCK_TABLE,
MD_BLOCK_THEAD,
MD_BLOCK_TBODY,
MD_BLOCK_TR,
MD_BLOCK_TH,
MD_BLOCK_TD
} MD_BLOCKTYPE;
/* Span represents an in-line piece of a document which should be rendered with
* the same font, color and other attributes. A sequence of spans forms a block
* like paragraph or list item. */
typedef enum MD_SPANTYPE {
/* <em>...</em> */
MD_SPAN_EM,
/* <strong>...</strong> */
MD_SPAN_STRONG,
/* <a href="xxx">...</a>
* Detail: Structure MD_SPAN_A_DETAIL. */
MD_SPAN_A,
/* <img src="xxx">...</a>
* Detail: Structure MD_SPAN_IMG_DETAIL.
* Note: Image text can contain nested spans and even nested images.
* If rendered into ALT attribute of HTML <IMG> tag, it's responsibility
* of the parser to deal with it.
*/
MD_SPAN_IMG,
/* <code>...</code> */
MD_SPAN_CODE,
/* <del>...</del>
* Note: Recognized only when MD_FLAG_STRIKETHROUGH is enabled.
*/
MD_SPAN_DEL,
/* For recognizing inline ($) and display ($$) equations
* Note: Recognized only when MD_FLAG_LATEXMATHSPANS is enabled.
*/
MD_SPAN_LATEXMATH,
MD_SPAN_LATEXMATH_DISPLAY,
/* Wiki links
* Note: Recognized only when MD_FLAG_WIKILINKS is enabled.
*/
MD_SPAN_WIKILINK,
/* <u>...</u>
* Note: Recognized only when MD_FLAG_UNDERLINE is enabled. */
MD_SPAN_U
} MD_SPANTYPE;
/* Text is the actual textual contents of span. */
typedef enum MD_TEXTTYPE {
/* Normal text. */
MD_TEXT_NORMAL = 0,
/* NULL character. CommonMark requires replacing NULL character with
* the replacement char U+FFFD, so this allows caller to do that easily. */
MD_TEXT_NULLCHAR,
/* Line breaks.
* Note these are not sent from blocks with verbatim output (MD_BLOCK_CODE
* or MD_BLOCK_HTML). In such cases, '\n' is part of the text itself. */
MD_TEXT_BR, /* <br> (hard break) */
MD_TEXT_SOFTBR, /* '\n' in source text where it is not semantically meaningful (soft break) */
/* Entity.
* (a) Named entity, e.g. &nbsp;
* (Note MD4C does not have a list of known entities.
* Anything matching the regexp /&[A-Za-z][A-Za-z0-9]{1,47};/ is
* treated as a named entity.)
* (b) Numerical entity, e.g. &#1234;
* (c) Hexadecimal entity, e.g. &#x12AB;
*
* As MD4C is mostly encoding agnostic, application gets the verbatim
* entity text into the MD_PARSER::text_callback(). */
MD_TEXT_ENTITY,
/* Text in a code block (inside MD_BLOCK_CODE) or inlined code (`code`).
* If it is inside MD_BLOCK_CODE, it includes spaces for indentation and
* '\n' for new lines. MD_TEXT_BR and MD_TEXT_SOFTBR are not sent for this
* kind of text. */
MD_TEXT_CODE,
/* Text is a raw HTML. If it is contents of a raw HTML block (i.e. not
* an inline raw HTML), then MD_TEXT_BR and MD_TEXT_SOFTBR are not used.
* The text contains verbatim '\n' for the new lines. */
MD_TEXT_HTML,
/* Text is inside an equation. This is processed the same way as inlined code
* spans (`code`). */
MD_TEXT_LATEXMATH
} MD_TEXTTYPE;
/* Alignment enumeration. */
typedef enum MD_ALIGN {
MD_ALIGN_DEFAULT = 0, /* When unspecified. */
MD_ALIGN_LEFT,
MD_ALIGN_CENTER,
MD_ALIGN_RIGHT
} MD_ALIGN;
/* String attribute.
*
* This wraps strings which are outside of a normal text flow and which are
* propagated within various detailed structures, but which still may contain
* string portions of different types like e.g. entities.
*
* So, for example, lets consider this image:
*
* ![image alt text](http://example.org/image.png 'foo &quot; bar')
*
* The image alt text is propagated as a normal text via the MD_PARSER::text()
* callback. However, the image title ('foo &quot; bar') is propagated as
* MD_ATTRIBUTE in MD_SPAN_IMG_DETAIL::title.
*
* Then the attribute MD_SPAN_IMG_DETAIL::title shall provide the following:
* -- [0]: "foo " (substr_types[0] == MD_TEXT_NORMAL; substr_offsets[0] == 0)
* -- [1]: "&quot;" (substr_types[1] == MD_TEXT_ENTITY; substr_offsets[1] == 4)
* -- [2]: " bar" (substr_types[2] == MD_TEXT_NORMAL; substr_offsets[2] == 10)
* -- [3]: (n/a) (n/a ; substr_offsets[3] == 14)
*
* Note that these invariants are always guaranteed:
* -- substr_offsets[0] == 0
* -- substr_offsets[LAST+1] == size
* -- Currently, only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR
* substrings can appear. This could change only of the specification
* changes.
*/
typedef struct MD_ATTRIBUTE {
const MD_CHAR* text;
MD_SIZE size;
const MD_TEXTTYPE* substr_types;
const MD_OFFSET* substr_offsets;
} MD_ATTRIBUTE;
/* Detailed info for MD_BLOCK_UL. */
typedef struct MD_BLOCK_UL_DETAIL {
int is_tight; /* Non-zero if tight list, zero if loose. */
MD_CHAR mark; /* Item bullet character in MarkDown source of the list, e.g. '-', '+', '*'. */
} MD_BLOCK_UL_DETAIL;
/* Detailed info for MD_BLOCK_OL. */
typedef struct MD_BLOCK_OL_DETAIL {
unsigned start; /* Start index of the ordered list. */
int is_tight; /* Non-zero if tight list, zero if loose. */
MD_CHAR mark_delimiter; /* Character delimiting the item marks in MarkDown source, e.g. '.' or ')' */
} MD_BLOCK_OL_DETAIL;
/* Detailed info for MD_BLOCK_LI. */
typedef struct MD_BLOCK_LI_DETAIL {
int is_task; /* Can be non-zero only with MD_FLAG_TASKLISTS */
MD_CHAR task_mark; /* If is_task, then one of 'x', 'X' or ' '. Undefined otherwise. */
MD_OFFSET task_mark_offset; /* If is_task, then offset in the input of the char between '[' and ']'. */
} MD_BLOCK_LI_DETAIL;
/* Detailed info for MD_BLOCK_H. */
typedef struct MD_BLOCK_H_DETAIL {
unsigned level; /* Header level (1 - 6) */
} MD_BLOCK_H_DETAIL;
/* Detailed info for MD_BLOCK_CODE. */
typedef struct MD_BLOCK_CODE_DETAIL {
MD_ATTRIBUTE info;
MD_ATTRIBUTE lang;
MD_CHAR fence_char; /* The character used for fenced code block; or zero for indented code block. */
} MD_BLOCK_CODE_DETAIL;
/* Detailed info for MD_BLOCK_TABLE. */
typedef struct MD_BLOCK_TABLE_DETAIL {
unsigned col_count; /* Count of columns in the table. */
unsigned head_row_count; /* Count of rows in the table header (currently always 1) */
unsigned body_row_count; /* Count of rows in the table body */
} MD_BLOCK_TABLE_DETAIL;
/* Detailed info for MD_BLOCK_TH and MD_BLOCK_TD. */
typedef struct MD_BLOCK_TD_DETAIL {
MD_ALIGN align;
} MD_BLOCK_TD_DETAIL;
/* Detailed info for MD_SPAN_A. */
typedef struct MD_SPAN_A_DETAIL {
MD_ATTRIBUTE href;
MD_ATTRIBUTE title;
} MD_SPAN_A_DETAIL;
/* Detailed info for MD_SPAN_IMG. */
typedef struct MD_SPAN_IMG_DETAIL {
MD_ATTRIBUTE src;
MD_ATTRIBUTE title;
} MD_SPAN_IMG_DETAIL;
/* Detailed info for MD_SPAN_WIKILINK. */
typedef struct MD_SPAN_WIKILINK {
MD_ATTRIBUTE target;
} MD_SPAN_WIKILINK_DETAIL;
/* Flags specifying extensions/deviations from CommonMark specification.
*
* By default (when MD_PARSER::flags == 0), we follow CommonMark specification.
* The following flags may allow some extensions or deviations from it.
*/
#define MD_FLAG_COLLAPSEWHITESPACE 0x0001 /* In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' ' */
#define MD_FLAG_PERMISSIVEATXHEADERS 0x0002 /* Do not require space in ATX headers ( ###header ) */
#define MD_FLAG_PERMISSIVEURLAUTOLINKS 0x0004 /* Recognize URLs as autolinks even without '<', '>' */
#define MD_FLAG_PERMISSIVEEMAILAUTOLINKS 0x0008 /* Recognize e-mails as autolinks even without '<', '>' and 'mailto:' */
#define MD_FLAG_NOINDENTEDCODEBLOCKS 0x0010 /* Disable indented code blocks. (Only fenced code works.) */
#define MD_FLAG_NOHTMLBLOCKS 0x0020 /* Disable raw HTML blocks. */
#define MD_FLAG_NOHTMLSPANS 0x0040 /* Disable raw HTML (inline). */
#define MD_FLAG_TABLES 0x0100 /* Enable tables extension. */
#define MD_FLAG_STRIKETHROUGH 0x0200 /* Enable strikethrough extension. */
#define MD_FLAG_PERMISSIVEWWWAUTOLINKS 0x0400 /* Enable WWW autolinks (even without any scheme prefix, if they begin with 'www.') */
#define MD_FLAG_TASKLISTS 0x0800 /* Enable task list extension. */
#define MD_FLAG_LATEXMATHSPANS 0x1000 /* Enable $ and $$ containing LaTeX equations. */
#define MD_FLAG_WIKILINKS 0x2000 /* Enable wiki links extension. */
#define MD_FLAG_UNDERLINE 0x4000 /* Enable underline extension (and disables '_' for normal emphasis). */
#define MD_FLAG_PERMISSIVEAUTOLINKS (MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEURLAUTOLINKS | MD_FLAG_PERMISSIVEWWWAUTOLINKS)
#define MD_FLAG_NOHTML (MD_FLAG_NOHTMLBLOCKS | MD_FLAG_NOHTMLSPANS)
/* Convenient sets of flags corresponding to well-known Markdown dialects.
*
* Note we may only support subset of features of the referred dialect.
* The constant just enables those extensions which bring us as close as
* possible given what features we implement.
*
* ABI compatibility note: Meaning of these can change in time as new
* extensions, bringing the dialect closer to the original, are implemented.
*/
#define MD_DIALECT_COMMONMARK 0
#define MD_DIALECT_GITHUB (MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES | MD_FLAG_STRIKETHROUGH | MD_FLAG_TASKLISTS)
/* Parser structure.
*/
typedef struct MD_PARSER {
/* Reserved. Set to zero.
*/
unsigned abi_version;
/* Dialect options. Bitmask of MD_FLAG_xxxx values.
*/
unsigned flags;
/* Caller-provided rendering callbacks.
*
* For some block/span types, more detailed information is provided in a
* type-specific structure pointed by the argument 'detail'.
*
* The last argument of all callbacks, 'userdata', is just propagated from
* md_parse() and is available for any use by the application.
*
* Note any strings provided to the callbacks as their arguments or as
* members of any detail structure are generally not zero-terminated.
* Application has to take the respective size information into account.
*
* Any rendering callback may abort further parsing of the document by
* returning non-zero.
*/
int (*enter_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*leave_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*enter_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*leave_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*text)(MD_TEXTTYPE /*type*/, const MD_CHAR* /*text*/, MD_SIZE /*size*/, void* /*userdata*/);
/* Debug callback. Optional (may be NULL).
*
* If provided and something goes wrong, this function gets called.
* This is intended for debugging and problem diagnosis for developers;
* it is not intended to provide any errors suitable for displaying to an
* end user.
*/
void (*debug_log)(const char* /*msg*/, void* /*userdata*/);
/* Reserved. Set to NULL.
*/
void (*syntax)(void);
} MD_PARSER;
/* For backward compatibility. Do not use in new code.
*/
typedef MD_PARSER MD_RENDERER;
/* Parse the Markdown document stored in the string 'text' of size 'size'.
* The parser provides callbacks to be called during the parsing so the
* caller can render the document on the screen or convert the Markdown
* to another format.
*
* Zero is returned on success. If a runtime error occurs (e.g. a memory
* fails), -1 is returned. If the processing is aborted due any callback
* returning non-zero, the return value of the callback is returned.
*/
int md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata);
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* MD4C_H */

@ -193,7 +193,7 @@ time_extension_functions(struct FuncDef** basic_funcs,
"To group log messages into five minute buckets and count "
"them",
"SELECT timeslice(log_time_msecs, '5m') AS slice, "
"count(1) FROM lnav_example_log GROUP BY slice",
"count(1)\n FROM lnav_example_log GROUP BY slice",
})
.with_example({
"To group log messages by those before 4:30am and after",

@ -258,6 +258,8 @@ view_curses::mvwattrline(WINDOW* window,
short attr_fg = iter->sa_value.get<int64_t>();
if (attr_fg == view_colors::MATCH_COLOR_SEMANTIC) {
attr_fg = vc.color_for_ident(al.to_string_fragment(iter));
} else if (attr_fg < 8) {
attr_fg = vc.ansi_to_theme_color(attr_fg);
}
std::fill(&fg_color[attr_range.lr_start],
&fg_color[attr_range.lr_end],
@ -906,6 +908,15 @@ view_colors::init_roles(const lnav_theme& lt,
lt.lt_style_header[5],
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_HR)]
= this->to_attrs(
color_pair_base, lt, lt.lt_style_hr, lt.lt_style_text, reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_HYPERLINK)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_hyperlink,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_LIST_GLYPH)]
= this->to_attrs(color_pair_base,
lt,
@ -918,6 +929,43 @@ view_colors::init_roles(const lnav_theme& lt,
lt.lt_style_breadcrumb,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_TABLE_BORDER)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_table_border,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_TABLE_HEADER)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_table_header,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_QUOTE_BORDER)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_quote_border,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_QUOTED_TEXT)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_quoted_text,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(
role_t::VCR_FOOTNOTE_BORDER)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_footnote_border,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_FOOTNOTE_TEXT)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_footnote_text,
lt.lt_style_text,
reporter);
{
style_config stitch_sc;
@ -1052,6 +1100,18 @@ view_colors::init_roles(const lnav_theme& lt,
color_pair_base, lt, bar_sc, lt.lt_style_warn_status, reporter);
}
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_QUOTED_CODE)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_quoted_code,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_CODE_BORDER)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_code_border,
lt.lt_style_text,
reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_KEYWORD)]
= this->to_attrs(color_pair_base,
lt,

@ -32,11 +32,15 @@
#include "base/humanize.hh"
#include "base/itertools.hh"
#include "config.h"
#include "document.sections.hh"
#include "environ_vtab.hh"
#include "help-md.h"
#include "help-txt.h"
#include "intervaltree/IntervalTree.h"
#include "lnav.hh"
#include "lnav.indexing.hh"
#include "md2attr_line.hh"
#include "md4cpp.hh"
#include "pretty_printer.hh"
#include "shlex.hh"
#include "sql_help.hh"
@ -47,6 +51,7 @@
#include "vtab_module.hh"
using namespace std::chrono_literals;
using namespace lnav::roles::literals;
const char* lnav_view_strings[LNV__MAX + 1] = {
"log",
@ -130,7 +135,7 @@ public:
const auto& tl = this->tds_lines[line];
const auto initial_size = crumbs.size();
pretty_printer::hier_node* root_node;
lnav::document::hier_node* root_node{nullptr};
this->pss_hier_tree->template visit_overlapping(
tl.tl_offset,
@ -144,7 +149,7 @@ public:
| lnav::itertools::append(iv.value);
auto poss_provider = [root_node, path]() {
std::vector<breadcrumb::possibility> retval;
auto curr_node = pretty_printer::hier_node::lookup_path(
auto curr_node = lnav::document::hier_node::lookup_path(
root_node, path);
if (curr_node) {
auto* parent_node = curr_node.value()->hn_parent;
@ -161,7 +166,7 @@ public:
auto path_performer =
[this, root_node, path](
const breadcrumb::crumb::key_t& value) {
auto curr_node = pretty_printer::hier_node::lookup_path(
auto curr_node = lnav::document::hier_node::lookup_path(
root_node, path);
if (!curr_node) {
return;
@ -202,13 +207,13 @@ public:
std::move(poss_provider),
std::move(path_performer));
auto curr_node
= pretty_printer::hier_node::lookup_path(root_node, path);
= lnav::document::hier_node::lookup_path(root_node, path);
if (curr_node
&& curr_node.value()->hn_parent->hn_children.size()
!= curr_node.value()
->hn_parent->hn_named_children.size())
{
auto node = pretty_printer::hier_node::lookup_path(
auto node = lnav::document::hier_node::lookup_path(
root_node, path);
crumbs.back().c_expected_input
@ -226,7 +231,7 @@ public:
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key);
auto node = pretty_printer::hier_node::lookup_path(root_node, path);
auto node = lnav::document::hier_node::lookup_path(root_node, path);
if (node && !node.value()->hn_children.empty()) {
auto poss_provider = [curr_node = node.value()]() {
@ -267,12 +272,12 @@ public:
}
using hier_tree_t
= interval_tree::IntervalTree<file_off_t, pretty_printer::hier_node*>;
= interval_tree::IntervalTree<file_off_t, lnav::document::hier_node*>;
using hier_interval_t
= interval_tree::Interval<file_off_t, pretty_printer::hier_node*>;
= interval_tree::Interval<file_off_t, lnav::document::hier_node*>;
std::shared_ptr<pretty_printer::pretty_tree> pss_interval_tree;
std::vector<std::unique_ptr<pretty_printer::hier_node>> pss_hier_nods;
std::shared_ptr<lnav::document::sections_tree_t> pss_interval_tree;
std::vector<std::unique_ptr<lnav::document::hier_node>> pss_hier_nods;
std::shared_ptr<hier_tree_t> pss_hier_tree;
};
@ -294,8 +299,8 @@ open_pretty_view()
return;
}
std::vector<pretty_printer::pretty_interval> all_intervals;
std::vector<std::unique_ptr<pretty_printer::hier_node>> hier_nodes;
std::vector<lnav::document::section_interval_t> all_intervals;
std::vector<std::unique_ptr<lnav::document::hier_node>> hier_nodes;
std::vector<pretty_sub_source::hier_interval_t> hier_tree_vec;
if (top_tc == log_tc) {
logfile_sub_source& lss = lnav_data.ld_log_source;
@ -367,7 +372,7 @@ open_pretty_view()
interval.stop += prefix_al.length();
}
}
pretty_printer::hier_node::depth_first(
lnav::document::hier_node::depth_first(
line_hier_root.get(),
[line_off, prefix_len = prefix_al.length()](auto* hn) {
if (line_off <= hn->hn_start) {
@ -383,7 +388,7 @@ open_pretty_view()
interval.start += start_off;
interval.stop += start_off;
}
pretty_printer::hier_node::depth_first(
lnav::document::hier_node::depth_first(
line_hier_root.get(),
[start_off](auto* hn) { hn->hn_start += start_off; });
hier_nodes.emplace_back(std::move(line_hier_root));
@ -424,7 +429,7 @@ open_pretty_view()
}
}
auto* pts = new pretty_sub_source();
pts->pss_interval_tree = std::make_shared<pretty_printer::pretty_tree>(
pts->pss_interval_tree = std::make_shared<lnav::document::sections_tree_t>(
std::move(all_intervals));
pts->pss_hier_nods = std::move(hier_nodes);
pts->pss_hier_tree = std::make_shared<pretty_sub_source::hier_tree_t>(
@ -438,6 +443,12 @@ open_pretty_view()
pretty_tc->redo_search();
}
template<typename T>
static void
ignore_case(const T&)
{
}
static void
build_all_help_text()
{
@ -445,13 +456,15 @@ build_all_help_text()
return;
}
attr_line_t all_help_text;
shlex lexer(help_txt.to_string_fragment());
shlex lexer(help_md.to_string_fragment());
std::string sub_help_text;
lexer.with_ignore_quotes(true).eval(
sub_help_text, lnav_data.ld_exec_context.ec_global_vars);
all_help_text.with_ansi_string(sub_help_text);
md2attr_line mdal;
auto parse_res = md4cpp::parse(sub_help_text, mdal);
attr_line_t all_help_text = parse_res.unwrap();
std::map<std::string, help_text*> sql_funcs;
std::map<std::string, help_text*> sql_keywords;
@ -470,9 +483,26 @@ build_all_help_text()
}
}
all_help_text.append("\n").append("Command Reference"_h2);
for (const auto& cmd : lnav_commands) {
if (cmd.second->c_help.ht_summary == nullptr) {
continue;
}
all_help_text.append(2, '\n');
format_help_text_for_term(cmd.second->c_help, 70, all_help_text);
if (!cmd.second->c_help.ht_example.empty()) {
all_help_text.append("\n");
format_example_text_for_term(
cmd.second->c_help, eval_example, 90, all_help_text);
}
}
all_help_text.append("\n").append("SQL Reference"_h2);
for (const auto& iter : sql_funcs) {
all_help_text.append(2, '\n');
format_help_text_for_term(*iter.second, 79, all_help_text);
format_help_text_for_term(*iter.second, 70, all_help_text);
if (!iter.second->ht_example.empty()) {
all_help_text.append(1, '\n');
format_example_text_for_term(
@ -482,7 +512,7 @@ build_all_help_text()
for (const auto& iter : sql_keywords) {
all_help_text.append(2, '\n');
format_help_text_for_term(*iter.second, 79, all_help_text);
format_help_text_for_term(*iter.second, 70, all_help_text);
if (!iter.second->ht_example.empty()) {
all_help_text.append(1, '\n');
format_example_text_for_term(

@ -343,7 +343,7 @@ CREATE TABLE lnav_views (
tab->zErrMsg = sqlite3_mprintf(
"Rows cannot be inserted into the lnav_views table");
return SQLITE_ERROR;
};
}
int update_row(sqlite3_vtab* tab,
sqlite3_int64& index,
@ -433,7 +433,7 @@ CREATE TABLE lnav_view_stack (
}
return SQLITE_OK;
};
}
int delete_row(sqlite3_vtab* tab, sqlite3_int64 rowid)
{
@ -446,7 +446,7 @@ CREATE TABLE lnav_view_stack (
lnav_data.ld_last_view = *lnav_data.ld_view_stack.top();
lnav_data.ld_view_stack.pop_back();
return SQLITE_OK;
};
}
int insert_row(sqlite3_vtab* tab,
sqlite3_int64& rowid_out,
@ -458,14 +458,14 @@ CREATE TABLE lnav_view_stack (
rowid_out = lnav_data.ld_view_stack.size() - 1;
return SQLITE_OK;
};
}
int update_row(sqlite3_vtab* tab, sqlite3_int64& index)
{
tab->zErrMsg
= sqlite3_mprintf("The lnav_view_stack table cannot be updated");
return SQLITE_ERROR;
};
}
};
struct lnav_view_filter_base {
@ -803,7 +803,7 @@ CREATE TABLE lnav_view_filters (
tc.set_needs_update();
return SQLITE_OK;
};
}
};
struct lnav_view_filter_stats

File diff suppressed because it is too large Load Diff

@ -1139,6 +1139,7 @@ yajlpp_parse_context::get_snippet() const
}
}
content.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
return lnav::console::snippet::from(this->ypc_source, content)
.with_line(line_number);
}

@ -1260,6 +1260,13 @@ public:
});
}
yajlpp_parser& with_ignore_unused(bool value)
{
this->yp_parse_context.with_ignore_unused(value);
return *this;
}
Result<T, std::vector<lnav::console::user_message>> of(
const string_fragment& json)
{

@ -20,6 +20,11 @@ add_executable(test_auto_mem test_auto_mem.cc test_stubs.cc)
target_link_libraries(test_auto_mem diag)
add_test(NAME test_auto_mem COMMAND test_auto_mem)
add_executable(document.sections.tests document.sections.tests.cc test_stubs.cc)
target_include_directories(document.sections.tests PUBLIC ../src/third-party/doctest-root)
target_link_libraries(document.sections.tests diag)
add_test(NAME document.sections.tests COMMAND document.sections.tests)
add_executable(test_bookmarks test_bookmarks.cc test_stubs.cc)
target_link_libraries(test_bookmarks diag)
add_test(NAME test_bookmarks COMMAND test_bookmarks)

@ -101,6 +101,7 @@ LDADD = \
$(TEXT2C_OBJS) \
$(DUMMY_OBJS) \
$(top_builddir)/src/libdiag.a \
$(top_builddir)/src/libdatascanner.a \
$(top_builddir)/src/formats/logfmt/liblogfmt.a \
$(top_builddir)/src/fmtlib/libcppfmt.a \
$(top_builddir)/src/pcrepp/libpcrepp.a \

@ -0,0 +1,84 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"
#include "document.sections.hh"
TEST_CASE("lnav::document::sections::basics")
{
static const std::string INPUT = R"(
{
"msg": "Hello, World!",
"obj": {
"a": 1,
"b": "Two",
"c": 3.0
},
"arr": [1, 2, 3],
"arr2": [
456,
789,
{
"def": 123,
"ghi": null,
"jkl": "other"
},
{
"def": 456,
"ghi": null,
"jkl": "OTHER"
},
{
"def": 789,
"ghi": null,
"jkl": "OtHeR"
}
]
}
)";
auto meta = lnav::document::discover_structure(INPUT);
meta.m_sections_tree.visit_all([](const auto& intv) {
auto ser = intv.value.match(
[](const std::string& name) { return name; },
[](const size_t index) { return fmt::format("{}", index); });
printf("interval %d:%d %s\n", intv.start, intv.stop, ser.c_str());
});
lnav::document::hier_node::depth_first(
meta.m_sections_root.get(), [](const auto* node) {
printf("node %p %d\n", node, node->hn_start);
for (const auto& pair : node->hn_named_children) {
printf(" child: %p %s\n", pair.second, pair.first.c_str());
}
});
}

@ -6,6 +6,200 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_cli.sh_97e19b9ff3775d84074455a2e8993a0611b1c269.out \
$(srcdir)/%reldir%/test_cli.sh_a1a09f890f4604309d0a81bbbec8e50fb7d5e887.err \
$(srcdir)/%reldir%/test_cli.sh_a1a09f890f4604309d0a81bbbec8e50fb7d5e887.out \
$(srcdir)/%reldir%/test_cmds.sh_017b495b95218b7c083951e2dba331cfec6e90be.err \
$(srcdir)/%reldir%/test_cmds.sh_017b495b95218b7c083951e2dba331cfec6e90be.out \
$(srcdir)/%reldir%/test_cmds.sh_0b1e4b1523dfca71927b1fe721c74490c51361d1.err \
$(srcdir)/%reldir%/test_cmds.sh_0b1e4b1523dfca71927b1fe721c74490c51361d1.out \
$(srcdir)/%reldir%/test_cmds.sh_0b41fe57743ba0be088037d9ba29bc465e7c9bf9.err \
$(srcdir)/%reldir%/test_cmds.sh_0b41fe57743ba0be088037d9ba29bc465e7c9bf9.out \
$(srcdir)/%reldir%/test_cmds.sh_0f0ab532d8d845f8201af65bf5f6fc994e21a8aa.err \
$(srcdir)/%reldir%/test_cmds.sh_0f0ab532d8d845f8201af65bf5f6fc994e21a8aa.out \
$(srcdir)/%reldir%/test_cmds.sh_109a44ac6a8f1be2736c8e9c47aeed187e0581ee.err \
$(srcdir)/%reldir%/test_cmds.sh_109a44ac6a8f1be2736c8e9c47aeed187e0581ee.out \
$(srcdir)/%reldir%/test_cmds.sh_12856706bfb4a8e2686098dd2644a7989d370b02.err \
$(srcdir)/%reldir%/test_cmds.sh_12856706bfb4a8e2686098dd2644a7989d370b02.out \
$(srcdir)/%reldir%/test_cmds.sh_12b4cb9bd6586f9694100db76734b19a75158eab.err \
$(srcdir)/%reldir%/test_cmds.sh_12b4cb9bd6586f9694100db76734b19a75158eab.out \
$(srcdir)/%reldir%/test_cmds.sh_145126309709179759926289caf729703ef6e1c6.err \
$(srcdir)/%reldir%/test_cmds.sh_145126309709179759926289caf729703ef6e1c6.out \
$(srcdir)/%reldir%/test_cmds.sh_148007d2626b3c92d00ac31639b6918b1fc4aa60.err \
$(srcdir)/%reldir%/test_cmds.sh_148007d2626b3c92d00ac31639b6918b1fc4aa60.out \
$(srcdir)/%reldir%/test_cmds.sh_1cab7d240cf85ff2c3538f5a06af141b01bc83ad.err \
$(srcdir)/%reldir%/test_cmds.sh_1cab7d240cf85ff2c3538f5a06af141b01bc83ad.out \
$(srcdir)/%reldir%/test_cmds.sh_1d92c5bc12f5e7aaa6d84c5ed47f0b9f96e36c6a.err \
$(srcdir)/%reldir%/test_cmds.sh_1d92c5bc12f5e7aaa6d84c5ed47f0b9f96e36c6a.out \
$(srcdir)/%reldir%/test_cmds.sh_1e1c8492b295913ce5afcd104cde0ec4ca1dcdac.err \
$(srcdir)/%reldir%/test_cmds.sh_1e1c8492b295913ce5afcd104cde0ec4ca1dcdac.out \
$(srcdir)/%reldir%/test_cmds.sh_1f53f5b16c7c5aa695ed2e6427d822a1b940fcf4.err \
$(srcdir)/%reldir%/test_cmds.sh_1f53f5b16c7c5aa695ed2e6427d822a1b940fcf4.out \
$(srcdir)/%reldir%/test_cmds.sh_1ffb06d63469f0e6dae94af026f77d5b255386c2.err \
$(srcdir)/%reldir%/test_cmds.sh_1ffb06d63469f0e6dae94af026f77d5b255386c2.out \
$(srcdir)/%reldir%/test_cmds.sh_22577861cb0921a7e7f3d1af6485938f4930ba7b.err \
$(srcdir)/%reldir%/test_cmds.sh_22577861cb0921a7e7f3d1af6485938f4930ba7b.out \
$(srcdir)/%reldir%/test_cmds.sh_2339d09953b6937981d8a448000c3fdc2837f8c4.err \
$(srcdir)/%reldir%/test_cmds.sh_2339d09953b6937981d8a448000c3fdc2837f8c4.out \
$(srcdir)/%reldir%/test_cmds.sh_2539ff9c4dbed93df3f0408ccc5fd81df34d1193.err \
$(srcdir)/%reldir%/test_cmds.sh_2539ff9c4dbed93df3f0408ccc5fd81df34d1193.out \
$(srcdir)/%reldir%/test_cmds.sh_29f0c808f4e93c6ef3890e6b793bee274a5b36ca.err \
$(srcdir)/%reldir%/test_cmds.sh_29f0c808f4e93c6ef3890e6b793bee274a5b36ca.out \
$(srcdir)/%reldir%/test_cmds.sh_2a449c0a43e895e85c8b1c9547f32d7b5b4f84f6.err \
$(srcdir)/%reldir%/test_cmds.sh_2a449c0a43e895e85c8b1c9547f32d7b5b4f84f6.out \
$(srcdir)/%reldir%/test_cmds.sh_2a535de164de4c060d2bff34aa7cc75ac7cac2c2.err \
$(srcdir)/%reldir%/test_cmds.sh_2a535de164de4c060d2bff34aa7cc75ac7cac2c2.out \
$(srcdir)/%reldir%/test_cmds.sh_2cd167954a3be3e130e5f9601b72794a856cef92.err \
$(srcdir)/%reldir%/test_cmds.sh_2cd167954a3be3e130e5f9601b72794a856cef92.out \
$(srcdir)/%reldir%/test_cmds.sh_2de9ec294e2f533d13e04c70d9525f8b58d47bb2.err \
$(srcdir)/%reldir%/test_cmds.sh_2de9ec294e2f533d13e04c70d9525f8b58d47bb2.out \
$(srcdir)/%reldir%/test_cmds.sh_2e123104cdd2087ac40731a0aa533ba6a87ea744.err \
$(srcdir)/%reldir%/test_cmds.sh_2e123104cdd2087ac40731a0aa533ba6a87ea744.out \
$(srcdir)/%reldir%/test_cmds.sh_2ff0fe712c9b0012e42282c5f77b0b83cad37ddf.err \
$(srcdir)/%reldir%/test_cmds.sh_2ff0fe712c9b0012e42282c5f77b0b83cad37ddf.out \
$(srcdir)/%reldir%/test_cmds.sh_305b1dfdfe785b945df4220aad6671ae1d364f55.err \
$(srcdir)/%reldir%/test_cmds.sh_305b1dfdfe785b945df4220aad6671ae1d364f55.out \
$(srcdir)/%reldir%/test_cmds.sh_3429080ed14d01c6a887900186f37750df0d5ff0.err \
$(srcdir)/%reldir%/test_cmds.sh_3429080ed14d01c6a887900186f37750df0d5ff0.out \
$(srcdir)/%reldir%/test_cmds.sh_34a6bcaa2877471b8ea718374101fa9ce3b78235.err \
$(srcdir)/%reldir%/test_cmds.sh_34a6bcaa2877471b8ea718374101fa9ce3b78235.out \
$(srcdir)/%reldir%/test_cmds.sh_35b0dd8a030396742bc5acfde7715fb19f312f29.err \
$(srcdir)/%reldir%/test_cmds.sh_35b0dd8a030396742bc5acfde7715fb19f312f29.out \
$(srcdir)/%reldir%/test_cmds.sh_36800217930a6a30e68c4efb20f6959c4f71aeb0.err \
$(srcdir)/%reldir%/test_cmds.sh_36800217930a6a30e68c4efb20f6959c4f71aeb0.out \
$(srcdir)/%reldir%/test_cmds.sh_38fa2a95b703d4ce12e82882eca1938264822690.err \
$(srcdir)/%reldir%/test_cmds.sh_38fa2a95b703d4ce12e82882eca1938264822690.out \
$(srcdir)/%reldir%/test_cmds.sh_3b20a298e2c059d7f6045cbc0c07ca3db3917695.err \
$(srcdir)/%reldir%/test_cmds.sh_3b20a298e2c059d7f6045cbc0c07ca3db3917695.out \
$(srcdir)/%reldir%/test_cmds.sh_453054e29aaca4c2662c45c2a1f2f63f3510d8dd.err \
$(srcdir)/%reldir%/test_cmds.sh_453054e29aaca4c2662c45c2a1f2f63f3510d8dd.out \
$(srcdir)/%reldir%/test_cmds.sh_4b2d91b19008d5b775090e3ef87c111f9e603b15.err \
$(srcdir)/%reldir%/test_cmds.sh_4b2d91b19008d5b775090e3ef87c111f9e603b15.out \
$(srcdir)/%reldir%/test_cmds.sh_4f06183ed231669965965f5042fbbb507fa7deab.err \
$(srcdir)/%reldir%/test_cmds.sh_4f06183ed231669965965f5042fbbb507fa7deab.out \
$(srcdir)/%reldir%/test_cmds.sh_512872aebaae73ca4f33fa93acb2f4e3b018f8b4.err \
$(srcdir)/%reldir%/test_cmds.sh_512872aebaae73ca4f33fa93acb2f4e3b018f8b4.out \
$(srcdir)/%reldir%/test_cmds.sh_53a9686102f69b07b034df291f554a00b265ed20.err \
$(srcdir)/%reldir%/test_cmds.sh_53a9686102f69b07b034df291f554a00b265ed20.out \
$(srcdir)/%reldir%/test_cmds.sh_55c2fd15ec2c7d96dbef7b36a42a1b7b42f90dbc.err \
$(srcdir)/%reldir%/test_cmds.sh_55c2fd15ec2c7d96dbef7b36a42a1b7b42f90dbc.out \
$(srcdir)/%reldir%/test_cmds.sh_5bfd08c1639701476d7b9348c36afd46fdbe6f2a.err \
$(srcdir)/%reldir%/test_cmds.sh_5bfd08c1639701476d7b9348c36afd46fdbe6f2a.out \
$(srcdir)/%reldir%/test_cmds.sh_624a41e152675575f4b07c19b2cf0e3a028429a2.err \
$(srcdir)/%reldir%/test_cmds.sh_624a41e152675575f4b07c19b2cf0e3a028429a2.out \
$(srcdir)/%reldir%/test_cmds.sh_62d68c0a11757c996f24c8f003e6b4059c3e30b2.err \
$(srcdir)/%reldir%/test_cmds.sh_62d68c0a11757c996f24c8f003e6b4059c3e30b2.out \
$(srcdir)/%reldir%/test_cmds.sh_63e1925e9d7aadcdaa54a35a29711d7ecaad8d27.err \
$(srcdir)/%reldir%/test_cmds.sh_63e1925e9d7aadcdaa54a35a29711d7ecaad8d27.out \
$(srcdir)/%reldir%/test_cmds.sh_661ec61acdd8f6fa6ec1e3c2cf5f896eef431351.err \
$(srcdir)/%reldir%/test_cmds.sh_661ec61acdd8f6fa6ec1e3c2cf5f896eef431351.out \
$(srcdir)/%reldir%/test_cmds.sh_6a6031113aca32fabc5a3da64b7be46f5ce5a312.err \
$(srcdir)/%reldir%/test_cmds.sh_6a6031113aca32fabc5a3da64b7be46f5ce5a312.out \
$(srcdir)/%reldir%/test_cmds.sh_6e016c0ed61fc652be1a79b864875ffede64f281.err \
$(srcdir)/%reldir%/test_cmds.sh_6e016c0ed61fc652be1a79b864875ffede64f281.out \
$(srcdir)/%reldir%/test_cmds.sh_7270e37dab4549cfa7c5232451c031e1e04b4aef.err \
$(srcdir)/%reldir%/test_cmds.sh_7270e37dab4549cfa7c5232451c031e1e04b4aef.out \
$(srcdir)/%reldir%/test_cmds.sh_73ea99c84fb1d4570e8bcd45c423b4a28fe41e81.err \
$(srcdir)/%reldir%/test_cmds.sh_73ea99c84fb1d4570e8bcd45c423b4a28fe41e81.out \
$(srcdir)/%reldir%/test_cmds.sh_7cb644890c4b945ff3f1e15c86a58c85cb5425c0.err \
$(srcdir)/%reldir%/test_cmds.sh_7cb644890c4b945ff3f1e15c86a58c85cb5425c0.out \
$(srcdir)/%reldir%/test_cmds.sh_7e14e7f18219719453838835fa96c3451f78996d.err \
$(srcdir)/%reldir%/test_cmds.sh_7e14e7f18219719453838835fa96c3451f78996d.out \
$(srcdir)/%reldir%/test_cmds.sh_819b3dd21348f7242f3914ad0a8c5b1cdb3f91af.err \
$(srcdir)/%reldir%/test_cmds.sh_819b3dd21348f7242f3914ad0a8c5b1cdb3f91af.out \
$(srcdir)/%reldir%/test_cmds.sh_8298805f897346b4bb0f14e53c06b4fa28e309e3.err \
$(srcdir)/%reldir%/test_cmds.sh_8298805f897346b4bb0f14e53c06b4fa28e309e3.out \
$(srcdir)/%reldir%/test_cmds.sh_83654557317602d2e00adde1e5cba190d9db0dff.err \
$(srcdir)/%reldir%/test_cmds.sh_83654557317602d2e00adde1e5cba190d9db0dff.out \
$(srcdir)/%reldir%/test_cmds.sh_85ae6ac1eb9a8378f7a6c39659f52671218ce64b.err \
$(srcdir)/%reldir%/test_cmds.sh_85ae6ac1eb9a8378f7a6c39659f52671218ce64b.out \
$(srcdir)/%reldir%/test_cmds.sh_85ed177028f226e86b1d164eb1a4e18eaf036c9d.err \
$(srcdir)/%reldir%/test_cmds.sh_85ed177028f226e86b1d164eb1a4e18eaf036c9d.out \
$(srcdir)/%reldir%/test_cmds.sh_8758082427d6232a15053433942a4b5ad9f2e3ce.err \
$(srcdir)/%reldir%/test_cmds.sh_8758082427d6232a15053433942a4b5ad9f2e3ce.out \
$(srcdir)/%reldir%/test_cmds.sh_876116da8ab46c0c8a212ce230d1b8a13970f78f.err \
$(srcdir)/%reldir%/test_cmds.sh_876116da8ab46c0c8a212ce230d1b8a13970f78f.out \
$(srcdir)/%reldir%/test_cmds.sh_8765cbf326648e9014f8cf5f761895010fff443a.err \
$(srcdir)/%reldir%/test_cmds.sh_8765cbf326648e9014f8cf5f761895010fff443a.out \
$(srcdir)/%reldir%/test_cmds.sh_8d5b43c693e78804a8fb06989392fa8cccb46b7b.err \
$(srcdir)/%reldir%/test_cmds.sh_8d5b43c693e78804a8fb06989392fa8cccb46b7b.out \
$(srcdir)/%reldir%/test_cmds.sh_9445861db011dfa2d21a44788047de345ee291e8.err \
$(srcdir)/%reldir%/test_cmds.sh_9445861db011dfa2d21a44788047de345ee291e8.out \
$(srcdir)/%reldir%/test_cmds.sh_95beaabe41d72cf4c6810e79c623da759ac1c71b.err \
$(srcdir)/%reldir%/test_cmds.sh_95beaabe41d72cf4c6810e79c623da759ac1c71b.out \
$(srcdir)/%reldir%/test_cmds.sh_968dac54dc80d91a5da2322890c6c26dfa0d8462.err \
$(srcdir)/%reldir%/test_cmds.sh_968dac54dc80d91a5da2322890c6c26dfa0d8462.out \
$(srcdir)/%reldir%/test_cmds.sh_a00943ef715598c7554b85de8502454e41bb9e28.err \
$(srcdir)/%reldir%/test_cmds.sh_a00943ef715598c7554b85de8502454e41bb9e28.out \
$(srcdir)/%reldir%/test_cmds.sh_a1123427c31c022433d66d05ee5d5e1c8ab415e4.err \
$(srcdir)/%reldir%/test_cmds.sh_a1123427c31c022433d66d05ee5d5e1c8ab415e4.out \
$(srcdir)/%reldir%/test_cmds.sh_a190bfc279fa046a823864f1484f899d27d22953.err \
$(srcdir)/%reldir%/test_cmds.sh_a190bfc279fa046a823864f1484f899d27d22953.out \
$(srcdir)/%reldir%/test_cmds.sh_a2690827f648d15ee64d1bdc8638f0277f175b95.err \
$(srcdir)/%reldir%/test_cmds.sh_a2690827f648d15ee64d1bdc8638f0277f175b95.out \
$(srcdir)/%reldir%/test_cmds.sh_a5742238bad948b1372d32f7a491f03fa4e8b711.err \
$(srcdir)/%reldir%/test_cmds.sh_a5742238bad948b1372d32f7a491f03fa4e8b711.out \
$(srcdir)/%reldir%/test_cmds.sh_a6c431f2871ea96cfdf4e11465b3bca543c7b678.err \
$(srcdir)/%reldir%/test_cmds.sh_a6c431f2871ea96cfdf4e11465b3bca543c7b678.out \
$(srcdir)/%reldir%/test_cmds.sh_a8006c4169d76baecd99a0699c2fc66a583ad676.err \
$(srcdir)/%reldir%/test_cmds.sh_a8006c4169d76baecd99a0699c2fc66a583ad676.out \
$(srcdir)/%reldir%/test_cmds.sh_ac45fb0f8f9578c3ded0855f694698ec38ce31ad.err \
$(srcdir)/%reldir%/test_cmds.sh_ac45fb0f8f9578c3ded0855f694698ec38ce31ad.out \
$(srcdir)/%reldir%/test_cmds.sh_af0fcbd30b3fd0d13477aa3325ef0302052a4d9f.err \
$(srcdir)/%reldir%/test_cmds.sh_af0fcbd30b3fd0d13477aa3325ef0302052a4d9f.out \
$(srcdir)/%reldir%/test_cmds.sh_b5a530d16c982cf769151291f0bfd612ea71183f.err \
$(srcdir)/%reldir%/test_cmds.sh_b5a530d16c982cf769151291f0bfd612ea71183f.out \
$(srcdir)/%reldir%/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.err \
$(srcdir)/%reldir%/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out \
$(srcdir)/%reldir%/test_cmds.sh_b755a8b48c0f602f0270500b0117b76e11db546e.err \
$(srcdir)/%reldir%/test_cmds.sh_b755a8b48c0f602f0270500b0117b76e11db546e.out \
$(srcdir)/%reldir%/test_cmds.sh_b7fcd26c45c850c3d43ce25b1f610a311eb898c5.err \
$(srcdir)/%reldir%/test_cmds.sh_b7fcd26c45c850c3d43ce25b1f610a311eb898c5.out \
$(srcdir)/%reldir%/test_cmds.sh_b9f8bf53ec2736432eb048d94a391175eb4dc5bf.err \
$(srcdir)/%reldir%/test_cmds.sh_b9f8bf53ec2736432eb048d94a391175eb4dc5bf.out \
$(srcdir)/%reldir%/test_cmds.sh_bc60341827636715c14c562863da9733cbde7e68.err \
$(srcdir)/%reldir%/test_cmds.sh_bc60341827636715c14c562863da9733cbde7e68.out \
$(srcdir)/%reldir%/test_cmds.sh_be1d9628fc447b6f17121d9457ea1602afe8f3f3.err \
$(srcdir)/%reldir%/test_cmds.sh_be1d9628fc447b6f17121d9457ea1602afe8f3f3.out \
$(srcdir)/%reldir%/test_cmds.sh_be3b7c5874b5f4d86cc230bd2f9802c98909e148.err \
$(srcdir)/%reldir%/test_cmds.sh_be3b7c5874b5f4d86cc230bd2f9802c98909e148.out \
$(srcdir)/%reldir%/test_cmds.sh_c01e10f7cae8d36fa79ae03be887cb5477025f6d.err \
$(srcdir)/%reldir%/test_cmds.sh_c01e10f7cae8d36fa79ae03be887cb5477025f6d.out \
$(srcdir)/%reldir%/test_cmds.sh_c2b4431dd0cc36c6201d263b727b3305e8cda6b1.err \
$(srcdir)/%reldir%/test_cmds.sh_c2b4431dd0cc36c6201d263b727b3305e8cda6b1.out \
$(srcdir)/%reldir%/test_cmds.sh_c4777849c39a6c34dea5b0279cd7400692f1ab5f.err \
$(srcdir)/%reldir%/test_cmds.sh_c4777849c39a6c34dea5b0279cd7400692f1ab5f.out \
$(srcdir)/%reldir%/test_cmds.sh_c4a15771f7e1487bf73b2e9d1564ad8ecfd76c7e.err \
$(srcdir)/%reldir%/test_cmds.sh_c4a15771f7e1487bf73b2e9d1564ad8ecfd76c7e.out \
$(srcdir)/%reldir%/test_cmds.sh_c72aed622c19d493968e33f20d5dde3838a4258f.err \
$(srcdir)/%reldir%/test_cmds.sh_c72aed622c19d493968e33f20d5dde3838a4258f.out \
$(srcdir)/%reldir%/test_cmds.sh_c7fabc25374ff47c47931f63b1d697061b816a28.err \
$(srcdir)/%reldir%/test_cmds.sh_c7fabc25374ff47c47931f63b1d697061b816a28.out \
$(srcdir)/%reldir%/test_cmds.sh_ca66660c973f76a3c2a147c7f5035bcb4e8a8bbc.err \
$(srcdir)/%reldir%/test_cmds.sh_ca66660c973f76a3c2a147c7f5035bcb4e8a8bbc.out \
$(srcdir)/%reldir%/test_cmds.sh_ccd326da92d1cacda63501cd1a3077381a18e8f2.err \
$(srcdir)/%reldir%/test_cmds.sh_ccd326da92d1cacda63501cd1a3077381a18e8f2.out \
$(srcdir)/%reldir%/test_cmds.sh_d76d77ad95b9f120825417a6a8220c13df9541fc.err \
$(srcdir)/%reldir%/test_cmds.sh_d76d77ad95b9f120825417a6a8220c13df9541fc.out \
$(srcdir)/%reldir%/test_cmds.sh_d8eeef53a58bdeddbc1028d7c525413e3ca1c8df.err \
$(srcdir)/%reldir%/test_cmds.sh_d8eeef53a58bdeddbc1028d7c525413e3ca1c8df.out \
$(srcdir)/%reldir%/test_cmds.sh_dbdd62995fdefc8318053af05a32416eccfa79fc.err \
$(srcdir)/%reldir%/test_cmds.sh_dbdd62995fdefc8318053af05a32416eccfa79fc.out \
$(srcdir)/%reldir%/test_cmds.sh_dd41fbbcd71699314af232156d4155fbdf849131.err \
$(srcdir)/%reldir%/test_cmds.sh_dd41fbbcd71699314af232156d4155fbdf849131.out \
$(srcdir)/%reldir%/test_cmds.sh_e495cf059477e3f80c3241c6f8d5808b6f1d19c7.err \
$(srcdir)/%reldir%/test_cmds.sh_e495cf059477e3f80c3241c6f8d5808b6f1d19c7.out \
$(srcdir)/%reldir%/test_cmds.sh_e7e8244fac65bc51dbd5af31be476fe3b8776bfc.err \
$(srcdir)/%reldir%/test_cmds.sh_e7e8244fac65bc51dbd5af31be476fe3b8776bfc.out \
$(srcdir)/%reldir%/test_cmds.sh_e911aebcb2defb7471aa620c45a86cad449ad505.err \
$(srcdir)/%reldir%/test_cmds.sh_e911aebcb2defb7471aa620c45a86cad449ad505.out \
$(srcdir)/%reldir%/test_cmds.sh_eb22c3e94c536a1bfaeae0c40d271b5b4b08f4fc.err \
$(srcdir)/%reldir%/test_cmds.sh_eb22c3e94c536a1bfaeae0c40d271b5b4b08f4fc.out \
$(srcdir)/%reldir%/test_cmds.sh_ec2b28c6ea328e3ea56b13ab8ca3d9ee856a9dda.err \
$(srcdir)/%reldir%/test_cmds.sh_ec2b28c6ea328e3ea56b13ab8ca3d9ee856a9dda.out \
$(srcdir)/%reldir%/test_cmds.sh_ed5b73be0b991e0e8d6735e31df5b37c4286321b.err \
$(srcdir)/%reldir%/test_cmds.sh_ed5b73be0b991e0e8d6735e31df5b37c4286321b.out \
$(srcdir)/%reldir%/test_cmds.sh_f788d5f5932905d09ecbd581040ec5ce76459da5.err \
$(srcdir)/%reldir%/test_cmds.sh_f788d5f5932905d09ecbd581040ec5ce76459da5.out \
$(srcdir)/%reldir%/test_cmds.sh_ff6faebbde8586e04bfadba14a3d2bb4451784ad.err \
$(srcdir)/%reldir%/test_cmds.sh_ff6faebbde8586e04bfadba14a3d2bb4451784ad.out \
$(srcdir)/%reldir%/test_config.sh_2765ea0d4c037b8c935840604edb0ae796c97a04.err \
$(srcdir)/%reldir%/test_config.sh_2765ea0d4c037b8c935840604edb0ae796c97a04.out \
$(srcdir)/%reldir%/test_config.sh_5105c29004e297521310ca0bd0fd560b01c2c549.err \
@ -100,58 +294,6 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_pretty_print.sh_a6d9042e5e95f2a49194bd80c1eed154813ddf41.out \
$(srcdir)/%reldir%/test_pretty_print.sh_cd361eeca7e91bfab942b75d6c3422c7a456a111.err \
$(srcdir)/%reldir%/test_pretty_print.sh_cd361eeca7e91bfab942b75d6c3422c7a456a111.out \
$(srcdir)/%reldir%/test_regex101.sh_0fa3663a45aca6a328cb728872af7ed7ee896f1c.err \
$(srcdir)/%reldir%/test_regex101.sh_0fa3663a45aca6a328cb728872af7ed7ee896f1c.out \
$(srcdir)/%reldir%/test_regex101.sh_182ae9244db314a953af2bee969726e381bc5a32.err \
$(srcdir)/%reldir%/test_regex101.sh_182ae9244db314a953af2bee969726e381bc5a32.out \
$(srcdir)/%reldir%/test_regex101.sh_2158f1f011ba8e1b152396c072790c076fdb8ce8.err \
$(srcdir)/%reldir%/test_regex101.sh_2158f1f011ba8e1b152396c072790c076fdb8ce8.out \
$(srcdir)/%reldir%/test_regex101.sh_281af24141680330791db7f7c5fa70833ce08a6b.err \
$(srcdir)/%reldir%/test_regex101.sh_281af24141680330791db7f7c5fa70833ce08a6b.out \
$(srcdir)/%reldir%/test_regex101.sh_35703b13990785632cca82123fb3883797959c0b.err \
$(srcdir)/%reldir%/test_regex101.sh_35703b13990785632cca82123fb3883797959c0b.out \
$(srcdir)/%reldir%/test_regex101.sh_366730cac50b4a09b7de4b84641791470b1cb9a3.err \
$(srcdir)/%reldir%/test_regex101.sh_366730cac50b4a09b7de4b84641791470b1cb9a3.out \
$(srcdir)/%reldir%/test_regex101.sh_3d18474a3e472fff6e23e0c41337ec9188fee591.err \
$(srcdir)/%reldir%/test_regex101.sh_3d18474a3e472fff6e23e0c41337ec9188fee591.out \
$(srcdir)/%reldir%/test_regex101.sh_442cc58676590a3604d5c2183f5fe0a75c98351a.err \
$(srcdir)/%reldir%/test_regex101.sh_442cc58676590a3604d5c2183f5fe0a75c98351a.out \
$(srcdir)/%reldir%/test_regex101.sh_566fd88d216a44bc1c6e23f2d6f2d0caf99d42f9.err \
$(srcdir)/%reldir%/test_regex101.sh_566fd88d216a44bc1c6e23f2d6f2d0caf99d42f9.out \
$(srcdir)/%reldir%/test_regex101.sh_5f2f7ecb6ab9cbec4b41385b91bd038906b8a7b2.err \
$(srcdir)/%reldir%/test_regex101.sh_5f2f7ecb6ab9cbec4b41385b91bd038906b8a7b2.out \
$(srcdir)/%reldir%/test_regex101.sh_629bde30483e0a6461076e9058f3a5eb81ae0425.err \
$(srcdir)/%reldir%/test_regex101.sh_629bde30483e0a6461076e9058f3a5eb81ae0425.out \
$(srcdir)/%reldir%/test_regex101.sh_630db454054cf92ec9bd0f4e3e83300047f583ff.err \
$(srcdir)/%reldir%/test_regex101.sh_630db454054cf92ec9bd0f4e3e83300047f583ff.out \
$(srcdir)/%reldir%/test_regex101.sh_771af6f3d29b8350542d5c6e98bdbf4c223cd531.err \
$(srcdir)/%reldir%/test_regex101.sh_771af6f3d29b8350542d5c6e98bdbf4c223cd531.out \
$(srcdir)/%reldir%/test_regex101.sh_7991a5b617867cf37c9f7baa85ffa425f7d455a2.err \
$(srcdir)/%reldir%/test_regex101.sh_7991a5b617867cf37c9f7baa85ffa425f7d455a2.out \
$(srcdir)/%reldir%/test_regex101.sh_79ee3f5fe71ccec97b2619d8c1f74ca97ffd2243.err \
$(srcdir)/%reldir%/test_regex101.sh_79ee3f5fe71ccec97b2619d8c1f74ca97ffd2243.out \
$(srcdir)/%reldir%/test_regex101.sh_7de76c174c58d67bf93e8f01d6d55ebb6a023f10.err \
$(srcdir)/%reldir%/test_regex101.sh_7de76c174c58d67bf93e8f01d6d55ebb6a023f10.out \
$(srcdir)/%reldir%/test_regex101.sh_8a43e6657d4f60e68d31eb8302542ca28e80d077.err \
$(srcdir)/%reldir%/test_regex101.sh_8a43e6657d4f60e68d31eb8302542ca28e80d077.out \
$(srcdir)/%reldir%/test_regex101.sh_8e93a3b6b941847c71409a297779fbb0a6666a51.err \
$(srcdir)/%reldir%/test_regex101.sh_8e93a3b6b941847c71409a297779fbb0a6666a51.out \
$(srcdir)/%reldir%/test_regex101.sh_95c56a9d146ec9a7c2196559d316f928b2ae6ae9.err \
$(srcdir)/%reldir%/test_regex101.sh_95c56a9d146ec9a7c2196559d316f928b2ae6ae9.out \
$(srcdir)/%reldir%/test_regex101.sh_9d101ee29c45cdb8c0f117ad736c9a5dd5da5839.err \
$(srcdir)/%reldir%/test_regex101.sh_9d101ee29c45cdb8c0f117ad736c9a5dd5da5839.out \
$(srcdir)/%reldir%/test_regex101.sh_c43e07df9b3068696fdc8759c7561135db981b38.err \
$(srcdir)/%reldir%/test_regex101.sh_c43e07df9b3068696fdc8759c7561135db981b38.out \
$(srcdir)/%reldir%/test_regex101.sh_cbd859487e4ea011cd6e0f0f114d70158bfd8b43.err \
$(srcdir)/%reldir%/test_regex101.sh_cbd859487e4ea011cd6e0f0f114d70158bfd8b43.out \
$(srcdir)/%reldir%/test_regex101.sh_cf6c0a9f0f04e24ce1fae7a0a434830b14447f83.err \
$(srcdir)/%reldir%/test_regex101.sh_cf6c0a9f0f04e24ce1fae7a0a434830b14447f83.out \
$(srcdir)/%reldir%/test_regex101.sh_d84597760285c3964b258726341e018f6cd49954.err \
$(srcdir)/%reldir%/test_regex101.sh_d84597760285c3964b258726341e018f6cd49954.out \
$(srcdir)/%reldir%/test_regex101.sh_f23e393dbf23d0d8e276e9b7610c7b74d79980f8.err \
$(srcdir)/%reldir%/test_regex101.sh_f23e393dbf23d0d8e276e9b7610c7b74d79980f8.out \
$(srcdir)/%reldir%/test_regex101.sh_fc41b6ee90cbf038620151f16d164b361acf82dd.err \
$(srcdir)/%reldir%/test_regex101.sh_fc41b6ee90cbf038620151f16d164b361acf82dd.out \
$(srcdir)/%reldir%/test_sessions.sh_0300a1391c33b1c45ddfa90198a6bd0a5404a77f.err \
$(srcdir)/%reldir%/test_sessions.sh_0300a1391c33b1c45ddfa90198a6bd0a5404a77f.out \
$(srcdir)/%reldir%/test_sessions.sh_17b85654b929b2a8fc1705a170ced544783292fa.err \
@ -194,8 +336,6 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_shlexer.sh_90961e6728e96d0a44535a6c9907cc990c10316c.out \
$(srcdir)/%reldir%/test_shlexer.sh_95c4e861804a5434900fdb4d67b149d1baa2edf4.err \
$(srcdir)/%reldir%/test_shlexer.sh_95c4e861804a5434900fdb4d67b149d1baa2edf4.out \
$(srcdir)/%reldir%/test_shlexer.sh_d5abaa610808635cebb8031a686274d44fcae1b3.err \
$(srcdir)/%reldir%/test_shlexer.sh_d5abaa610808635cebb8031a686274d44fcae1b3.out \
$(srcdir)/%reldir%/test_shlexer.sh_d7fe5f6b8fc9ba00539fad0fa0bfb08319d8b04b.err \
$(srcdir)/%reldir%/test_shlexer.sh_d7fe5f6b8fc9ba00539fad0fa0bfb08319d8b04b.out \
$(srcdir)/%reldir%/test_shlexer.sh_d9d46422a913e3a06ddbd262933ef5352c30e68f.err \
@ -274,8 +414,6 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_sql_fs_func.sh_3ed11101a413e47c3dfe219557b7a6df04a64253.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_469380561dccd79c7249562067107c330838eaad.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_469380561dccd79c7249562067107c330838eaad.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_549647b59b4fec38734fa58842c0e720c21af120.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_549647b59b4fec38734fa58842c0e720c21af120.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_54b004f301907860d360434b37fd6c81fcc12f99.err \
$(srcdir)/%reldir%/test_sql_fs_func.sh_54b004f301907860d360434b37fd6c81fcc12f99.out \
$(srcdir)/%reldir%/test_sql_fs_func.sh_73df81c6889d1f06fb3f3b6bf30c6046b3f52c8b.err \

@ -1,7 +1,7 @@
✘ error: invalid value for “-c” option
 --> arg
 |  -c foo
 |  ^ command type prefix is missing
 --> command-line argument
 |  -c foo 
 |  ^ command type prefix is missing
 = help: command arguments must start with one of the following symbols to denote the type of command:
: - an lnav command (e.g. :goto 42)
; - an SQL statement (e.g. ;SELECT * FROM syslog_log)

@ -0,0 +1,6 @@
✘ error: no log files loaded
 --> command-option:2
 | :close 
 = help: :close
══════════════════════════════════════════════════════════════════════
Close the top file in the view

@ -0,0 +1,3 @@
192.168.202.254 - - [20/Jul/2009:21:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:21:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:21:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"

@ -0,0 +1,3 @@
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"

@ -0,0 +1,2 @@
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"

@ -0,0 +1,6 @@
✘ error: filter expression failed with: unable to parse time slice value: bad -- Unrecognized input
 --> command-option:1
 | :filter-expr timeslice(:log_time_msecs, 'bad') is not null
 = help: :filter-expr expr
══════════════════════════════════════════════════════════════════════
Set the filter expression

@ -0,0 +1,2 @@
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save