[views] add a breadcrumb view

pull/1006/head
Timothy Stack 2 years ago
parent 2c4b1d3886
commit f03f9e704f

15
NEWS

@ -1,11 +1,26 @@
lnav v0.10.2: lnav v0.10.2:
Features: Features:
* Redesigned the top status bar to make it display breadcrumbs that
are interactive. Pressing ENTER will activate the breadcrumb bar
and the left/right cursor keys can be used to select a particular
crumb while the up/down keys can select a value to switch to.
While a crumb is selected, you can also type in some text to do
a fuzzy search on the possibilities or, if the crumb represents
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.
* Added an integration with regex101.com to make it easier to edit * Added an integration with regex101.com to make it easier to edit
log message regular expressions. Using the new "management CLI" log message regular expressions. Using the new "management CLI"
(activated by the -m option), a log format can be created from (activated by the -m option), a log format can be created from
a regular expression entry on regex101.com and existing patterns a regular expression entry on regex101.com and existing patterns
can be edited. can be edited.
* Add initial support for pcap(3) files using tshark(1). * Add initial support for pcap(3) files using tshark(1).
* Added a "top_meta" column to the lnav_views table that contains
metadata related to the top line in the view.
* Added a "log_opid" hidden column to all log tables that contains
the "operation ID" as specified in the log format.
* Moved the "log_format" column from the all_logs table to a hidden
column on all tables.
* Add format for UniFi gateway. * Add format for UniFi gateway.
Breaking Changes: Breaking Changes:

@ -352,6 +352,11 @@
"description": "Styling for glyphs that prefix a list item", "description": "Styling for glyphs that prefix a list item",
"title": "/ui/theme-defs/<theme_name>/styles/list-glyph", "title": "/ui/theme-defs/<theme_name>/styles/list-glyph",
"$ref": "#/definitions/style" "$ref": "#/definitions/style"
},
"breadcrumb": {
"description": "Styling for the separator between breadcrumbs",
"title": "/ui/theme-defs/<theme_name>/styles/breadcrumb",
"$ref": "#/definitions/style"
} }
}, },
"additionalProperties": false "additionalProperties": false
@ -509,7 +514,7 @@
"title": "/ui/theme-defs/<theme_name>/highlights", "title": "/ui/theme-defs/<theme_name>/highlights",
"type": "object", "type": "object",
"patternProperties": { "patternProperties": {
"(\\w+)": { "([\\w\\-]+)": {
"title": "/ui/theme-defs/<theme_name>/highlights/<highlight_name>", "title": "/ui/theme-defs/<theme_name>/highlights/<highlight_name>",
"type": "object", "type": "object",
"properties": { "properties": {

@ -1,4 +1,4 @@
#! /usr/bin/env python #! /usr/bin/env python3
import os import os
import sys import sys
@ -156,7 +156,7 @@ while True:
for fname, gen in FILES: for fname, gen in FILES:
for i in range(random.randrange(0, 4)): for i in range(random.randrange(0, 4)):
with open(fname, "a+") as fp: with open(fname, "a+") as fp:
fp.write(gen.next()) fp.write(next(gen))
#if random.uniform(0.0, 1.0) < 0.010: #if random.uniform(0.0, 1.0) < 0.010:
# fp.truncate(0) # fp.truncate(0)
time.sleep(random.uniform(0.05, 0.10)) time.sleep(random.uniform(0.05, 0.10))

@ -250,6 +250,7 @@ add_library(
bin2c.hh bin2c.hh
bookmarks.cc bookmarks.cc
bottom_status_source.cc bottom_status_source.cc
breadcrumb_curses.cc
collation-functions.cc collation-functions.cc
column_namer.cc column_namer.cc
command_executor.cc command_executor.cc
@ -299,6 +300,7 @@ add_library(
data_parser.cc data_parser.cc
papertrail_proc.cc papertrail_proc.cc
pcap_manager.cc pcap_manager.cc
plain_text_source.cc
pretty_printer.cc pretty_printer.cc
pugixml/pugixml.cpp pugixml/pugixml.cpp
readline_callbacks.cc readline_callbacks.cc
@ -349,6 +351,8 @@ add_library(
big_array.hh big_array.hh
bottom_status_source.hh bottom_status_source.hh
bound_tags.hh bound_tags.hh
breadcrumb.hh
breadcrumb_curses.hh
byte_array.hh byte_array.hh
command_executor.hh command_executor.hh
column_namer.hh column_namer.hh
@ -371,6 +375,7 @@ add_library(
highlighter.hh highlighter.hh
hotkeys.hh hotkeys.hh
input_dispatcher.hh input_dispatcher.hh
itertools.similar.hh
k_merge_tree.h k_merge_tree.h
lnav.indexing.hh lnav.indexing.hh
lnav.management_cli.hh lnav.management_cli.hh
@ -433,6 +438,7 @@ add_library(
top_status_source.hh top_status_source.hh
url_loader.hh url_loader.hh
view_helpers.hh view_helpers.hh
view_helpers.crumbs.hh
view_helpers.examples.hh view_helpers.examples.hh
view_helpers.hist.hh view_helpers.hist.hh
views_vtab.hh views_vtab.hh
@ -469,6 +475,8 @@ add_library(
third-party/CLI/Split.hpp third-party/CLI/Split.hpp
third-party/CLI/TypeTools.hpp third-party/CLI/TypeTools.hpp
third-party/CLI/ConfigFwd.hpp third-party/CLI/ConfigFwd.hpp
third-party/intervaltree/IntervalTree.h
) )
set(lnav_SRCS lnav.cc) set(lnav_SRCS lnav.cc)

@ -161,6 +161,8 @@ noinst_HEADERS = \
bookmarks.hh \ bookmarks.hh \
bottom_status_source.hh \ bottom_status_source.hh \
bound_tags.hh \ bound_tags.hh \
breadcrumb.hh \
breadcrumb_curses.hh \
byte_array.hh \ byte_array.hh \
column_namer.hh \ column_namer.hh \
command_executor.hh \ command_executor.hh \
@ -193,6 +195,7 @@ noinst_HEADERS = \
hotkeys.hh \ hotkeys.hh \
init.sql \ init.sql \
input_dispatcher.hh \ input_dispatcher.hh \
itertools.similar.hh \
k_merge_tree.h \ k_merge_tree.h \
line_buffer.hh \ line_buffer.hh \
listview_curses.hh \ listview_curses.hh \
@ -275,6 +278,7 @@ noinst_HEADERS = \
url_loader.hh \ url_loader.hh \
view_curses.hh \ view_curses.hh \
view_helpers.hh \ view_helpers.hh \
view_helpers.crumbs.hh \
view_helpers.examples.hh \ view_helpers.examples.hh \
view_helpers.hist.hh \ view_helpers.hist.hh \
views_vtab.hh \ views_vtab.hh \
@ -317,6 +321,7 @@ THIRD_PARTY_SRCS = \
third-party/CLI/TypeTools.hpp \ third-party/CLI/TypeTools.hpp \
third-party/CLI/ConfigFwd.hpp \ third-party/CLI/ConfigFwd.hpp \
third-party/doctest-root/doctest/doctest.h \ third-party/doctest-root/doctest/doctest.h \
third-party/intervaltree/IntervalTree.h \
third-party/sqlite/ext/dbdump.c \ third-party/sqlite/ext/dbdump.c \
third-party/sqlite/ext/series.c third-party/sqlite/ext/series.c
@ -326,6 +331,7 @@ libdiag_a_SOURCES = \
archive_manager.cc \ archive_manager.cc \
bookmarks.cc \ bookmarks.cc \
bottom_status_source.cc \ bottom_status_source.cc \
breadcrumb_curses.cc \
collation-functions.cc \ collation-functions.cc \
column_namer.cc \ column_namer.cc \
command_executor.cc \ command_executor.cc \
@ -375,6 +381,7 @@ libdiag_a_SOURCES = \
data_parser.cc \ data_parser.cc \
papertrail_proc.cc \ papertrail_proc.cc \
pcap_manager.cc \ pcap_manager.cc \
plain_text_source.cc \
pretty_printer.cc \ pretty_printer.cc \
ptimec_rt.cc \ ptimec_rt.cc \
readline_callbacks.cc \ readline_callbacks.cc \

@ -36,14 +36,11 @@ static auto intern_lifetime = intern_string::get_table_lifetime();
all_logs_vtab::all_logs_vtab() all_logs_vtab::all_logs_vtab()
: log_vtab_impl(intern_string::lookup("all_logs")), : log_vtab_impl(intern_string::lookup("all_logs")),
alv_value_meta(
intern_string::lookup("log_format"), value_kind_t::VALUE_TEXT, 0),
alv_msg_meta( alv_msg_meta(
intern_string::lookup("log_msg_format"), value_kind_t::VALUE_TEXT, 1), intern_string::lookup("log_msg_format"), value_kind_t::VALUE_TEXT, 0),
alv_schema_meta( alv_schema_meta(
intern_string::lookup("log_msg_schema"), value_kind_t::VALUE_TEXT, 2) intern_string::lookup("log_msg_schema"), value_kind_t::VALUE_TEXT, 1)
{ {
this->alv_value_meta.lvm_identifier = true;
this->alv_msg_meta.lvm_identifier = true; this->alv_msg_meta.lvm_identifier = true;
this->alv_schema_meta.lvm_identifier = true; this->alv_schema_meta.lvm_identifier = true;
} }
@ -51,8 +48,6 @@ all_logs_vtab::all_logs_vtab()
void void
all_logs_vtab::get_columns(std::vector<vtab_column>& cols) const all_logs_vtab::get_columns(std::vector<vtab_column>& cols) const
{ {
cols.emplace_back(vtab_column(this->alv_value_meta.lvm_name.get())
.with_comment("The name of the log file format"));
cols.emplace_back( cols.emplace_back(
vtab_column(this->alv_msg_meta.lvm_name.get()) vtab_column(this->alv_msg_meta.lvm_name.get())
.with_comment( .with_comment(
@ -71,7 +66,6 @@ all_logs_vtab::extract(std::shared_ptr<logfile> lf,
std::vector<logline_value>& values) std::vector<logline_value>& values)
{ {
auto format = lf->get_format(); auto format = lf->get_format();
values.emplace_back(this->alv_value_meta, format->get_name());
std::vector<logline_value> sub_values; std::vector<logline_value> sub_values;
@ -105,24 +99,10 @@ all_logs_vtab::extract(std::shared_ptr<logfile> lf,
values.emplace_back(this->alv_schema_meta, schema_ref); values.emplace_back(this->alv_schema_meta, schema_ref);
} }
bool
all_logs_vtab::is_valid(log_cursor& lc, logfile_sub_source& lss)
{
auto cl = lss.at(lc.lc_curr_line);
auto lf = lss.find(cl);
auto lf_iter = lf->begin() + cl;
if (!lf_iter->is_message()) {
return false;
}
return true;
}
bool bool
all_logs_vtab::next(log_cursor& lc, logfile_sub_source& lss) all_logs_vtab::next(log_cursor& lc, logfile_sub_source& lss)
{ {
lc.lc_curr_line = lc.lc_curr_line + vis_line_t(1); lc.lc_curr_line = lc.lc_curr_line + 1_vl;
lc.lc_sub_index = 0; lc.lc_sub_index = 0;
if (lc.is_eof()) { if (lc.is_eof()) {

@ -51,12 +51,9 @@ public:
shared_buffer_ref& line, shared_buffer_ref& line,
std::vector<logline_value>& values) override; std::vector<logline_value>& values) override;
bool is_valid(log_cursor& lc, logfile_sub_source& lss) override;
bool next(log_cursor& lc, logfile_sub_source& lss) override; bool next(log_cursor& lc, logfile_sub_source& lss) override;
private: private:
logline_value_meta alv_value_meta;
logline_value_meta alv_msg_meta; logline_value_meta alv_msg_meta;
logline_value_meta alv_schema_meta; logline_value_meta alv_schema_meta;
shared_buffer alv_schema_manager; shared_buffer alv_schema_manager;

@ -526,10 +526,16 @@ public:
return *this; return *this;
} }
template<typename C> attr_line_t& with_attr_for_all(const string_attr_pair& sap)
{
this->al_attrs.emplace_back(line_range{0, -1}, sap);
return *this;
}
template<typename C, typename F>
attr_line_t& join(const C& container, attr_line_t& join(const C& container,
const string_attr_pair& sap, const string_attr_pair& sap,
const char* fill) const F& fill)
{ {
bool init = true; bool init = true;
for (const auto& elem : container) { for (const auto& elem : container) {
@ -543,6 +549,21 @@ public:
return *this; return *this;
} }
template<typename C, typename F>
attr_line_t& join(const C& container, const F& fill)
{
bool init = true;
for (const auto& elem : container) {
if (!init) {
this->append(fill);
}
this->append(elem);
init = false;
}
return *this;
}
attr_line_t& insert(size_t index, attr_line_t& insert(size_t index,
const attr_line_t& al, const attr_line_t& al,
text_wrap_settings* tws = nullptr); text_wrap_settings* tws = nullptr);

@ -88,6 +88,17 @@ invoke(Fn&& f, Args&&... args) noexcept(
return std::forward<Fn>(f)(std::forward<Args>(args)...); return std::forward<Fn>(f)(std::forward<Args>(args)...);
} }
template<class F, class... Args>
struct is_invocable {
template<class U>
static auto test(U* p)
-> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template<class U>
static auto test(...) -> decltype(std::false_type());
static constexpr bool value = decltype(test<F>(0))::value;
};
} // namespace func } // namespace func
} // namespace lnav } // namespace lnav

@ -1,9 +1,38 @@
/**
* 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_itertools_hh #ifndef lnav_itertools_hh
#define lnav_itertools_hh #define lnav_itertools_hh
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <set>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
@ -76,6 +105,16 @@ struct append {
T p_value; T p_value;
}; };
struct nth {
nonstd::optional<size_t> a_index;
};
struct skip {
size_t a_count;
};
struct unique {};
} // namespace details } // namespace details
template<typename T> template<typename T>
@ -105,6 +144,22 @@ find(T value)
}; };
} }
inline details::nth
nth(nonstd::optional<size_t> index)
{
return details::nth{
index,
};
}
inline details::skip
skip(size_t count)
{
return details::skip{
count,
};
}
template<typename F> template<typename F>
inline details::filter_in<F> inline details::filter_in<F>
filter_in(F func) filter_in(F func)
@ -163,6 +218,12 @@ map(F func)
return details::mapper<F>{func}; return details::mapper<F>{func};
} }
inline auto
deref()
{
return map([](auto iter) { return *iter; });
}
template<typename R, typename T> template<typename R, typename T>
inline details::folder<R, T> inline details::folder<R, T>
fold(R func, T init) fold(R func, T init)
@ -170,6 +231,12 @@ fold(R func, T init)
return details::folder<R, T>{func, init}; return details::folder<R, T>{func, init};
} }
inline details::unique
unique()
{
return details::unique{};
}
inline details::sorted inline details::sorted
sorted() sorted()
{ {
@ -195,12 +262,15 @@ chain(const T& value1, const Args&... args)
} // namespace lnav } // namespace lnav
template<typename C, typename P> template<typename C, typename P>
nonstd::optional<typename C::value_type> nonstd::optional<std::conditional_t<
operator|(const C& in, const lnav::itertools::details::find_if<P>& finder) std::is_const<typename std::remove_reference_t<C>>::value,
typename std::remove_reference_t<C>::const_iterator,
typename std::remove_reference_t<C>::iterator>>
operator|(C&& in, const lnav::itertools::details::find_if<P>& finder)
{ {
for (const auto& elem : in) { for (auto iter = in.begin(); iter != in.end(); ++iter) {
if (lnav::func::invoke(finder.fi_predicate, elem)) { if (lnav::func::invoke(finder.fi_predicate, *iter)) {
return nonstd::make_optional(elem); return nonstd::make_optional(iter);
} }
} }
@ -222,6 +292,56 @@ operator|(const C& in, const lnav::itertools::details::find<T>& finder)
return nonstd::nullopt; return nonstd::nullopt;
} }
template<typename C>
nonstd::optional<typename C::const_iterator>
operator|(const C& in, const lnav::itertools::details::nth indexer)
{
if (!indexer.a_index.has_value()) {
return nonstd::nullopt;
}
if (indexer.a_index.value() < in.size()) {
auto iter = in.begin();
std::advance(iter, indexer.a_index.value());
return nonstd::make_optional(iter);
}
return nonstd::nullopt;
}
template<typename C>
C
operator|(const C& in, const lnav::itertools::details::skip& skipper)
{
C retval;
if (skipper.a_count < in.size()) {
auto iter = in.begin();
std::advance(iter, skipper.a_count);
for (; iter != in.end(); ++iter) {
retval.emplace_back(*iter);
}
}
return retval;
}
template<typename T, typename F>
std::vector<T*>
operator|(const std::vector<std::unique_ptr<T>>& in,
const lnav::itertools::details::filter_in<F>& filterer)
{
std::vector<T*> retval;
for (const auto& elem : in) {
if (lnav::func::invoke(filterer.f_func, elem)) {
retval.emplace_back(elem.get());
}
}
return retval;
}
template<typename C, typename F> template<typename C, typename F>
C C
operator|(const C& in, const lnav::itertools::details::filter_in<F>& filterer) operator|(const C& in, const lnav::itertools::details::filter_in<F>& filterer)
@ -283,6 +403,13 @@ operator|(const C& in, const lnav::itertools::details::folder<R, T>& folder)
return accum; return accum;
} }
template<typename C>
std::set<typename C::value_type>
operator|(C&& in, const lnav::itertools::details::unique& sorter)
{
return {in.begin(), in.end()};
}
template<typename T, typename C> template<typename T, typename C>
T T
operator|(T in, const lnav::itertools::details::sort_by<C>& sorter) operator|(T in, const lnav::itertools::details::sort_by<C>& sorter)
@ -301,6 +428,23 @@ operator|(T in, const lnav::itertools::details::sorted& sorter)
return in; return in;
} }
template<typename T,
typename F,
std::enable_if_t<lnav::func::is_invocable<F, T>::value, int> = 0>
auto
operator|(nonstd::optional<T> in,
const lnav::itertools::details::mapper<F>& mapper)
-> nonstd::optional<
typename std::remove_reference_t<typename std::remove_const_t<
decltype(lnav::func::invoke(mapper.m_func, in.value()))>>>
{
if (!in) {
return nonstd::nullopt;
}
return nonstd::make_optional(lnav::func::invoke(mapper.m_func, in.value()));
}
template<typename T, typename F> template<typename T, typename F>
auto auto
operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper) operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
@ -319,13 +463,13 @@ operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
template<typename T, typename F> template<typename T, typename F>
auto auto
operator|(const std::vector<std::shared_ptr<T>>& in, operator|(const std::vector<T>& in,
const lnav::itertools::details::mapper<F>& mapper) const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<typename std::remove_const_t<decltype(((*in.front()) -> std::vector<typename std::remove_const_t<decltype((*(*in.begin())
.*mapper.m_func)())>> .*mapper.m_func)())>>
{ {
using return_type = std::vector<typename std::remove_const_t<decltype(( using return_type = std::vector<typename std::remove_const_t<decltype((
(*in.front()).*mapper.m_func)())>>; *(*in.begin()).*mapper.m_func)())>>;
return_type retval; return_type retval;
retval.reserve(in.size()); retval.reserve(in.size());
@ -358,6 +502,25 @@ operator|(const std::vector<std::shared_ptr<T>>& in,
return retval; return retval;
} }
template<typename T, typename F>
auto
operator|(const std::vector<T>& in,
const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<typename std::remove_reference_t<
typename std::remove_const_t<decltype(((in.front()).*mapper.m_func))>>>
{
using return_type = std::vector<typename std::remove_reference_t<
typename std::remove_const_t<decltype(((in.front()).*mapper.m_func))>>>;
return_type retval;
retval.reserve(in.size());
for (const auto& elem : in) {
retval.template emplace_back(elem.*mapper.m_func);
}
return retval;
}
template<typename T, typename F> template<typename T, typename F>
auto auto
operator|(nonstd::optional<T> in, operator|(nonstd::optional<T> in,
@ -372,6 +535,21 @@ operator|(nonstd::optional<T> in,
return nonstd::make_optional((in.value()).*mapper.m_func); return nonstd::make_optional((in.value()).*mapper.m_func);
} }
template<typename T, typename F>
auto
operator|(nonstd::optional<T> in,
const lnav::itertools::details::mapper<F>& mapper)
-> nonstd::optional<
typename std::remove_const_t<typename std::remove_reference_t<
decltype(((*in.value()).*mapper.m_func))>>>
{
if (!in) {
return nonstd::nullopt;
}
return nonstd::make_optional((*in.value()).*mapper.m_func);
}
template<typename T> template<typename T>
T T
operator|(nonstd::optional<T> in, operator|(nonstd::optional<T> in,
@ -383,11 +561,11 @@ operator|(nonstd::optional<T> in,
template<typename T, typename F> template<typename T, typename F>
auto auto
operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper) operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<std::remove_const_t<decltype(((typename T::value_type{}) -> std::vector<
.*mapper.m_func)())>> std::remove_const_t<decltype(((*in.begin()).*mapper.m_func)())>>
{ {
using return_type = std::vector<std::remove_const_t<decltype(( using return_type = std::vector<
(typename T::value_type{}).*mapper.m_func)())>>; std::remove_const_t<decltype(((*in.begin()).*mapper.m_func)())>>;
return_type retval; return_type retval;
retval.reserve(in.size()); retval.reserve(in.size());

@ -340,10 +340,14 @@ println(FILE* file, const attr_line_t& al)
line_style |= fg_style; line_style |= fg_style;
} }
fmt::print(file, if (start < str.size()) {
line_style, auto actual_end
FMT_STRING("{}"), = std::min(str.size(), static_cast<size_t>(point));
str.substr(start, point - start)); fmt::print(file,
line_style,
FMT_STRING("{}"),
str.substr(start, actual_end - start));
}
} }
last_point = point; last_point = point;
} }

@ -110,6 +110,7 @@ enum class role_t : int32_t {
VCR_H6, VCR_H6,
VCR_LIST_GLYPH, VCR_LIST_GLYPH,
VCR_BREADCRUMB,
VCR__MAX VCR__MAX
}; };
@ -119,7 +120,8 @@ using string_attr_value = mapbox::util::variant<int64_t,
const intern_string_t, const intern_string_t,
std::string, std::string,
std::shared_ptr<logfile>, std::shared_ptr<logfile>,
bookmark_metadata*>; bookmark_metadata*,
timespec>;
class string_attr_type_base { class string_attr_type_base {
public: public:
@ -200,6 +202,22 @@ status(S str)
VC_ROLE.template value(role_t::VCR_STATUS)); VC_ROLE.template value(role_t::VCR_STATUS));
} }
template<typename S>
inline std::pair<S, string_attr_pair>
inactive_status(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_INACTIVE_STATUS));
}
template<typename S>
inline std::pair<S, string_attr_pair>
status_title(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_STATUS_TITLE));
}
template<typename S> template<typename S>
inline std::pair<S, string_attr_pair> inline std::pair<S, string_attr_pair>
ok(S str) ok(S str)
@ -256,6 +274,30 @@ comment(S str)
VC_ROLE.template value(role_t::VCR_COMMENT)); VC_ROLE.template value(role_t::VCR_COMMENT));
} }
template<typename S>
inline std::pair<S, string_attr_pair>
identifier(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_IDENTIFIER));
}
template<typename S>
inline std::pair<S, string_attr_pair>
list_glyph(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_LIST_GLYPH));
}
template<typename S>
inline std::pair<S, string_attr_pair>
breadcrumb(S str)
{
return std::make_pair(std::move(str),
VC_ROLE.template value(role_t::VCR_BREADCRUMB));
}
template<typename S> template<typename S>
inline std::pair<S, string_attr_pair> inline std::pair<S, string_attr_pair>
h1(S str) h1(S str)
@ -362,6 +404,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _list_glyph(
VC_ROLE.template value(role_t::VCR_LIST_GLYPH)); VC_ROLE.template value(role_t::VCR_LIST_GLYPH));
} }
inline std::pair<std::string, string_attr_pair> operator"" _breadcrumb(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_BREADCRUMB));
}
} // namespace literals } // namespace literals
} // namespace roles } // namespace roles

@ -76,7 +76,8 @@ bookmark_type_t::find_type(const std::string& name)
{ {
return get_all_types() return get_all_types()
| lnav::itertools::find_if( | lnav::itertools::find_if(
[&name](const auto& elem) { return elem->bt_name == name; }); [&name](const auto& elem) { return elem->bt_name == name; })
| lnav::itertools::deref();
} }
std::vector<bookmark_type_t*>& std::vector<bookmark_type_t*>&

@ -0,0 +1,124 @@
/**
* 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_breadcrumb_hh
#define lnav_breadcrumb_hh
#include <string>
#include "base/attr_line.hh"
#include "fmt/format.h"
#include "mapbox/variant.hpp"
namespace breadcrumb {
struct possibility {
possibility(std::string key, attr_line_t value)
: p_key(std::move(key)), p_display_value(std::move(value))
{
}
explicit possibility(std::string key) : p_key(key), p_display_value(key) {}
possibility() = default;
std::string p_key;
attr_line_t p_display_value;
};
using crumb_possibilities = std::function<std::vector<possibility>()>;
struct crumb {
using key_t = mapbox::util::variant<std::string, size_t>;
using perform = std::function<void(const key_t& key)>;
crumb(std::string key,
attr_line_t al,
crumb_possibilities cp,
perform performer)
: c_key(std::move(key)), c_display_value(std::move(al)),
c_possibility_provider(std::move(cp)),
c_performer(std::move(performer))
{
}
crumb(std::string key, crumb_possibilities cp, perform performer)
: c_key(key), c_display_value(key),
c_possibility_provider(std::move(cp)),
c_performer(std::move(performer))
{
}
explicit crumb(size_t index, crumb_possibilities cp, perform performer)
: c_key(index), c_display_value(fmt::format(FMT_STRING("[{}]"), index)),
c_possibility_provider(std::move(cp)),
c_performer(std::move(performer))
{
}
explicit crumb(key_t key, crumb_possibilities cp, perform performer)
: c_key(key), c_display_value(key.match(
[](std::string str) { return str; },
[](size_t index) {
return fmt::format(FMT_STRING("[{}]"), index);
})),
c_possibility_provider(std::move(cp)),
c_performer(std::move(performer))
{
}
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);
return *this;
}
enum class expected_input_t {
exact,
index,
index_or_exact,
anything,
};
key_t c_key;
attr_line_t c_display_value;
crumb_possibilities c_possibility_provider;
perform c_performer;
nonstd::optional<size_t> c_possible_range;
expected_input_t c_expected_input{expected_input_t::exact};
std::string c_search_placeholder;
};
} // namespace breadcrumb
#endif

@ -0,0 +1,397 @@
/**
* 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 "breadcrumb_curses.hh"
#include "base/itertools.hh"
#include "itertools.similar.hh"
#include "log_format_fwd.hh"
#include "logfile.hh"
using namespace lnav::roles::literals;
breadcrumb_curses::breadcrumb_curses()
{
this->bc_match_search_overlay.sos_parent = this;
this->bc_match_source.set_reverse_selection(true);
this->bc_match_view.set_selectable(true);
this->bc_match_view.set_overlay_source(&this->bc_match_search_overlay);
this->bc_match_view.set_sub_source(&this->bc_match_source);
this->bc_match_view.set_height(0_vl);
this->bc_match_view.set_show_scrollbar(true);
this->bc_match_view.set_default_role(role_t::VCR_POPUP);
this->add_child_view(&this->bc_match_view);
}
void
breadcrumb_curses::do_update()
{
if (!this->bc_line_source) {
return;
}
size_t crumb_index = 0;
size_t sel_crumb_offset = 0;
auto width = getmaxx(this->bc_window);
auto crumbs = this->bc_focused_crumbs.empty() ? this->bc_line_source()
: this->bc_focused_crumbs;
auto crumbs_line
= crumbs | lnav::itertools::map(&breadcrumb::crumb::c_display_value)
| lnav::itertools::fold(
[&](const auto& elem, auto& accum) {
auto accum_width = utf8_string_length(accum.get_string())
.template unwrap();
auto elem_width
= utf8_string_length(elem.get_string()).template unwrap();
auto is_selected = this->bc_selected_crumb
&& (crumb_index == this->bc_selected_crumb.value());
if (is_selected && ((accum_width + elem_width) > width)) {
accum.clear();
accum.append("\u22ef\u276d"_breadcrumb);
accum_width = 2;
}
accum.append(elem);
if (is_selected) {
sel_crumb_offset = accum_width;
accum.get_attrs().emplace_back(
line_range{
(int) (accum.length() - elem.length()),
(int) accum.length(),
},
VC_STYLE.template value(A_REVERSE));
}
crumb_index += 1;
return accum.append("\u276d"_breadcrumb);
},
attr_line_t());
line_range lr{0, width};
view_curses::mvwattrline(
this->bc_window, this->bc_y, 0, crumbs_line, lr, role_t::VCR_STATUS);
if (this->bc_selected_crumb) {
this->bc_match_view.set_x(sel_crumb_offset);
}
view_curses::do_update();
}
void
breadcrumb_curses::reload_data()
{
if (!this->bc_selected_crumb) {
return;
}
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);
nonstd::optional<size_t> selected_value;
this->bc_similar_values = this->bc_possible_values
| lnav::itertools::similar_to(
[](const auto& elem) { return elem.p_key; },
this->bc_current_search,
INT_MAX);
if (selected_crumb_ref.c_key.is<std::string>()) {
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) {
return elem.p_key == selected_crumb_key;
});
if (found_poss_opt) {
selected_value = std::distance(this->bc_similar_values.begin(),
found_poss_opt.value());
} else {
selected_value = 0;
}
}
auto matches = attr_line_t().join(
this->bc_similar_values
| lnav::itertools::map(&breadcrumb::possibility::p_display_value),
"\n");
this->bc_match_source.replace_with(matches);
auto width = this->bc_possible_values
| lnav::itertools::fold(
[](const auto& match, auto& accum) {
auto mlen = match.p_display_value.length();
if (mlen > accum) {
return mlen;
}
return accum;
},
selected_crumb_ref.c_display_value.length());
if (selected_crumb_ref.c_search_placeholder.size() > width) {
width = selected_crumb_ref.c_search_placeholder.size();
}
this->bc_match_view.set_height(vis_line_t(
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.reload_data();
this->set_needs_update();
}
void
breadcrumb_curses::focus()
{
this->bc_focused_crumbs = this->bc_line_source();
if (this->bc_focused_crumbs.empty()) {
return;
}
this->bc_current_search.clear();
this->bc_selected_crumb = this->bc_last_selected_crumb.value_or(0);
this->reload_data();
}
void
breadcrumb_curses::blur()
{
this->bc_last_selected_crumb = this->bc_selected_crumb;
this->bc_focused_crumbs.clear();
this->bc_selected_crumb = nonstd::nullopt;
this->bc_current_search.clear();
this->bc_match_view.set_height(0_vl);
this->bc_match_source.clear();
this->set_needs_update();
}
bool
breadcrumb_curses::handle_key(int ch)
{
bool retval = false;
switch (ch) {
case KEY_BTAB:
case KEY_LEFT:
if (this->bc_selected_crumb) {
if (this->bc_selected_crumb.value() > 0) {
this->bc_selected_crumb
= this->bc_selected_crumb.value() - 1;
} else {
this->bc_selected_crumb
= this->bc_focused_crumbs.size() - 1;
}
this->bc_current_search.clear();
this->reload_data();
}
retval = true;
break;
case '\t':
case KEY_RIGHT:
if (this->bc_selected_crumb) {
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;
}
this->bc_current_search.clear();
this->reload_data();
}
retval = true;
break;
case KEY_HOME:
this->bc_match_view.set_selection(0_vl);
retval = true;
break;
case KEY_END:
this->bc_match_view.set_selection(
this->bc_match_view.get_inner_height() - 1_vl);
retval = true;
break;
case KEY_NPAGE:
this->bc_match_view.shift_selection(3);
retval = true;
break;
case KEY_PPAGE:
this->bc_match_view.shift_selection(-3);
retval = true;
break;
case KEY_UP:
this->bc_match_view.shift_selection(-1);
retval = true;
break;
case KEY_DOWN:
this->bc_match_view.shift_selection(1);
retval = true;
break;
case 0x7f:
case KEY_BACKSPACE:
if (!this->bc_current_search.empty()) {
this->bc_current_search.pop_back();
this->reload_data();
}
retval = true;
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);
}
}
}
break;
default:
if (isprint(ch)) {
this->bc_current_search.push_back(ch);
this->reload_data();
retval = true;
}
break;
}
if (!retval) {
this->blur();
}
this->set_needs_update();
return retval;
}
bool
breadcrumb_curses::search_overlay_source::list_value_for_overlay(
const listview_curses& lv,
int y,
int bottom,
vis_line_t line,
attr_line_t& value_out)
{
if (y == 0) {
auto* parent = this->sos_parent;
auto sel_opt = parent->bc_focused_crumbs
| lnav::itertools::nth(parent->bc_selected_crumb);
auto exp_input = sel_opt
| lnav::itertools::map(&breadcrumb::crumb::c_expected_input)
| lnav::itertools::unwrap_or(
breadcrumb::crumb::expected_input_t::exact);
value_out.with_attr_for_all(VC_STYLE.value(A_UNDERLINE));
if (!parent->bc_current_search.empty()) {
value_out = parent->bc_current_search;
role_t combobox_role = role_t::VCR_STATUS;
switch (exp_input) {
case breadcrumb::crumb::expected_input_t::exact:
if (parent->bc_similar_values.empty()) {
combobox_role = role_t::VCR_ALERT_STATUS;
}
break;
case breadcrumb::crumb::expected_input_t::index: {
size_t index;
if (sscanf(parent->bc_current_search.c_str(), "%zu", &index)
!= 1
|| index < 0
|| (index
>= (sel_opt
| lnav::itertools::map([](const auto& cr) {
return cr->c_possible_range.value_or(0);
})
| lnav::itertools::unwrap_or(size_t{0}))))
{
combobox_role = role_t::VCR_ALERT_STATUS;
}
break;
}
case breadcrumb::crumb::expected_input_t::index_or_exact: {
size_t index;
if (sscanf(parent->bc_current_search.c_str(), "%zu", &index)
== 1) {
if (index < 0
|| (index
>= (sel_opt
| lnav::itertools::map([](const auto& cr) {
return cr->c_possible_range.value_or(
0);
})
| lnav::itertools::unwrap_or(size_t{0}))))
{
combobox_role = role_t::VCR_ALERT_STATUS;
}
} else if (parent->bc_similar_values.empty()) {
combobox_role = role_t::VCR_ALERT_STATUS;
}
break;
}
case breadcrumb::crumb::expected_input_t::anything:
break;
}
value_out.with_attr_for_all(VC_ROLE.value(combobox_role));
return true;
}
if (parent->bc_selected_crumb) {
auto& selected_crumb_ref
= parent->bc_focused_crumbs[parent->bc_selected_crumb.value()];
if (!selected_crumb_ref.c_search_placeholder.empty()) {
value_out = selected_crumb_ref.c_search_placeholder;
value_out.with_attr_for_all(
VC_ROLE.value(role_t::VCR_INACTIVE_STATUS));
return true;
}
}
}
return false;
}

@ -0,0 +1,100 @@
/**
* 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_breadcrumb_curses_hh
#define lnav_breadcrumb_curses_hh
#include <functional>
#include <utility>
#include <vector>
#include "plain_text_source.hh"
#include "textview_curses.hh"
#include "view_curses.hh"
class breadcrumb_curses : public view_curses {
public:
breadcrumb_curses();
void set_y(int y)
{
this->bc_y = y;
this->bc_match_view.set_y(y + 1);
}
int get_y() const { return this->bc_y; }
void set_window(WINDOW* win)
{
this->bc_window = win;
this->bc_match_view.set_window(win);
}
void set_line_source(std::function<std::vector<breadcrumb::crumb>()> ls)
{
this->bc_line_source = std::move(ls);
}
void focus();
void blur();
bool handle_key(int ch);
void do_update() override;
void reload_data();
private:
class search_overlay_source : public list_overlay_source {
public:
bool list_value_for_overlay(const listview_curses& lv,
int y,
int bottom,
vis_line_t line,
attr_line_t& value_out) override;
breadcrumb_curses* sos_parent{nullptr};
};
WINDOW* bc_window{nullptr};
std::function<std::vector<breadcrumb::crumb>()> bc_line_source;
int bc_y{0};
std::vector<breadcrumb::crumb> bc_focused_crumbs;
nonstd::optional<size_t> bc_selected_crumb;
nonstd::optional<size_t> bc_last_selected_crumb;
std::vector<breadcrumb::possibility> bc_possible_values;
std::vector<breadcrumb::possibility> bc_similar_values;
std::string bc_current_search;
plain_text_source bc_match_source;
search_overlay_source bc_match_search_overlay;
textview_curses bc_match_view;
};
#endif

@ -81,8 +81,6 @@ sql_progress(const struct log_cursor& lc)
if (ui_periodic_timer::singleton().time_to_update(sql_counter)) { if (ui_periodic_timer::singleton().time_to_update(sql_counter)) {
lnav_data.ld_bottom_source.update_loading(off, total); lnav_data.ld_bottom_source.update_loading(off, total);
lnav_data.ld_top_source.update_time();
lnav_data.ld_status[LNS_TOP].do_update();
lnav_data.ld_status[LNS_BOTTOM].do_update(); lnav_data.ld_status[LNS_BOTTOM].do_update();
refresh(); refresh();
} }
@ -98,8 +96,6 @@ sql_progress_finished()
} }
lnav_data.ld_bottom_source.update_loading(0, 0); lnav_data.ld_bottom_source.update_loading(0, 0);
lnav_data.ld_top_source.update_time();
lnav_data.ld_status[LNS_TOP].do_update();
lnav_data.ld_status[LNS_BOTTOM].do_update(); lnav_data.ld_status[LNS_BOTTOM].do_update();
lnav_data.ld_views[LNV_DB].redo_search(); lnav_data.ld_views[LNV_DB].redo_search();
} }
@ -926,6 +922,17 @@ exec_context::exec_context(std::vector<logline_value>* line_values,
this->ec_source.emplace( this->ec_source.emplace(
lnav::console::snippet::from(COMMAND_SRC, "").with_line(1)); lnav::console::snippet::from(COMMAND_SRC, "").with_line(1));
this->ec_output_stack.emplace_back("screen", nonstd::nullopt); this->ec_output_stack.emplace_back("screen", nonstd::nullopt);
this->ec_error_callback_stack.emplace_back(
[](const auto& um) { lnav::console::print(stderr, um); });
}
void
exec_context::execute(const std::string& cmdline)
{
auto exec_res = execute_any(*this, cmdline);
if (exec_res.isErr()) {
this->ec_error_callback_stack.back()(exec_res.unwrapErr());
}
} }
void void

@ -56,6 +56,9 @@ int sql_callback(exec_context& ec, sqlite3_stmt* stmt);
using pipe_callback_t using pipe_callback_t
= std::future<std::string> (*)(exec_context&, const std::string&, auto_fd&); = std::future<std::string> (*)(exec_context&, const std::string&, auto_fd&);
using error_callback_t
= std::function<void(const lnav::console::user_message&)>;
struct exec_context { struct exec_context {
enum class perm_t { enum class perm_t {
READ_WRITE, READ_WRITE,
@ -117,14 +120,23 @@ struct exec_context {
void clear_output(); void clear_output();
struct source_guard { struct source_guard {
source_guard(exec_context& context) : sg_context(context) {} source_guard(exec_context* context) : sg_context(context) {}
source_guard(const source_guard&) = delete;
source_guard(source_guard&& other) : sg_context(other.sg_context)
{
other.sg_context = nullptr;
}
~source_guard() ~source_guard()
{ {
this->sg_context.ec_source.pop(); if (this->sg_context != nullptr) {
this->sg_context->ec_source.pop();
}
} }
exec_context& sg_context; exec_context* sg_context;
}; };
struct output_guard { struct output_guard {
@ -144,7 +156,32 @@ struct exec_context {
{ {
this->ec_source.emplace( this->ec_source.emplace(
lnav::console::snippet::from(path, content).with_line(line_number)); lnav::console::snippet::from(path, content).with_line(line_number));
return {*this}; return {this};
}
struct error_cb_guard {
error_cb_guard(exec_context* context) : sg_context(context) {}
error_cb_guard(const error_cb_guard&) = delete;
error_cb_guard(error_cb_guard&& other) : sg_context(other.sg_context)
{
other.sg_context = nullptr;
}
~error_cb_guard()
{
if (this->sg_context != nullptr) {
this->sg_context->ec_error_callback_stack.pop_back();
}
}
exec_context* sg_context;
};
error_cb_guard add_error_callback(error_callback_t cb)
{
this->ec_error_callback_stack.emplace_back(std::move(cb));
return {this};
} }
scoped_resolver create_resolver() scoped_resolver create_resolver()
@ -155,6 +192,32 @@ struct exec_context {
}; };
} }
void execute(const std::string& cmdline);
using kv_pair_t = std::pair<std::string, std::string>;
void execute_with_int(const std::string& cmdline)
{
this->execute(cmdline);
}
template<typename... Args>
void execute_with_int(const std::string& cmdline,
kv_pair_t pair,
Args... args)
{
this->ec_local_vars.top().template emplace(pair);
this->execute(cmdline, args...);
}
template<typename... Args>
void execute_with(const std::string& cmdline, Args... args)
{
this->ec_local_vars.push({});
this->execute_with_int(cmdline, args...);
this->ec_local_vars.pop();
}
vis_line_t ec_top_line{0_vl}; vis_line_t ec_top_line{0_vl};
bool ec_dry_run{false}; bool ec_dry_run{false};
perm_t ec_perms{perm_t::READ_WRITE}; perm_t ec_perms{perm_t::READ_WRITE};
@ -174,6 +237,7 @@ struct exec_context {
sql_callback_t ec_sql_callback; sql_callback_t ec_sql_callback;
pipe_callback_t ec_pipe_callback; pipe_callback_t ec_pipe_callback;
std::vector<error_callback_t> ec_error_callback_stack;
}; };
Result<std::string, lnav::console::user_message> execute_command( Result<std::string, lnav::console::user_message> execute_command(

@ -330,6 +330,7 @@ data_parser::pairup(data_parser::schema_id_t* schema,
case DT_IPV6_ADDRESS: case DT_IPV6_ADDRESS:
case DT_MAC_ADDRESS: case DT_MAC_ADDRESS:
case DT_HEX_DUMP: case DT_HEX_DUMP:
case DT_XML_DECL_TAG:
case DT_XML_OPEN_TAG: case DT_XML_OPEN_TAG:
case DT_XML_CLOSE_TAG: case DT_XML_CLOSE_TAG:
case DT_XML_EMPTY_TAG: case DT_XML_EMPTY_TAG:

@ -81,6 +81,12 @@ static struct {
pcrepp("\\A([0-9a-fA-F][0-9a-fA-F](?::[0-9a-fA-F][0-9a-fA-F])+)"), pcrepp("\\A([0-9a-fA-F][0-9a-fA-F](?::[0-9a-fA-F][0-9a-fA-F])+)"),
}, },
{
"xmld",
pcrepp("\\A(<!\\??[\\w:]+\\s*(?:[\\w:]+(?:\\s*=\\s*"
"(?:\"((?:\\\\.|[^\"])+)\"|'((?:\\\\.|[^'])+)'|[^>]+)"
"))*\\s*>)"),
},
{ {
"xmlt", "xmlt",
pcrepp("\\A(<\\??[\\w:]+\\s*(?:[\\w:]+(?:\\s*=\\s*" pcrepp("\\A(<\\??[\\w:]+\\s*(?:[\\w:]+(?:\\s*=\\s*"

@ -46,6 +46,7 @@ enum data_token_t {
DT_TIME, DT_TIME,
DT_IPV6_ADDRESS, DT_IPV6_ADDRESS,
DT_HEX_DUMP, DT_HEX_DUMP,
DT_XML_DECL_TAG,
DT_XML_EMPTY_TAG, DT_XML_EMPTY_TAG,
DT_XML_OPEN_TAG, DT_XML_OPEN_TAG,
DT_XML_CLOSE_TAG, DT_XML_CLOSE_TAG,
@ -119,7 +120,7 @@ public:
if (!line.empty() && line[line.length() - 1] == '.') { if (!line.empty() && line[line.length() - 1] == '.') {
this->ds_pcre_input.pi_length -= 1; this->ds_pcre_input.pi_length -= 1;
} }
}; }
data_scanner(shared_buffer_ref& line, data_scanner(shared_buffer_ref& line,
size_t off = 0, size_t off = 0,
@ -132,20 +133,14 @@ public:
if (line.length() > 0 && line.get_data()[line.length() - 1] == '.') { if (line.length() > 0 && line.get_data()[line.length() - 1] == '.') {
this->ds_pcre_input.pi_length -= 1; this->ds_pcre_input.pi_length -= 1;
} }
}; }
bool tokenize(pcre_context& pc, data_token_t& token_out); bool tokenize(pcre_context& pc, data_token_t& token_out);
bool tokenize2(pcre_context& pc, data_token_t& token_out); bool tokenize2(pcre_context& pc, data_token_t& token_out);
pcre_input& get_input() pcre_input& get_input() { return this->ds_pcre_input; }
{
return this->ds_pcre_input;
};
void reset() void reset() { this->ds_pcre_input.reset_next_offset(); }
{
this->ds_pcre_input.reset_next_offset();
};
private: private:
std::string ds_line; std::string ds_line;

File diff suppressed because it is too large Load Diff

@ -180,15 +180,19 @@ bool data_scanner::tokenize2(pcre_context &pc, data_token_t &token_out)
} }
IPV6ADDR/[^:a-zA-Z0-9] { RET(DT_IPV6_ADDRESS); } IPV6ADDR/[^:a-zA-Z0-9] { RET(DT_IPV6_ADDRESS); }
"<""?"?[a-zA-Z0-9_:\-]+SPACE*([a-zA-Z0-9_:\-]+(SPACE*'='SPACE*('"'(('\\'.|[^\x00"\\])+)'"'|"'"(('\\'.|[^\x00'\\])+)"'"|[^\x00>]+)))*SPACE*("/"|"?")">" { "<!"[a-zA-Z0-9_:\-]+SPACE*([a-zA-Z0-9_:\-]+(SPACE*'='SPACE*('"'(('\\'.|[^\x00"\\])+)'"'|"'"(('\\'.|[^\x00'\\])+)"'"|[^\x00>]+))?|SPACE*('"'(('\\'.|[^\x00"\\])+)'"'|"'"(('\\'.|[^\x00'\\])+)"'"))*SPACE*">" {
RET(DT_XML_DECL_TAG);
}
"<""?"?[a-zA-Z0-9_:\-]+SPACE*([a-zA-Z0-9_:\-]+(SPACE*'='SPACE*('"'(('\\'.|[^\x00"\\])+)'"'|"'"(('\\'.|[^\x00'\\])+)"'"|[^\x00>]+))?)*SPACE*("/"|"?")">" {
RET(DT_XML_EMPTY_TAG); RET(DT_XML_EMPTY_TAG);
} }
"<"[a-zA-Z0-9_:\-]+SPACE*([a-zA-Z0-9_:\-]+(SPACE*"="SPACE*('"'(('\\'.|[^\x00"\\])+)'"'|"'"(('\\'.|[^\x00'\\])+)"'"|[^\x00>]+)))*SPACE*">" { "<"[a-zA-Z0-9_:\-]+SPACE*([a-zA-Z0-9_:\-]+(SPACE*"="SPACE*('"'(('\\'.|[^\x00"\\])+)'"'|"'"(('\\'.|[^\x00'\\])+)"'"|[^\x00>]+))?)*SPACE*">" {
RET(DT_XML_OPEN_TAG); RET(DT_XML_OPEN_TAG);
} }
"</"[a-zA-Z0-9:\-]+SPACE*">" { "</"[a-zA-Z0-9_:\-]+SPACE*">" {
RET(DT_XML_CLOSE_TAG); RET(DT_XML_CLOSE_TAG);
} }

@ -353,27 +353,21 @@ filter_sub_source::text_attrs_for_line(textview_curses& tc,
line_range lr{2, 3}; line_range lr{2, 3};
value_out.emplace_back(lr, VC_GRAPHIC.value(enabled)); value_out.emplace_back(lr, VC_GRAPHIC.value(enabled));
if (tf->is_enabled()) { if (tf->is_enabled()) {
value_out.emplace_back(lr, value_out.emplace_back(
VC_FOREGROUND.value( lr, VC_FOREGROUND.value(vcolors.ansi_to_theme_color(COLOR_GREEN)));
vcolors.ansi_to_theme_color(COLOR_GREEN)));
} }
role_t fg_role = tf->get_type() == text_filter::INCLUDE ? role_t::VCR_OK role_t fg_role = tf->get_type() == text_filter::INCLUDE ? role_t::VCR_OK
: role_t::VCR_ERROR; : role_t::VCR_ERROR;
value_out.emplace_back(line_range{4, 7}, value_out.emplace_back(line_range{4, 7}, VC_ROLE.value(fg_role));
VC_ROLE_FG.value(fg_role)); value_out.emplace_back(line_range{4, 7}, VC_STYLE.value(A_BOLD));
value_out.emplace_back(line_range{4, 7},
VC_STYLE.value(A_BOLD));
value_out.emplace_back(line_range{8, 17}, value_out.emplace_back(line_range{8, 17}, VC_STYLE.value(A_BOLD));
VC_STYLE.value(A_BOLD)); value_out.emplace_back(line_range{23, 24}, VC_GRAPHIC.value(ACS_VLINE));
value_out.emplace_back(line_range{23, 24},
VC_GRAPHIC.value(ACS_VLINE));
if (selected) { if (selected) {
value_out.emplace_back( value_out.emplace_back(line_range{0, -1},
line_range{0, -1}, VC_ROLE.value(role_t::VCR_FOCUSED));
VC_ROLE.value(role_t::VCR_FOCUSED));
} }
attr_line_t content{tf->get_id()}; attr_line_t content{tf->get_id()};
@ -613,7 +607,7 @@ filter_sub_source::rl_display_matches(readline_curses* rc)
attr_line_t al; attr_line_t al;
vis_line_t line, selected_line; vis_line_t line, selected_line;
for (auto& match : matches) { for (const auto& match : matches) {
if (match == current_match) { if (match == current_match) {
al.append(match, VC_STYLE.value(A_REVERSE)); al.append(match, VC_STYLE.value(A_REVERSE));
selected_line = line; selected_line = line;

@ -6,10 +6,10 @@
"url": "http://en.wikipedia.org/wiki/Syslog", "url": "http://en.wikipedia.org/wiki/Syslog",
"regex": { "regex": {
"std": { "std": {
"pattern": "^(?<timestamp>(?:\\S{3,8}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z))(?: (?<log_hostname>[a-zA-Z0-9:][^ ]+[a-zA-Z0-9]))?(?:(?: (?<log_procname>(?:[^\\[:]+|[^:]+))(?:\\[(?<log_pid>\\d+)\\])?:\\s*(?<body>.*))$|:?(?:(?: ---)? last message repeated \\d+ times?(?: ---)?))" "pattern": "^(?<timestamp>(?:\\S{3,8}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z))(?: (?<log_hostname>[a-zA-Z0-9:][^ ]+[a-zA-Z0-9]))?(?:(?: (?<log_syslog_tag>(?<log_procname>(?:[^\\[:]+|[^:]+))(?:\\[(?<log_pid>\\d+)\\])?):\\s*(?<body>.*))$|:?(?:(?: ---)? last message repeated \\d+ times?(?: ---)?))"
}, },
"rfc5424": { "rfc5424": {
"pattern": "^<(?<log_pri>\\d+)>(?<syslog_version>\\d+) (?<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{6})?(?:[^ ]+)?) (?<log_hostname>[^ ]+|-) (?<log_procname>[^ ]+|-) (?<log_pid>[^ ]+|-) (?<log_msgid>[^ ]+|-) (?<log_struct>\\[(?:[^\\]\"]|\"(?:\\.|[^\"])+\")*\\]|-|)\\s+(?<body>.*)" "pattern": "^<(?<log_pri>\\d+)>(?<syslog_version>\\d+) (?<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{6})?(?:[^ ]+)?) (?<log_hostname>[^ ]+|-) (?<log_syslog_tag>(?<log_procname>[^ ]+|-) (?<log_pid>[^ ]+|-) (?<log_msgid>[^ ]+|-)) (?<log_struct>\\[(?:[^\\]\"]|\"(?:\\.|[^\"])+\")*\\]|-|)\\s+(?<body>.*)"
} }
}, },
"level-field": "body", "level-field": "body",
@ -17,7 +17,7 @@
"error": "(?:(?:(?<![a-zA-Z]))(?:(?i)error(?:s)?)(?:(?![a-zA-Z]))|failed|failure)", "error": "(?:(?:(?<![a-zA-Z]))(?:(?i)error(?:s)?)(?:(?![a-zA-Z]))|failed|failure)",
"warning": "(?:(?:(?i)warn)|not responding|init: cannot execute)" "warning": "(?:(?:(?i)warn)|not responding|init: cannot execute)"
}, },
"opid-field": "log_pid", "opid-field": "log_syslog_tag",
"multiline": true, "multiline": true,
"module-field": "log_procname", "module-field": "log_procname",
"value": { "value": {
@ -50,6 +50,11 @@
], ],
"description": "The ID of the process that generated the message" "description": "The ID of the process that generated the message"
}, },
"log_syslog_tag": {
"kind": "string",
"identifier": true,
"description": "The combination of the procname and pid"
},
"log_msgid": { "log_msgid": {
"kind": "string", "kind": "string",
"identifier": true "identifier": true

@ -45,7 +45,6 @@
#include "lnav_util.hh" #include "lnav_util.hh"
#include "log_data_helper.hh" #include "log_data_helper.hh"
#include "plain_text_source.hh" #include "plain_text_source.hh"
#include "pretty_printer.hh"
#include "readline_highlighters.hh" #include "readline_highlighters.hh"
#include "shlex.hh" #include "shlex.hh"
#include "sql_util.hh" #include "sql_util.hh"
@ -55,10 +54,7 @@
class logline_helper { class logline_helper {
public: public:
logline_helper(logfile_sub_source& lss) logline_helper(logfile_sub_source& lss) : lh_sub_source(lss) {}
: lh_sub_source(lss){
};
logline& move_to_msg_start() logline& move_to_msg_start()
{ {
@ -71,7 +67,7 @@ public:
} }
return (*lf)[cl]; return (*lf)[cl];
}; }
logline& current_line() logline& current_line()
{ {
@ -79,7 +75,7 @@ public:
std::shared_ptr<logfile> lf = this->lh_sub_source.find(cl); std::shared_ptr<logfile> lf = this->lh_sub_source.find(cl);
return (*lf)[cl]; return (*lf)[cl];
}; }
void annotate() void annotate()
{ {
@ -95,7 +91,7 @@ public:
this->lh_string_attrs, this->lh_string_attrs,
this->lh_line_values, this->lh_line_values,
false); false);
}; }
std::string to_string(const struct line_range& lr) const std::string to_string(const struct line_range& lr) const
{ {

@ -0,0 +1,133 @@
/**
* 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_itertools_similar_hh
#define lnav_itertools_similar_hh
#include <queue>
#include <string>
#include "base/itertools.hh"
#include "fts_fuzzy_match.hh"
namespace lnav {
namespace itertools {
namespace details {
template<typename F>
struct similar_to {
nonstd::optional<F> st_mapper;
std::string st_pattern;
size_t st_count{5};
};
struct identity {
template<typename U>
constexpr auto operator()(U&& v) const noexcept
-> decltype(std::forward<U>(v))
{
return std::forward<U>(v);
}
};
} // namespace details
template<typename F>
inline details::similar_to<F>
similar_to(F mapper, std::string pattern, size_t count = 5)
{
return lnav::itertools::details::similar_to<F>{
mapper, std::move(pattern), count};
}
inline auto
similar_to(std::string pattern, size_t count = 5)
{
return similar_to(details::identity{}, std::move(pattern), count);
}
} // namespace itertools
} // namespace lnav
template<typename T, typename F>
std::vector<typename T::value_type>
operator|(const T& in, const lnav::itertools::details::similar_to<F>& st)
{
using score_pair = std::pair<int, typename T::value_type>;
struct score_cmp {
bool operator()(const score_pair& lhs, const score_pair& rhs)
{
return lhs.first > rhs.first;
}
};
std::vector<std::remove_const_t<typename T::value_type>> retval;
if (st.st_pattern.empty()) {
retval.insert(retval.begin(), in.begin(), in.end());
if (retval.size() > st.st_count) {
retval.resize(st.st_count);
}
return retval;
}
std::priority_queue<score_pair, std::vector<score_pair>, score_cmp> pq;
for (const auto& elem : in) {
int score = 0;
if (!fts::fuzzy_match(
st.st_pattern.c_str(),
lnav::func::invoke(st.st_mapper.value(), elem).c_str(),
score))
{
continue;
}
if (score <= 0) {
continue;
}
pq.push(std::make_pair(score, elem));
if (pq.size() > st.st_count) {
pq.pop();
}
}
while (!pq.empty()) {
retval.template emplace_back(pq.top().second);
pq.pop();
}
std::reverse(retval.begin(), retval.end());
return retval;
}
#endif

@ -126,6 +126,9 @@ public:
listview_curses(); listview_curses();
listview_curses(const listview_curses&) = delete;
listview_curses(listview_curses&) = delete;
void set_title(const std::string& title) { this->lv_title = title; } void set_title(const std::string& title) { this->lv_title = title; }
const std::string& get_title() const { return this->lv_title; } const std::string& get_title() const { return this->lv_title; }
@ -506,7 +509,7 @@ public:
this->lv_tail_space = space; this->lv_tail_space = space;
return *this; return *this;
}; }
void log_state() void log_state()
{ {

@ -89,6 +89,7 @@
#include "bookmarks.hh" #include "bookmarks.hh"
#include "bottom_status_source.hh" #include "bottom_status_source.hh"
#include "bound_tags.hh" #include "bound_tags.hh"
#include "breadcrumb_curses.hh"
#include "CLI/CLI.hpp" #include "CLI/CLI.hpp"
#include "dump_internals.hh" #include "dump_internals.hh"
#include "environ_vtab.hh" #include "environ_vtab.hh"
@ -126,6 +127,7 @@
#include "textfile_highlighters.hh" #include "textfile_highlighters.hh"
#include "textview_curses.hh" #include "textview_curses.hh"
#include "top_status_source.hh" #include "top_status_source.hh"
#include "view_helpers.crumbs.hh"
#include "view_helpers.examples.hh" #include "view_helpers.examples.hh"
#include "view_helpers.hist.hh" #include "view_helpers.hist.hh"
#include "views_vtab.hh" #include "views_vtab.hh"
@ -275,6 +277,8 @@ force_linking(services::main_t anno)
} }
} // namespace injector } // namespace injector
static breadcrumb_curses breadcrumb_view;
bool bool
setup_logline_table(exec_context& ec) setup_logline_table(exec_context& ec)
{ {
@ -835,8 +839,22 @@ handle_key(int ch)
default: { default: {
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case ln_mode_t::PAGING: case ln_mode_t::PAGING:
if (ch == KEY_ENTER || ch == '\n' || ch == '\r') {
breadcrumb_view.focus();
lnav_data.ld_mode = ln_mode_t::BREADCRUMBS;
return true;
}
return handle_paging_key(ch); return handle_paging_key(ch);
case ln_mode_t::BREADCRUMBS:
if (!breadcrumb_view.handle_key(ch)) {
lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_view_stack.set_needs_update();
return true;
}
return true;
case ln_mode_t::FILTER: case ln_mode_t::FILTER:
case ln_mode_t::FILES: case ln_mode_t::FILES:
return handle_config_ui_key(ch); return handle_config_ui_key(ch);
@ -1048,6 +1066,15 @@ looper()
view_colors& vc = view_colors::singleton(); view_colors& vc = view_colors::singleton();
view_colors::init(); view_colors::init();
auto ecb_guard
= lnav_data.ld_exec_context.add_error_callback([](const auto& um) {
lnav_data.ld_user_message_source.replace_with(
um.to_attr_line().rtrim());
lnav_data.ld_user_message_view.reload_data();
lnav_data.ld_user_message_expiration
= std::chrono::steady_clock::now() + 20s;
});
{ {
setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights()); setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights());
setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights()); setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
@ -1092,10 +1119,6 @@ looper()
lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]); lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]);
sb.push_back(clear_last_user_mark); sb.push_back(clear_last_user_mark);
sb.push_back(bind_mem(&top_status_source::update_filename,
&lnav_data.ld_top_source));
vsb.push_back(bind_mem(&top_status_source::update_view_name,
&lnav_data.ld_top_source));
sb.push_back(bind_mem(&bottom_status_source::update_line_number, sb.push_back(bind_mem(&bottom_status_source::update_line_number,
&lnav_data.ld_bottom_source)); &lnav_data.ld_bottom_source));
sb.push_back(bind_mem(&bottom_status_source::update_percent, sb.push_back(bind_mem(&bottom_status_source::update_percent,
@ -1112,6 +1135,9 @@ looper()
vsb.push_back(sb); vsb.push_back(sb);
breadcrumb_view.set_y(0);
breadcrumb_view.set_window(lnav_data.ld_window);
breadcrumb_view.set_line_source(lnav_crumb_source);
for (lpc = 0; lpc < LNV__MAX; lpc++) { for (lpc = 0; lpc < LNV__MAX; lpc++) {
lnav_data.ld_views[lpc].set_window(lnav_data.ld_window); lnav_data.ld_views[lpc].set_window(lnav_data.ld_window);
lnav_data.ld_views[lpc].set_y(1); lnav_data.ld_views[lpc].set_y(1);
@ -1152,12 +1178,10 @@ looper()
lnav_data.ld_user_message_view.set_window(lnav_data.ld_window); lnav_data.ld_user_message_view.set_window(lnav_data.ld_window);
lnav_data.ld_status[LNS_TOP].set_top(0);
lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1)); lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
for (auto& sc : lnav_data.ld_status) { for (auto& sc : lnav_data.ld_status) {
sc.set_window(lnav_data.ld_window); sc.set_window(lnav_data.ld_window);
} }
lnav_data.ld_status[LNS_TOP].set_data_source(&lnav_data.ld_top_source);
lnav_data.ld_status[LNS_BOTTOM].set_data_source( lnav_data.ld_status[LNS_BOTTOM].set_data_source(
&lnav_data.ld_bottom_source); &lnav_data.ld_bottom_source);
lnav_data.ld_status[LNS_FILTER].set_data_source( lnav_data.ld_status[LNS_FILTER].set_data_source(
@ -1256,7 +1280,6 @@ looper()
gettimeofday(&current_time, nullptr); gettimeofday(&current_time, nullptr);
lnav_data.ld_top_source.update_time(current_time);
lnav_data.ld_preview_view.set_needs_update(); lnav_data.ld_preview_view.set_needs_update();
layout_views(); layout_views();
@ -1349,6 +1372,11 @@ looper()
lnav_data.ld_files_view.set_overlay_needs_update(); lnav_data.ld_files_view.set_overlay_needs_update();
} }
if (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS
&& breadcrumb_view.get_needs_update())
{
lnav_data.ld_view_stack.set_needs_update();
}
lnav_data.ld_view_stack.do_update(); lnav_data.ld_view_stack.do_update();
lnav_data.ld_doc_view.do_update(); lnav_data.ld_doc_view.do_update();
lnav_data.ld_example_view.do_update(); lnav_data.ld_example_view.do_update();
@ -1378,6 +1406,7 @@ looper()
default: default:
break; break;
} }
breadcrumb_view.do_update();
if (lnav_data.ld_mode != ln_mode_t::FILTER if (lnav_data.ld_mode != ln_mode_t::FILTER
&& lnav_data.ld_mode != ln_mode_t::FILES) && lnav_data.ld_mode != ln_mode_t::FILES)
{ {
@ -1457,6 +1486,8 @@ looper()
int ch; int ch;
while ((ch = getch()) != ERR) { while ((ch = getch()) != ERR) {
lnav_data.ld_user_message_source.clear();
alerter::singleton().new_input(ch); alerter::singleton().new_input(ch);
lnav_data.ld_input_dispatcher.new_input(current_time, lnav_data.ld_input_dispatcher.new_input(current_time,
@ -1467,8 +1498,6 @@ looper()
ch, tc->get_top()); ch, tc->get_top());
}; };
lnav_data.ld_user_message_source.clear();
if (!lnav_data.ld_looping) { if (!lnav_data.ld_looping) {
// No reason to keep processing input after the // No reason to keep processing input after the
// user has quit. The view stack will also be // user has quit. The view stack will also be
@ -1484,6 +1513,7 @@ looper()
case ln_mode_t::FILES: case ln_mode_t::FILES:
next_rescan_time = next_status_update_time + 1s; next_rescan_time = next_status_update_time + 1s;
break; break;
case ln_mode_t::BREADCRUMBS:
case ln_mode_t::COMMAND: case ln_mode_t::COMMAND:
case ln_mode_t::SEARCH: case ln_mode_t::SEARCH:
case ln_mode_t::SEARCH_FILTERS: case ln_mode_t::SEARCH_FILTERS:
@ -2227,6 +2257,7 @@ main(int argc, char* argv[])
lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>( lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>(
lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source); lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source);
lnav_data.ld_log_source.set_exec_context(&lnav_data.ld_exec_context);
lnav_data.ld_views[LNV_HELP] lnav_data.ld_views[LNV_HELP]
.set_sub_source(&lnav_data.ld_help_source) .set_sub_source(&lnav_data.ld_help_source)
.set_word_wrap(true); .set_word_wrap(true);

@ -72,7 +72,6 @@
#include "sql_util.hh" #include "sql_util.hh"
#include "statusview_curses.hh" #include "statusview_curses.hh"
#include "textfile_sub_source.hh" #include "textfile_sub_source.hh"
#include "top_status_source.hh"
#include "view_helpers.hh" #include "view_helpers.hh"
enum { enum {
@ -92,7 +91,6 @@ extern const char* lnav_zoom_strings[];
/** The status bars. */ /** The status bars. */
typedef enum { typedef enum {
LNS_TOP,
LNS_BOTTOM, LNS_BOTTOM,
LNS_FILTER, LNS_FILTER,
LNS_FILTER_HELP, LNS_FILTER_HELP,
@ -195,7 +193,6 @@ struct lnav_data_t {
ln_mode_t ld_last_config_mode{ln_mode_t::FILTER}; ln_mode_t ld_last_config_mode{ln_mode_t::FILTER};
statusview_curses ld_status[LNS__MAX]; statusview_curses ld_status[LNS__MAX];
top_status_source ld_top_source;
bottom_status_source ld_bottom_source; bottom_status_source ld_bottom_source;
filter_status_source ld_filter_status_source; filter_status_source ld_filter_status_source;
filter_help_status_source ld_filter_help_status_source; filter_help_status_source ld_filter_help_status_source;

@ -80,7 +80,6 @@ do_observer_update(const std::shared_ptr<logfile>& lf)
if (isendwin()) { if (isendwin()) {
return; return;
} }
lnav_data.ld_top_source.update_time();
for (auto& sc : lnav_data.ld_status) { for (auto& sc : lnav_data.ld_status) {
sc.do_update(); sc.do_update();
} }

@ -35,7 +35,7 @@
#include "base/result.h" #include "base/result.h"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "fmt/format.h" #include "fmt/format.h"
#include "fts_fuzzy_match.hh" #include "itertools.similar.hh"
#include "log_format.hh" #include "log_format.hh"
#include "log_format_ext.hh" #include "log_format_ext.hh"
#include "mapbox/variant.hpp" #include "mapbox/variant.hpp"
@ -44,69 +44,6 @@
using namespace lnav::roles::literals; using namespace lnav::roles::literals;
namespace lnav {
namespace itertools {
namespace details {
struct similar_to {
std::string st_pattern;
size_t st_count;
};
} // namespace details
details::similar_to
similar_to(std::string pattern, size_t count = 5)
{
return lnav::itertools::details::similar_to{std::move(pattern), count};
}
} // namespace itertools
} // namespace lnav
template<typename T>
std::vector<typename T::value_type>
operator|(const T& in, const lnav::itertools::details::similar_to& st)
{
using score_pair = std::pair<int, typename T::value_type>;
struct score_cmp {
bool operator()(const score_pair& lhs, const score_pair& rhs)
{
return lhs.first > rhs.first;
}
};
std::priority_queue<score_pair, std::vector<score_pair>, score_cmp> pq;
for (const auto& elem : in) {
int score = 0;
if (!fts::fuzzy_match(st.st_pattern.c_str(), elem.c_str(), score)) {
continue;
}
if (score <= 0) {
continue;
}
pq.push(std::make_pair(score, elem));
if (pq.size() > st.st_count) {
pq.pop();
}
}
std::vector<std::remove_const_t<typename T::value_type>> retval;
while (!pq.empty()) {
retval.template emplace_back(pq.top().second);
pq.pop();
}
std::reverse(retval.begin(), retval.end());
return retval;
}
namespace lnav { namespace lnav {
namespace management { namespace management {

@ -423,9 +423,13 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
dst_vl = vis_line_t(line_number); dst_vl = vis_line_t(line_number);
} else { } else {
return ec.make_error( auto um = lnav::console::user_message::error(
"expecting line number/percentage, timestamp, or relative " attr_line_t("invalid argument: ").append(args[1]))
"time"); .with_reason(
"expecting line number/percentage, timestamp, or "
"relative time");
ec.add_error_context(um);
return Err(um);
} }
dst_vl | [&ec, tc, &retval](auto new_top) { dst_vl | [&ec, tc, &retval](auto new_top) {

@ -647,6 +647,13 @@ static const struct json_path_container theme_styles_handlers = {
return &root->lt_style_list_glyph; return &root->lt_style_list_glyph;
}) })
.with_children(style_config_handlers), .with_children(style_config_handlers),
yajlpp::property_handler("breadcrumb")
.with_description("Styling for the separator between breadcrumbs")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_breadcrumb;
})
.with_children(style_config_handlers),
}; };
static const struct json_path_container theme_syntax_styles_handlers = { static const struct json_path_container theme_syntax_styles_handlers = {
@ -863,7 +870,7 @@ static const struct json_path_container highlighter_handlers = {
}; };
static const struct json_path_container theme_highlights_handlers = { static const struct json_path_container theme_highlights_handlers = {
yajlpp::pattern_property_handler("(?<highlight_name>\\w+)") yajlpp::pattern_property_handler("(?<highlight_name>[\\w\\-]+)")
.with_obj_provider<highlighter_config, lnav_theme>( .with_obj_provider<highlighter_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) { [](const yajlpp_provider_context& ypc, lnav_theme* root) {
highlighter_config& hc highlighter_config& hc

@ -118,7 +118,7 @@ pollfd_revents(const std::vector<struct pollfd>& pollfds, int fd)
return pollfds | lnav::itertools::find_if([fd](const auto& entry) { return pollfds | lnav::itertools::find_if([fd](const auto& entry) {
return entry.fd == fd; return entry.fd == fd;
}) })
| lnav::itertools::map(&pollfd::revents) | lnav::itertools::deref() | lnav::itertools::map(&pollfd::revents)
| lnav::itertools::unwrap_or((short) 0); | lnav::itertools::unwrap_or((short) 0);
} }
@ -183,7 +183,8 @@ to_json(yajlpp_gen& gen, const attr_line_t& al)
[&](const std::shared_ptr<logfile>& lf) { [&](const std::shared_ptr<logfile>& lf) {
elem_map.gen(""); elem_map.gen("");
}, },
[&](const bookmark_metadata* bm) { elem_map.gen(""); }); [&](const bookmark_metadata* bm) { elem_map.gen(""); },
[&](const timespec& ts) { elem_map.gen(""); });
} }
} }
} }

@ -834,7 +834,17 @@ external_log_format::scan(logfile& lf,
this->check_for_new_year(dst, log_time_tm, log_tv); this->check_for_new_year(dst, log_time_tm, log_tv);
} }
if (opid_cap != nullptr) { if (opid_cap != nullptr && !opid_cap->empty()) {
auto opid_str = pi.get_substr(opid_cap);
{
safe::WriteAccess<logfile::safe_opid_map> writable_opid_map(
lf.get_opids());
auto opid_iter = writable_opid_map->find(opid_str);
if (opid_iter == writable_opid_map->end()) {
(*writable_opid_map)[opid_str] = log_tv;
}
}
opid = hash_str(pi.get_substr_start(opid_cap), opid_cap->length()); opid = hash_str(pi.get_substr_start(opid_cap), opid_cap->length());
} }

@ -143,7 +143,7 @@ public:
logline_value(logline_value_meta lvm) : lv_meta(std::move(lvm)) logline_value(logline_value_meta lvm) : lv_meta(std::move(lvm))
{ {
this->lv_meta.lvm_kind = value_kind_t::VALUE_NULL; this->lv_meta.lvm_kind = value_kind_t::VALUE_NULL;
}; }
logline_value(logline_value_meta lvm, bool b) logline_value(logline_value_meta lvm, bool b)
: lv_meta(std::move(lvm)), lv_value((int64_t) (b ? 1 : 0)) : lv_meta(std::move(lvm)), lv_value((int64_t) (b ? 1 : 0))
{ {
@ -153,18 +153,20 @@ public:
: lv_meta(std::move(lvm)), lv_value(i) : lv_meta(std::move(lvm)), lv_value(i)
{ {
this->lv_meta.lvm_kind = value_kind_t::VALUE_INTEGER; this->lv_meta.lvm_kind = value_kind_t::VALUE_INTEGER;
}; }
logline_value(logline_value_meta lvm, double i) logline_value(logline_value_meta lvm, double i)
: lv_meta(std::move(lvm)), lv_value(i) : lv_meta(std::move(lvm)), lv_value(i)
{ {
this->lv_meta.lvm_kind = value_kind_t::VALUE_FLOAT; this->lv_meta.lvm_kind = value_kind_t::VALUE_FLOAT;
}; }
logline_value(logline_value_meta lvm, shared_buffer_ref& sbr) logline_value(logline_value_meta lvm, shared_buffer_ref& sbr)
: lv_meta(std::move(lvm)), lv_sbr(sbr){}; : lv_meta(std::move(lvm)), lv_sbr(sbr)
{
}
logline_value(logline_value_meta lvm, const intern_string_t val) logline_value(logline_value_meta lvm, const intern_string_t val)
: lv_meta(std::move(lvm)), lv_intern_string(val){ : lv_meta(std::move(lvm)), lv_intern_string(val)
{
}; }
logline_value(logline_value_meta lvm, logline_value(logline_value_meta lvm,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
struct line_range origin); struct line_range origin);
@ -196,7 +198,7 @@ public:
return this->lv_intern_string.get(); return this->lv_intern_string.get();
} }
return this->lv_sbr.get_data(); return this->lv_sbr.get_data();
}; }
size_t text_length() const size_t text_length() const
{ {
@ -387,7 +389,9 @@ public:
virtual void get_subline(const logline& ll, virtual void get_subline(const logline& ll,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
bool full_message = false){}; bool full_message = false)
{
}
virtual const std::vector<std::string>* get_actions( virtual const std::vector<std::string>* get_actions(
const logline_value& lv) const const logline_value& lv) const

@ -34,6 +34,7 @@
#include <sys/types.h> #include <sys/types.h>
#include "base/file_range.hh"
#include "base/string_attr_type.hh" #include "base/string_attr_type.hh"
#include "byte_array.hh" #include "byte_array.hh"
#include "log_level.hh" #include "log_level.hh"

@ -127,7 +127,7 @@ class generic_log_format : public log_format {
}; };
return log_fmt; return log_fmt;
}; }
std::string get_pattern_regex(uint64_t line_number) const override std::string get_pattern_regex(uint64_t line_number) const override
{ {
@ -138,7 +138,7 @@ class generic_log_format : public log_format {
const intern_string_t get_name() const override const intern_string_t get_name() const override
{ {
return intern_string::lookup("generic_log"); return intern_string::lookup("generic_log");
}; }
void scrub(std::string& line) override void scrub(std::string& line) override
{ {
@ -155,7 +155,7 @@ class generic_log_format : public log_format {
line = new_line; line = new_line;
} }
}; }
scan_result_t scan(logfile& lf, scan_result_t scan(logfile& lf,
std::vector<logline>& dst, std::vector<logline>& dst,
@ -194,7 +194,7 @@ class generic_log_format : public log_format {
} }
return SCAN_NO_MATCH; return SCAN_NO_MATCH;
}; }
void annotate(uint64_t line_number, void annotate(uint64_t line_number,
shared_buffer_ref& line, shared_buffer_ref& line,
@ -232,12 +232,12 @@ class generic_log_format : public log_format {
lr.lr_start = prefix_len; lr.lr_start = prefix_len;
lr.lr_end = line.length(); lr.lr_end = line.length();
sa.emplace_back(lr, SA_BODY.value()); sa.emplace_back(lr, SA_BODY.value());
}; }
std::shared_ptr<log_format> specialized(int fmt_lock) override std::shared_ptr<log_format> specialized(int fmt_lock) override
{ {
return std::make_shared<generic_log_format>(*this); return std::make_shared<generic_log_format>(*this);
}; }
}; };
std::string std::string
@ -320,7 +320,7 @@ struct separated_string {
: i_parent(ss), i_pos(pos), i_next_pos(pos), i_index(0) : i_parent(ss), i_pos(pos), i_next_pos(pos), i_index(0)
{ {
this->update(); this->update();
}; }
void update() void update()
{ {
@ -334,7 +334,7 @@ struct separated_string {
} else { } else {
this->i_next_pos = ss.ss_str + ss.ss_len; this->i_next_pos = ss.ss_str + ss.ss_len;
} }
}; }
iterator& operator++() iterator& operator++()
{ {
@ -343,7 +343,7 @@ struct separated_string {
this->i_index += 1; this->i_index += 1;
return *this; return *this;
}; }
string_fragment operator*() string_fragment operator*()
{ {
@ -356,23 +356,20 @@ struct separated_string {
end = this->i_next_pos - ss.ss_str; end = this->i_next_pos - ss.ss_str;
} }
return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end); return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
}; }
bool operator==(const iterator& other) const bool operator==(const iterator& other) const
{ {
return (&this->i_parent == &other.i_parent) return (&this->i_parent == &other.i_parent)
&& (this->i_pos == other.i_pos); && (this->i_pos == other.i_pos);
}; }
bool operator!=(const iterator& other) const bool operator!=(const iterator& other) const
{ {
return !(*this == other); return !(*this == other);
}; }
size_t index() const size_t index() const { return this->i_index; }
{
return this->i_index;
};
}; };
iterator begin() iterator begin()
@ -397,7 +394,9 @@ public:
int col, int col,
log_format* format) log_format* format)
: fd_meta(name, value_kind_t::VALUE_TEXT, col, format), : fd_meta(name, value_kind_t::VALUE_TEXT, col, format),
fd_numeric_index(-1){}; fd_numeric_index(-1)
{
}
field_def& with_kind(value_kind_t kind, field_def& with_kind(value_kind_t kind,
bool identifier = false, bool identifier = false,
@ -407,7 +406,7 @@ public:
this->fd_meta.lvm_identifier = identifier; this->fd_meta.lvm_identifier = identifier;
this->fd_collator = collator; this->fd_collator = collator;
return *this; return *this;
}; }
field_def& with_numeric_index(int index) field_def& with_numeric_index(int index)
{ {
@ -420,21 +419,21 @@ public:
{ {
this->lf_is_self_describing = true; this->lf_is_self_describing = true;
this->lf_time_ordered = false; this->lf_time_ordered = false;
}; }
const intern_string_t get_name() const override const intern_string_t get_name() const override
{ {
static const intern_string_t name(intern_string::lookup("bro")); static const intern_string_t name(intern_string::lookup("bro"));
return this->blf_format_name.empty() ? name : this->blf_format_name; return this->blf_format_name.empty() ? name : this->blf_format_name;
}; }
void clear() override void clear() override
{ {
this->log_format::clear(); this->log_format::clear();
this->blf_format_name.clear(); this->blf_format_name.clear();
this->blf_field_defs.clear(); this->blf_field_defs.clear();
}; }
scan_result_t scan_int(std::vector<logline>& dst, scan_result_t scan_int(std::vector<logline>& dst,
const line_info& li, const line_info& li,
@ -664,7 +663,7 @@ public:
this->lf_value_stats.clear(); this->lf_value_stats.clear();
return SCAN_NO_MATCH; return SCAN_NO_MATCH;
}; }
void annotate(uint64_t line_number, void annotate(uint64_t line_number,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
@ -707,7 +706,7 @@ public:
values.emplace_back(fd.fd_meta); values.emplace_back(fd.fd_meta);
} }
} }
}; }
const logline_value_stats* stats_for_value( const logline_value_stats* stats_for_value(
const intern_string_t& name) const override const intern_string_t& name) const override
@ -725,12 +724,12 @@ public:
} }
return retval; return retval;
}; }
std::shared_ptr<log_format> specialized(int fmt_lock = -1) override std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
{ {
return std::make_shared<bro_log_format>(*this); return std::make_shared<bro_log_format>(*this);
}; }
class bro_log_table : public log_format_vtab_impl { class bro_log_table : public log_format_vtab_impl {
public: public:
@ -753,7 +752,7 @@ public:
"", "",
type_pair.second); type_pair.second);
} }
}; }
void get_foreign_keys( void get_foreign_keys(
std::vector<std::string>& keys_inout) const override std::vector<std::string>& keys_inout) const override
@ -776,7 +775,7 @@ public:
static std::map<intern_string_t, std::shared_ptr<bro_log_table>> retval; static std::map<intern_string_t, std::shared_ptr<bro_log_table>> retval;
return retval; return retval;
}; }
std::shared_ptr<log_vtab_impl> get_vtab_impl() const override std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
{ {
@ -794,7 +793,7 @@ public:
} }
return retval; return retval;
}; }
void get_subline(const logline& ll, void get_subline(const logline& ll,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
@ -833,7 +832,7 @@ struct ws_separated_string {
: i_parent(ss), i_pos(pos), i_next_pos(pos) : i_parent(ss), i_pos(pos), i_next_pos(pos)
{ {
this->update(); this->update();
}; }
void update() void update()
{ {
@ -859,7 +858,7 @@ struct ws_separated_string {
this->i_next_pos += 1; this->i_next_pos += 1;
} }
} }
}; }
iterator& operator++() iterator& operator++()
{ {
@ -875,7 +874,7 @@ struct ws_separated_string {
this->i_index += 1; this->i_index += 1;
return *this; return *this;
}; }
string_fragment operator*() string_fragment operator*()
{ {
@ -883,34 +882,25 @@ struct ws_separated_string {
int end = this->i_next_pos - ss.ss_str; int end = this->i_next_pos - ss.ss_str;
return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end); return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
}; }
bool operator==(const iterator& other) const bool operator==(const iterator& other) const
{ {
return (&this->i_parent == &other.i_parent) return (&this->i_parent == &other.i_parent)
&& (this->i_pos == other.i_pos); && (this->i_pos == other.i_pos);
}; }
bool operator!=(const iterator& other) const bool operator!=(const iterator& other) const
{ {
return !(*this == other); return !(*this == other);
}; }
size_t index() const size_t index() const { return this->i_index; }
{
return this->i_index;
};
}; };
iterator begin() iterator begin() { return {*this, this->ss_str}; }
{
return {*this, this->ss_str};
};
iterator end() iterator end() { return {*this, this->ss_str + this->ss_len}; }
{
return {*this, this->ss_str + this->ss_len};
};
}; };
class w3c_log_format : public log_format { class w3c_log_format : public log_format {
@ -955,7 +945,7 @@ public:
this->fd_meta.lvm_identifier = identifier; this->fd_meta.lvm_identifier = identifier;
this->fd_collator = collator; this->fd_collator = collator;
return *this; return *this;
}; }
field_def& with_numeric_index(int index) field_def& with_numeric_index(int index)
{ {
@ -982,14 +972,14 @@ public:
{ {
this->lf_is_self_describing = true; this->lf_is_self_describing = true;
this->lf_time_ordered = false; this->lf_time_ordered = false;
}; }
const intern_string_t get_name() const override const intern_string_t get_name() const override
{ {
static const intern_string_t name(intern_string::lookup("w3c")); static const intern_string_t name(intern_string::lookup("w3c"));
return this->wlf_format_name.empty() ? name : this->wlf_format_name; return this->wlf_format_name.empty() ? name : this->wlf_format_name;
}; }
void clear() override void clear() override
{ {
@ -997,7 +987,7 @@ public:
this->wlf_time_scanner.clear(); this->wlf_time_scanner.clear();
this->wlf_format_name.clear(); this->wlf_format_name.clear();
this->wlf_field_defs.clear(); this->wlf_field_defs.clear();
}; }
scan_result_t scan_int(std::vector<logline>& dst, scan_result_t scan_int(std::vector<logline>& dst,
const line_info& li, const line_info& li,
@ -1255,7 +1245,7 @@ public:
this->lf_value_stats.clear(); this->lf_value_stats.clear();
return SCAN_NO_MATCH; return SCAN_NO_MATCH;
}; }
void annotate(uint64_t line_number, void annotate(uint64_t line_number,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
@ -1297,7 +1287,7 @@ public:
values.emplace_back(fd.fd_meta); values.emplace_back(fd.fd_meta);
} }
} }
}; }
const logline_value_stats* stats_for_value( const logline_value_stats* stats_for_value(
const intern_string_t& name) const override const intern_string_t& name) const override
@ -1315,12 +1305,12 @@ public:
} }
return retval; return retval;
}; }
std::shared_ptr<log_format> specialized(int fmt_lock = -1) override std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
{ {
return std::make_shared<w3c_log_format>(*this); return std::make_shared<w3c_log_format>(*this);
}; }
class w3c_log_table : public log_format_vtab_impl { class w3c_log_table : public log_format_vtab_impl {
public: public:
@ -1372,7 +1362,7 @@ public:
static std::map<intern_string_t, std::shared_ptr<w3c_log_table>> retval; static std::map<intern_string_t, std::shared_ptr<w3c_log_table>> retval;
return retval; return retval;
}; }
std::shared_ptr<log_vtab_impl> get_vtab_impl() const override std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
{ {
@ -1390,7 +1380,7 @@ public:
} }
return retval; return retval;
}; }
void get_subline(const logline& ll, void get_subline(const logline& ll,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
@ -1570,7 +1560,7 @@ public:
static const auto FIELDS = std::string("fields"); static const auto FIELDS = std::string("fields");
cols.emplace_back(FIELDS); cols.emplace_back(FIELDS);
}; }
}; };
std::shared_ptr<log_vtab_impl> get_vtab_impl() const override std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
@ -1769,7 +1759,7 @@ public:
std::shared_ptr<log_format> specialized(int fmt_lock) override std::shared_ptr<log_format> specialized(int fmt_lock) override
{ {
return std::make_shared<logfmt_format>(*this); return std::make_shared<logfmt_format>(*this);
}; }
}; };
static auto format_binder = injector::bind_multiple<log_format>() static auto format_binder = injector::bind_multiple<log_format>()

@ -63,8 +63,11 @@ static const char* LOG_COLUMNS = R"( (
static const char* LOG_FOOTER_COLUMNS = R"( static const char* LOG_FOOTER_COLUMNS = R"(
-- END Format-specific fields -- END Format-specific fields
log_opid TEXT HIDDEN, -- The message's OPID
log_format TEXT HIDDEN, -- The name of the log file format
log_time_msecs INTEGER HIDDEN, -- The adjusted timestamp for the log message as the number of milliseconds from the epoch log_time_msecs INTEGER HIDDEN, -- The adjusted timestamp for the log message as the number of milliseconds from the epoch
log_path TEXT HIDDEN COLLATE naturalnocase, -- The path to the log file this message is from log_path TEXT HIDDEN COLLATE naturalnocase, -- The path to the log file this message is from
log_unique_path TEXT HIDDEN COLLATE naturalnocase, -- The unique portion of the path this message is from
log_text TEXT HIDDEN, -- The full text of the log message log_text TEXT HIDDEN, -- The full text of the log message
log_body TEXT HIDDEN, -- The body of the log message log_body TEXT HIDDEN, -- The body of the log message
log_raw_text TEXT HIDDEN -- The raw text from the log file log_raw_text TEXT HIDDEN -- The raw text from the log file
@ -200,6 +203,10 @@ log_vtab_impl::is_valid(log_cursor& lc, logfile_sub_source& lss)
return false; return false;
} }
if (lc.lc_opid && lf_iter->get_opid() != lc.lc_opid.value().value) {
return false;
}
return true; return true;
} }
@ -300,6 +307,7 @@ vt_open(sqlite3_vtab* p_svt, sqlite3_vtab_cursor** pp_cursor)
*pp_cursor = (sqlite3_vtab_cursor*) p_cur; *pp_cursor = (sqlite3_vtab_cursor*) p_cur;
p_cur->base.pVtab = p_svt; p_cur->base.pVtab = p_svt;
p_cur->log_cursor.lc_opid = nonstd::nullopt;
p_cur->log_cursor.lc_curr_line = -1_vl; p_cur->log_cursor.lc_curr_line = -1_vl;
p_cur->log_cursor.lc_end_line = vis_line_t(p_vt->lss->text_line_count()); p_cur->log_cursor.lc_end_line = vis_line_t(p_vt->lss->text_line_count());
p_cur->log_cursor.lc_sub_index = 0; p_cur->log_cursor.lc_sub_index = 0;
@ -473,11 +481,13 @@ vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
sqlite3_result_text( sqlite3_result_text(
ctx, level_name, strlen(level_name), SQLITE_STATIC); ctx, level_name, strlen(level_name), SQLITE_STATIC);
} break; break;
}
case VT_COL_MARK: { case VT_COL_MARK: {
sqlite3_result_int(ctx, ll->is_marked()); sqlite3_result_int(ctx, ll->is_marked());
} break; break;
}
case VT_COL_LOG_COMMENT: { case VT_COL_LOG_COMMENT: {
const auto& bm = vt->lss->get_user_bookmark_metadata(); const auto& bm = vt->lss->get_user_bookmark_metadata();
@ -566,17 +576,54 @@ vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
switch (post_col_number) { switch (post_col_number) {
case 0: { case 0: {
sqlite3_result_int64(ctx, ll->get_time_in_millis()); if (vc->line_values.empty()) {
lf->read_full_message(ll, vc->log_msg);
vt->vi->extract(
lf, line_number, vc->log_msg, vc->line_values);
}
auto opid_opt = get_string_attr(vt->vi->vi_attrs,
logline::L_OPID);
if (opid_opt) {
auto opid_range
= opid_opt.value().saw_string_attr->sa_range;
to_sqlite(
ctx,
vc->log_msg.to_string_fragment(
opid_range.lr_start, opid_range.length()));
} else {
sqlite3_result_null(ctx);
}
break; break;
} }
case 1: { case 1: {
auto format_name = lf->get_format_name();
sqlite3_result_text(ctx,
format_name.get(),
format_name.size(),
SQLITE_STATIC);
break;
}
case 2: {
sqlite3_result_int64(ctx, ll->get_time_in_millis());
break;
}
case 3: {
const auto& fn = lf->get_filename(); const auto& fn = lf->get_filename();
sqlite3_result_text( sqlite3_result_text(
ctx, fn.c_str(), fn.length(), SQLITE_STATIC); ctx, fn.c_str(), fn.length(), SQLITE_STATIC);
break; break;
} }
case 2: { case 4: {
const auto& fn = lf->get_unique_path();
sqlite3_result_text(
ctx, fn.c_str(), fn.length(), SQLITE_STATIC);
break;
}
case 5: {
shared_buffer_ref line; shared_buffer_ref line;
lf->read_full_message(ll, line); lf->read_full_message(ll, line);
@ -586,7 +633,7 @@ vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
SQLITE_TRANSIENT); SQLITE_TRANSIENT);
break; break;
} }
case 3: { case 6: {
if (vc->line_values.empty()) { if (vc->line_values.empty()) {
lf->read_full_message(ll, vc->log_msg); lf->read_full_message(ll, vc->log_msg);
vt->vi->extract( vt->vi->extract(
@ -609,7 +656,7 @@ vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
} }
break; break;
} }
case 4: { case 7: {
auto read_res = lf->read_raw_message(ll); auto read_res = lf->read_raw_message(ll);
if (read_res.isErr()) { if (read_res.isErr()) {
@ -827,26 +874,31 @@ vt_rowid(sqlite3_vtab_cursor* cur, sqlite_int64* p_rowid)
} }
void void
log_cursor::update(unsigned char op, vis_line_t vl, bool exact) log_cursor::update(unsigned char op, vis_line_t vl, constraint_t cons)
{ {
if (vl < 0) { if (vl < 0) {
vl = -1_vl; vl = -1_vl;
} }
this->lc_opid = nonstd::nullopt;
switch (op) { switch (op) {
case SQLITE_INDEX_CONSTRAINT_EQ: case SQLITE_INDEX_CONSTRAINT_EQ:
if (vl < this->lc_end_line) { if (vl < this->lc_end_line) {
this->lc_curr_line = vl; this->lc_curr_line = vl;
this->lc_end_line = vis_line_t(this->lc_curr_line + 1); if (cons == constraint_t::unique) {
this->lc_end_line = vis_line_t(this->lc_curr_line + 1);
}
} }
break; break;
case SQLITE_INDEX_CONSTRAINT_GE: case SQLITE_INDEX_CONSTRAINT_GE:
this->lc_curr_line = vl; this->lc_curr_line = vl;
break; break;
case SQLITE_INDEX_CONSTRAINT_GT: case SQLITE_INDEX_CONSTRAINT_GT:
this->lc_curr_line = vis_line_t(vl + (exact ? 1 : 0)); this->lc_curr_line
= vis_line_t(vl + (cons == constraint_t::unique ? 1 : 0));
break; break;
case SQLITE_INDEX_CONSTRAINT_LE: case SQLITE_INDEX_CONSTRAINT_LE:
this->lc_end_line = vis_line_t(vl + (exact ? 1 : 0)); this->lc_end_line
= vis_line_t(vl + (cons == constraint_t::unique ? 1 : 0));
break; break;
case SQLITE_INDEX_CONSTRAINT_LT: case SQLITE_INDEX_CONSTRAINT_LT:
this->lc_end_line = vl; this->lc_end_line = vl;
@ -867,6 +919,7 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
= (sqlite3_index_info::sqlite3_index_constraint*) idxStr; = (sqlite3_index_info::sqlite3_index_constraint*) idxStr;
log_info("(%p) filter called: %d", vt, idxNum); log_info("(%p) filter called: %d", vt, idxNum);
p_cur->log_cursor.lc_opid = nonstd::nullopt;
p_cur->log_cursor.lc_curr_line = -1_vl; p_cur->log_cursor.lc_curr_line = -1_vl;
p_cur->log_cursor.lc_end_line = vis_line_t(vt->lss->text_line_count()); p_cur->log_cursor.lc_end_line = vis_line_t(vt->lss->text_line_count());
vt_next(p_vtc); vt_next(p_vtc);
@ -876,10 +929,13 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
} }
for (int lpc = 0; lpc < idxNum; lpc++) { for (int lpc = 0; lpc < idxNum; lpc++) {
switch (index[lpc].iColumn) { auto col = index[lpc].iColumn;
switch (col) {
case VT_COL_LINE_NUMBER: case VT_COL_LINE_NUMBER:
p_cur->log_cursor.update( p_cur->log_cursor.update(
index[lpc].op, vis_line_t(sqlite3_value_int64(argv[lpc]))); index[lpc].op,
vis_line_t(sqlite3_value_int64(argv[lpc])),
log_cursor::constraint_t::unique);
break; break;
case VT_COL_LOG_TIME: case VT_COL_LOG_TIME:
@ -892,7 +948,7 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
dts.scan((const char*) datestr, dts.scan((const char*) datestr,
strlen((const char*) datestr), strlen((const char*) datestr),
NULL, nullptr,
&mytm, &mytm,
tv); tv);
auto vl_opt = vt->lss->find_from_time(tv); auto vl_opt = vt->lss->find_from_time(tv);
@ -901,10 +957,75 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
= p_cur->log_cursor.lc_end_line; = p_cur->log_cursor.lc_end_line;
} else { } else {
p_cur->log_cursor.update( p_cur->log_cursor.update(
index[lpc].op, vl_opt.value(), false); index[lpc].op,
vl_opt.value(),
log_cursor::constraint_t::none);
}
}
break;
default: {
if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
int post_col_number
= col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1;
switch (post_col_number) {
case 0: {
if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
continue;
}
const auto* opid
= (const char*) sqlite3_value_text(argv[lpc]);
auto opid_len = sqlite3_value_bytes(argv[lpc]);
nonstd::optional<timeval> min_time;
for (const auto& file_data : *vt->lss) {
safe::ReadAccess<logfile::safe_opid_map>
r_opid_map(
file_data->get_file_ptr()->get_opids());
const auto& iter = r_opid_map->find(opid);
if (iter == r_opid_map->end()) {
continue;
}
if (!min_time
|| iter->second < min_time.value()) {
min_time = iter->second;
}
}
if (!min_time) {
log_debug("no min time");
p_cur->log_cursor.lc_curr_line
= p_cur->log_cursor.lc_end_line;
continue;
}
log_debug("found min time: %d.%06d",
min_time.value().tv_sec,
min_time.value().tv_usec);
auto vl_opt
= vt->lss->row_for_time(min_time.value());
if (!vl_opt) {
log_debug("time not found");
p_cur->log_cursor.lc_curr_line
= p_cur->log_cursor.lc_end_line;
continue;
}
log_debug("got row %d", (int) vl_opt.value());
p_cur->log_cursor.update(
index[lpc].op,
vl_opt.value(),
log_cursor::constraint_t::none);
log_cursor::opid_hash opid_val;
opid_val.value = hash_str(opid, opid_len);
p_cur->log_cursor.lc_opid = opid_val;
log_debug("filter opid %d",
p_cur->log_cursor.lc_opid.value().value);
break;
}
} }
} }
break; break;
}
} }
} }
@ -914,6 +1035,10 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
p_cur->log_cursor.lc_curr_line += 1_vl; p_cur->log_cursor.lc_curr_line += 1_vl;
} }
log_debug("cursor %d %d",
(int) p_cur->log_cursor.lc_curr_line,
(int) p_cur->log_cursor.lc_end_line);
return SQLITE_OK; return SQLITE_OK;
} }
@ -931,24 +1056,50 @@ vt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info)
} }
for (int lpc = 0; lpc < p_info->nConstraint; lpc++) { for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
if (!p_info->aConstraint[lpc].usable if (!p_info->aConstraint[lpc].usable
|| p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_MATCH) || p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_MATCH
|| p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_OFFSET
|| p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_LIMIT)
{ {
log_debug(" [%d] not usable", lpc);
continue; continue;
} }
switch (p_info->aConstraint[lpc].iColumn) { auto col = p_info->aConstraint[lpc].iColumn;
case VT_COL_LINE_NUMBER: switch (col) {
case VT_COL_LINE_NUMBER: {
log_debug("line number index %d", p_info->aConstraint[lpc].op);
argvInUse += 1; argvInUse += 1;
indexes.push_back(p_info->aConstraint[lpc]); indexes.push_back(p_info->aConstraint[lpc]);
p_info->aConstraintUsage[lpc].argvIndex = argvInUse; p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
break; break;
}
default: {
if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
int post_col_number
= col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1;
switch (post_col_number) {
case 0: {
log_debug("opid index");
argvInUse += 1;
indexes.push_back(p_info->aConstraint[lpc]);
p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
break;
}
}
}
break;
}
} }
} }
if (!argvInUse) { if (!argvInUse) {
log_debug("fall back to log_time");
for (int lpc = 0; lpc < p_info->nConstraint; lpc++) { for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
if (!p_info->aConstraint[lpc].usable if (!p_info->aConstraint[lpc].usable
|| p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_MATCH) || p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_MATCH
|| p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_OFFSET
|| p_info->aConstraint[lpc].op == SQLITE_INDEX_CONSTRAINT_LIMIT)
{ {
continue; continue;
} }

@ -57,21 +57,26 @@ enum {
class logfile_sub_source; class logfile_sub_source;
struct log_cursor { struct log_cursor {
struct opid_hash {
unsigned int value : 6;
};
vis_line_t lc_curr_line; vis_line_t lc_curr_line;
int lc_sub_index; int lc_sub_index;
vis_line_t lc_end_line; vis_line_t lc_end_line;
void update(unsigned char op, vis_line_t vl, bool exact = true); nonstd::optional<opid_hash> lc_opid;
void set_eof() enum class constraint_t {
{ none,
this->lc_curr_line = this->lc_end_line = vis_line_t(0); unique,
}; };
bool is_eof() const void update(unsigned char op, vis_line_t vl, constraint_t cons);
{
return this->lc_curr_line >= this->lc_end_line; void set_eof() { this->lc_curr_line = this->lc_end_line = vis_line_t(0); }
};
bool is_eof() const { return this->lc_curr_line >= this->lc_end_line; }
}; };
const std::string LOG_BODY = "log_body"; const std::string LOG_BODY = "log_body";

@ -34,6 +34,7 @@
#define logfile_hh #define logfile_hh
#include <string> #include <string>
#include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -358,10 +359,12 @@ public:
using note_map = std::map<note_type, std::string>; using note_map = std::map<note_type, std::string>;
using safe_notes = safe::Safe<note_map>; using safe_notes = safe::Safe<note_map>;
note_map get_notes() const note_map get_notes() const { return *this->lf_notes.readAccess(); }
{
return *this->lf_notes.readAccess(); using opid_map = std::unordered_map<std::string, timeval>;
} using safe_opid_map = safe::Safe<opid_map>;
safe_opid_map& get_opids() { return this->lf_opids; }
protected: protected:
/** /**
@ -407,6 +410,7 @@ private:
text_format_t lf_text_format{text_format_t::TF_UNKNOWN}; text_format_t lf_text_format{text_format_t::TF_UNKNOWN};
uint32_t lf_out_of_time_order_count{0}; uint32_t lf_out_of_time_order_count{0};
safe_notes lf_notes; safe_notes lf_notes;
safe_opid_map lf_opids;
nonstd::optional<std::pair<file_off_t, size_t>> lf_next_line_cache; nonstd::optional<std::pair<file_off_t, size_t>> lf_next_line_cache;
}; };

@ -1734,7 +1734,8 @@ logfile_sub_source::get_sql_filter()
{ {
return this->tss_filters | lnav::itertools::find_if([](const auto& filt) { return this->tss_filters | lnav::itertools::find_if([](const auto& filt) {
return filt->get_index() == 0; return filt->get_index() == 0;
}); })
| lnav::itertools::deref();
} }
void void
@ -1906,3 +1907,265 @@ logfile_sub_source::meta_grepper::grep_match(grep_proc<vis_line_t>& gp,
{ {
this->lmg_source.tss_view->grep_match(gp, line, start, end); this->lmg_source.tss_view->grep_match(gp, line, start, end);
} }
logline_window::iterator
logline_window::begin()
{
if (this->lw_start_line < 0_vl) {
return this->end();
}
return {this->lw_source, this->lw_start_line};
}
logline_window::iterator
logline_window::end()
{
return {this->lw_source, vis_line_t(this->lw_source.text_line_count())};
}
logline_window::logmsg_info::logmsg_info(logfile_sub_source& lss, vis_line_t vl)
: li_source(lss), li_line(vl)
{
if (this->li_line < this->li_source.text_line_count()) {
while (true) {
auto pair_opt = this->li_source.find_line_with_file(vl);
if (!pair_opt) {
break;
}
auto line_pair = pair_opt.value();
if (line_pair.second->is_message()) {
this->li_file = line_pair.first.get();
this->li_logline = line_pair.second;
break;
} else {
--vl;
}
}
}
}
void
logline_window::logmsg_info::next_msg()
{
this->li_file = nullptr;
this->li_logline = logfile::iterator{};
this->li_msg_buffer.disown();
this->li_string_attrs.clear();
this->li_line_values.clear();
++this->li_line;
while (this->li_line < this->li_source.text_line_count()) {
auto pair_opt = this->li_source.find_line_with_file(this->li_line);
if (!pair_opt) {
break;
}
auto line_pair = pair_opt.value();
if (line_pair.second->is_message()) {
this->li_file = line_pair.first.get();
this->li_logline = line_pair.second;
break;
} else {
++this->li_line;
}
}
}
void
logline_window::logmsg_info::load_msg() const
{
if (!this->li_string_attrs.empty()) {
return;
}
auto format = this->li_file->get_format();
this->li_file->read_full_message(this->li_logline, this->li_msg_buffer);
format->annotate(std::distance(this->li_file->cbegin(), this->li_logline),
this->li_msg_buffer,
this->li_string_attrs,
this->li_line_values,
false);
}
std::string
logline_window::logmsg_info::to_string(const struct line_range& lr) const
{
this->load_msg();
return this->li_msg_buffer.to_string_fragment(lr.lr_start, lr.length())
.to_string();
}
logline_window::iterator&
logline_window::iterator::operator++()
{
this->i_info.next_msg();
return *this;
}
static std::vector<breadcrumb::possibility>
timestamp_poss()
{
const static std::vector<breadcrumb::possibility> retval = {
breadcrumb::possibility{"-1 day"},
breadcrumb::possibility{"-1h"},
breadcrumb::possibility{"-30m"},
breadcrumb::possibility{"-15m"},
breadcrumb::possibility{"-5m"},
breadcrumb::possibility{"-1m"},
breadcrumb::possibility{"+1m"},
breadcrumb::possibility{"+5m"},
breadcrumb::possibility{"+15m"},
breadcrumb::possibility{"+30m"},
breadcrumb::possibility{"+1h"},
breadcrumb::possibility{"+1 day"},
};
return retval;
}
void
logfile_sub_source::text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs)
{
text_sub_source::text_crumbs_for_line(line, crumbs);
if (this->lss_filtered_index.empty()) {
return;
}
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)";
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)
WHERE name = 'log'
)";
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 = (SELECT log_line FROM all_logs WHERE log_unique_path = $uniq_path LIMIT 1)
WHERE name = 'log'
)";
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;
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);
}
}
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'
)";
ec->execute_with(
MOVE_STMT,
std::make_pair("opid",
opid.template get<std::string>()));
});
}
}
}

@ -79,7 +79,9 @@ public:
class pcre_filter : public text_filter { class pcre_filter : public text_filter {
public: public:
pcre_filter(type_t type, const std::string& id, size_t index, pcre* code) pcre_filter(type_t type, const std::string& id, size_t index, pcre* code)
: text_filter(type, filter_lang_t::REGEX, id, index), pf_pcre(code){}; : text_filter(type, filter_lang_t::REGEX, id, index), pf_pcre(code)
{
}
~pcre_filter() override = default; ~pcre_filter() override = default;
@ -150,6 +152,72 @@ private:
content_line_t llh_backing[MAX_SIZE]; content_line_t llh_backing[MAX_SIZE];
}; };
class logline_window {
public:
logline_window(logfile_sub_source& lss, vis_line_t start_line)
: lw_source(lss), lw_start_line(start_line)
{
}
class iterator;
class logmsg_info {
public:
logmsg_info(logfile_sub_source& lss, vis_line_t vl);
vis_line_t get_vis_line() const { return this->li_line; }
const logline& get_logline() const { return *this->li_logline; }
const string_attrs_t& get_attrs() const
{
this->load_msg();
return this->li_string_attrs;
}
std::string to_string(const struct line_range& lr) const;
private:
friend iterator;
void next_msg();
void load_msg() const;
logfile_sub_source& li_source;
vis_line_t li_line;
logfile* li_file{nullptr};
logfile::const_iterator li_logline;
mutable shared_buffer_ref li_msg_buffer;
mutable string_attrs_t li_string_attrs;
mutable std::vector<logline_value> li_line_values;
};
class iterator {
public:
iterator(logfile_sub_source& lss, vis_line_t vl) : i_info(lss, vl) {}
iterator& operator++();
bool operator!=(const iterator& rhs) const
{
return this->i_info.get_vis_line() != rhs.i_info.get_vis_line();
}
const logmsg_info& operator*() const { return this->i_info; }
private:
logmsg_info i_info;
};
iterator begin();
iterator end();
private:
logfile_sub_source& lw_source;
vis_line_t lw_start_line;
};
/** /**
* Delegate class that merges the contents of multiple log files into a single * Delegate class that merges the contents of multiple log files into a single
* source of data for a text view. * source of data for a text view.
@ -332,7 +400,7 @@ public:
const_iterator iter; const_iterator iter;
for (iter = this->cbegin(); iter != this->cend(); ++iter) { for (iter = this->cbegin(); iter != this->cend(); ++iter) {
if (*iter != NULL && (*iter)->get_file() != NULL) { if (*iter != nullptr && (*iter)->get_file() != nullptr) {
retval += 1; retval += 1;
} }
} }
@ -363,7 +431,7 @@ public:
this->lss_line_size_cache[index].first = row; this->lss_line_size_cache[index].first = row;
} }
return this->lss_line_size_cache[index].second; return this->lss_line_size_cache[index].second;
}; }
void text_mark(const bookmark_type_t* bm, vis_line_t line, bool added); void text_mark(const bookmark_type_t* bm, vis_line_t line, bool added);
@ -477,6 +545,30 @@ public:
return retval; return retval;
} }
nonstd::optional<std::pair<std::shared_ptr<logfile>, logfile::iterator>>
find_line_with_file(content_line_t line) const
{
std::shared_ptr<logfile> lf = this->find(line);
if (lf != nullptr) {
auto ll_iter = lf->begin() + line;
return std::make_pair(lf, ll_iter);
}
return nonstd::nullopt;
}
nonstd::optional<std::pair<std::shared_ptr<logfile>, logfile::iterator>>
find_line_with_file(vis_line_t vl) const
{
if (vl >= 0_vl && vl <= this->lss_filtered_index.size()) {
return this->find_line_with_file(this->at(vl));
}
return nonstd::nullopt;
}
nonstd::optional<vis_line_t> find_from_time( nonstd::optional<vis_line_t> find_from_time(
const struct timeval& start) const; const struct timeval& start) const;
@ -507,7 +599,7 @@ public:
return this->find_from_time(time_bucket); return this->find_from_time(time_bucket);
} }
content_line_t at(vis_line_t vl) content_line_t at(vis_line_t vl) const
{ {
return this->lss_index[this->lss_filtered_index[vl]]; return this->lss_index[this->lss_filtered_index[vl]];
} }
@ -521,6 +613,11 @@ public:
return this->at(vl); return this->at(vl);
} }
logline_window window_at(vis_line_t vl)
{
return logline_window(*this, vl);
}
log_accel::direction_t get_line_accel_direction(vis_line_t vl); log_accel::direction_t get_line_accel_direction(vis_line_t vl);
/** /**
@ -557,7 +654,7 @@ public:
bool is_visible() const bool is_visible() const
{ {
return this->ld_visible; return this->get_file_ptr() != nullptr && this->ld_visible;
} }
void set_visibility(bool vis) void set_visibility(bool vis)
@ -649,7 +746,7 @@ public:
: public grep_proc_source<vis_line_t> : public grep_proc_source<vis_line_t>
, public grep_proc_sink<vis_line_t> { , public grep_proc_sink<vis_line_t> {
public: public:
meta_grepper(logfile_sub_source& source) : lmg_source(source){}; meta_grepper(logfile_sub_source& source) : lmg_source(source) {}
bool grep_value_for_line(vis_line_t line, bool grep_value_for_line(vis_line_t line,
std::string& value_out) override; std::string& value_out) override;
@ -683,21 +780,19 @@ public:
return &this->lss_location_history; return &this->lss_location_history;
} }
void text_crumbs_for_line(int line, std::vector<breadcrumb::crumb>& crumbs);
Result<bool, std::string> eval_sql_filter(sqlite3_stmt* stmt, Result<bool, std::string> eval_sql_filter(sqlite3_stmt* stmt,
iterator ld, iterator ld,
logfile::const_iterator ll); logfile::const_iterator ll);
void invalidate_sql_filter(); void invalidate_sql_filter();
void set_line_meta_changed() void set_line_meta_changed() { this->lss_line_meta_changed = true; }
{
this->lss_line_meta_changed = true;
}
bool is_line_meta_changed() const bool is_line_meta_changed() const { return this->lss_line_meta_changed; }
{
return this->lss_line_meta_changed; void set_exec_context(exec_context* ec) { this->lss_exec_context = ec; }
}
static const uint64_t MAX_CONTENT_LINES = (1ULL << 40) - 1; static const uint64_t MAX_CONTENT_LINES = (1ULL << 40) - 1;
static const uint64_t MAX_LINES_PER_FILE = 256 * 1024 * 1024; static const uint64_t MAX_LINES_PER_FILE = 256 * 1024 * 1024;
@ -726,7 +821,7 @@ private:
}; };
struct __attribute__((__packed__)) indexed_content { struct __attribute__((__packed__)) indexed_content {
indexed_content() {} indexed_content() = default;
indexed_content(content_line_t cl) : ic_value(cl) {} indexed_content(content_line_t cl) : ic_value(cl) {}
@ -739,7 +834,8 @@ private:
}; };
struct logline_cmp { struct logline_cmp {
logline_cmp(logfile_sub_source& lc) : llss_controller(lc){}; logline_cmp(logfile_sub_source& lc) : llss_controller(lc) {}
bool operator()(const content_line_t& lhs, bool operator()(const content_line_t& lhs,
const content_line_t& rhs) const const content_line_t& rhs) const
{ {
@ -789,8 +885,10 @@ private:
}; };
struct filtered_logline_cmp { struct filtered_logline_cmp {
filtered_logline_cmp(const logfile_sub_source& lc) filtered_logline_cmp(const logfile_sub_source& lc) : llss_controller(lc)
: llss_controller(lc){}; {
}
bool operator()(const uint32_t& lhs, const uint32_t& rhs) const bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
{ {
content_line_t cl_lhs content_line_t cl_lhs
@ -820,7 +918,9 @@ private:
*/ */
struct logfile_data_eq { struct logfile_data_eq {
explicit logfile_data_eq(std::shared_ptr<logfile> lf) explicit logfile_data_eq(std::shared_ptr<logfile> lf)
: lde_file(std::move(lf)){}; : lde_file(std::move(lf))
{
}
bool operator()(const std::unique_ptr<logfile_data>& ld) const bool operator()(const std::unique_ptr<logfile_data>& ld) const
{ {
@ -834,7 +934,7 @@ private:
{ {
this->lss_line_size_cache.fill(std::make_pair(0, 0)); this->lss_line_size_cache.fill(std::make_pair(0, 0));
this->lss_line_size_cache[0].first = -1; this->lss_line_size_cache[0].first = -1;
}; }
bool check_extra_filters(iterator ld, logfile::iterator ll); bool check_extra_filters(iterator ld, logfile::iterator ll);
@ -877,6 +977,7 @@ private:
size_t lss_longest_line{0}; size_t lss_longest_line{0};
meta_grepper lss_meta_grepper; meta_grepper lss_meta_grepper;
log_location_history lss_location_history; log_location_history lss_location_history;
exec_context* lss_exec_context;
bool lss_in_value_for_line{false}; bool lss_in_value_for_line{false};
bool lss_line_meta_changed{false}; bool lss_line_meta_changed{false};

@ -0,0 +1,202 @@
/**
* 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 "plain_text_source.hh"
#include "base/itertools.hh"
#include "config.h"
static std::vector<plain_text_source::text_line>
to_text_line(const std::vector<attr_line_t>& lines)
{
file_off_t off = 0;
return lines | lnav::itertools::map([&off](const auto& elem) {
auto retval = plain_text_source::text_line{
off,
elem,
};
off += elem.length() + 1;
return retval;
});
}
plain_text_source::plain_text_source(const std::string& text)
{
size_t start = 0, end;
while ((end = text.find('\n', start)) != std::string::npos) {
size_t len = (end - start);
this->tds_lines.emplace_back(start, text.substr(start, len));
start = end + 1;
}
if (start < text.length()) {
this->tds_lines.emplace_back(start, text.substr(start));
}
this->tds_longest_line = this->compute_longest_line();
}
plain_text_source::plain_text_source(const std::vector<std::string>& text_lines)
{
this->replace_with(text_lines);
}
plain_text_source::plain_text_source(const std::vector<attr_line_t>& text_lines)
: tds_lines(to_text_line(text_lines))
{
this->tds_longest_line = this->compute_longest_line();
}
plain_text_source&
plain_text_source::replace_with(const attr_line_t& text_lines)
{
this->tds_lines.clear();
this->tds_lines = to_text_line(text_lines.split_lines());
this->tds_longest_line = this->compute_longest_line();
return *this;
}
plain_text_source&
plain_text_source::replace_with(const std::vector<std::string>& text_lines)
{
file_off_t off = 0;
for (const auto& str : text_lines) {
this->tds_lines.emplace_back(off, str);
off += str.length() + 1;
}
this->tds_longest_line = this->compute_longest_line();
return *this;
}
void
plain_text_source::clear()
{
this->tds_lines.clear();
this->tds_longest_line = 0;
this->tds_text_format = text_format_t::TF_UNKNOWN;
}
plain_text_source&
plain_text_source::truncate_to(size_t max_lines)
{
while (this->tds_lines.size() > max_lines) {
this->tds_lines.pop_back();
}
return *this;
}
size_t
plain_text_source::text_line_width(textview_curses& curses)
{
return this->tds_longest_line;
}
void
plain_text_source::text_value_for_line(textview_curses& tc,
int row,
std::string& value_out,
text_sub_source::line_flags_t flags)
{
value_out = this->tds_lines[row].tl_value.get_string();
}
void
plain_text_source::text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out)
{
value_out = this->tds_lines[line].tl_value.get_attrs();
if (this->tds_reverse_selection && tc.is_selectable()
&& tc.get_selection() == line)
{
value_out.emplace_back(line_range{0, -1}, VC_STYLE.value(A_REVERSE));
}
}
size_t
plain_text_source::text_size_for_line(textview_curses& tc,
int row,
text_sub_source::line_flags_t flags)
{
return this->tds_lines[row].tl_value.length();
}
text_format_t
plain_text_source::get_text_format() const
{
return this->tds_text_format;
}
size_t
plain_text_source::compute_longest_line()
{
size_t retval = 0;
for (auto& iter : this->tds_lines) {
retval = std::max(retval, (size_t) iter.tl_value.length());
}
return retval;
}
nonstd::optional<vis_line_t>
plain_text_source::line_for_offset(file_off_t off)
{
struct cmper {
bool operator()(const file_off_t& lhs, const text_line& rhs)
{
return lhs < rhs.tl_offset;
}
bool operator()(const text_line& lhs, const file_off_t& rhs)
{
return lhs.tl_offset < rhs;
}
};
if (this->tds_lines.empty()) {
return nonstd::nullopt;
}
auto iter = std::lower_bound(
this->tds_lines.begin(), this->tds_lines.end(), off, cmper{});
if (iter == this->tds_lines.end()) {
if (this->tds_lines.back().contains_offset(off)) {
return nonstd::make_optional(
vis_line_t(std::distance(this->tds_lines.end() - 1, iter)));
}
return nonstd::nullopt;
}
if (!iter->contains_offset(off) && iter != this->tds_lines.begin()) {
--iter;
}
return nonstd::make_optional(
vis_line_t(std::distance(this->tds_lines.begin(), iter)));
}

@ -34,116 +34,76 @@
#include <vector> #include <vector>
#include "base/attr_line.hh" #include "base/attr_line.hh"
#include "base/file_range.hh"
#include "textview_curses.hh" #include "textview_curses.hh"
class plain_text_source class plain_text_source
: public text_sub_source : public text_sub_source
, public vis_location_history { , public vis_location_history {
public: public:
plain_text_source() = default; struct text_line {
text_line(file_off_t off, attr_line_t value)
plain_text_source(const std::string& text) : tl_offset(off), tl_value(std::move(value))
{ {
size_t start = 0, end;
while ((end = text.find('\n', start)) != std::string::npos) {
size_t len = (end - start);
this->tds_lines.emplace_back(text.substr(start, len));
start = end + 1;
} }
if (start < text.length()) {
this->tds_lines.emplace_back(text.substr(start)); bool contains_offset(file_off_t off) const
{
return (this->tl_offset <= off
&& off < this->tl_offset + this->tl_value.length());
} }
this->tds_longest_line = this->compute_longest_line();
}
plain_text_source(const std::vector<std::string>& text_lines) file_off_t tl_offset;
{ attr_line_t tl_value;
this->replace_with(text_lines); };
}
plain_text_source(const std::vector<attr_line_t>& text_lines) plain_text_source() = default;
{
this->tds_lines = text_lines;
this->tds_longest_line = this->compute_longest_line();
}
plain_text_source& replace_with(const attr_line_t& text_lines) plain_text_source(const std::string& text);
{
this->tds_lines.clear();
text_lines.split_lines(this->tds_lines);
this->tds_longest_line = this->compute_longest_line();
return *this;
}
plain_text_source& replace_with(const std::vector<std::string>& text_lines) plain_text_source(const std::vector<std::string>& text_lines);
{
for (const auto& str : text_lines) {
this->tds_lines.emplace_back(str);
}
this->tds_longest_line = this->compute_longest_line();
return *this;
}
void clear() plain_text_source(const std::vector<attr_line_t>& text_lines);
{
this->tds_lines.clear();
this->tds_longest_line = 0;
this->tds_text_format = text_format_t::TF_UNKNOWN;
}
plain_text_source& truncate_to(size_t max_lines) plain_text_source& set_reverse_selection(bool val)
{ {
while (this->tds_lines.size() > max_lines) { this->tds_reverse_selection = val;
this->tds_lines.pop_back();
}
return *this; return *this;
} }
size_t text_line_count() plain_text_source& replace_with(const attr_line_t& text_lines);
{
return this->tds_lines.size(); plain_text_source& replace_with(const std::vector<std::string>& text_lines);
}
void clear();
plain_text_source& truncate_to(size_t max_lines);
size_t text_line_count() override { return this->tds_lines.size(); }
bool empty() const bool empty() const
{ {
return this->tds_lines.empty(); return this->tds_lines.empty();
} }
size_t text_line_width(textview_curses& curses) size_t text_line_width(textview_curses& curses) override;
{
return this->tds_longest_line;
}
void text_value_for_line(textview_curses& tc, void text_value_for_line(textview_curses& tc,
int row, int row,
std::string& value_out, std::string& value_out,
line_flags_t flags) line_flags_t flags) override;
{
value_out = this->tds_lines[row].get_string();
}
void text_attrs_for_line(textview_curses& tc, void text_attrs_for_line(textview_curses& tc,
int line, int line,
string_attrs_t& value_out) string_attrs_t& value_out) override;
{
value_out = this->tds_lines[line].get_attrs();
}
size_t text_size_for_line(textview_curses& tc, int row, line_flags_t flags) size_t text_size_for_line(textview_curses& tc,
{ int row,
return this->tds_lines[row].length(); line_flags_t flags) override;
}
text_format_t get_text_format() const text_format_t get_text_format() const override;
{
return this->tds_text_format;
}
const std::vector<attr_line_t>& get_lines() const const std::vector<text_line>& get_lines() const { return this->tds_lines; }
{
return this->tds_lines;
}
plain_text_source& set_text_format(text_format_t format) plain_text_source& set_text_format(text_format_t format)
{ {
@ -151,24 +111,20 @@ public:
return *this; return *this;
} }
nonstd::optional<location_history*> get_location_history() nonstd::optional<location_history*> get_location_history() override
{ {
return this; return this;
} }
private: protected:
size_t compute_longest_line() size_t compute_longest_line();
{
size_t retval = 0; nonstd::optional<vis_line_t> line_for_offset(file_off_t off);
for (auto& iter : this->tds_lines) {
retval = std::max(retval, (size_t) iter.length());
}
return retval;
};
std::vector<attr_line_t> tds_lines; std::vector<text_line> tds_lines;
text_format_t tds_text_format{text_format_t::TF_UNKNOWN}; text_format_t tds_text_format{text_format_t::TF_UNKNOWN};
size_t tds_longest_line{0}; size_t tds_longest_line{0};
bool tds_reverse_selection{false};
}; };
#endif // LNAV_PLAIN_TEXT_SOURCE_HH #endif // LNAV_PLAIN_TEXT_SOURCE_HH

@ -35,14 +35,26 @@
void void
pretty_printer::append_to(attr_line_t& al) pretty_printer::append_to(attr_line_t& al)
{ {
auto& pi = this->pp_scanner->get_input();
pcre_context_static<30> pc; pcre_context_static<30> pc;
data_token_t dt; data_token_t dt;
this->pp_scanner->reset(); this->pp_scanner->reset();
if (pi.pi_offset > 0) {
pcre_context::capture_t leading_cap = {
0,
static_cast<int>(pi.pi_offset),
};
// this->pp_stream << pi.get_substr(&leading_cap);
this->pp_values.emplace_back(DT_WORD, leading_cap);
}
while (this->pp_scanner->tokenize2(pc, dt)) { while (this->pp_scanner->tokenize2(pc, dt)) {
element el(dt, pc); element el(dt, pc);
switch (dt) { switch (dt) {
case DT_XML_DECL_TAG:
case DT_XML_EMPTY_TAG: case DT_XML_EMPTY_TAG:
if (this->pp_is_xml && this->pp_line_length > 0) { if (this->pp_is_xml && this->pp_line_length > 0) {
this->start_new_line(); this->start_new_line();
@ -56,6 +68,10 @@ pretty_printer::append_to(attr_line_t& al)
if (this->pp_is_xml) { if (this->pp_is_xml) {
this->start_new_line(); this->start_new_line();
this->write_element(el); this->write_element(el);
this->pp_interval_state.back().is_start
= this->pp_stream.tellp();
this->pp_interval_state.back().is_name
= pi.get_substr(&el.e_capture);
this->descend(); this->descend();
} else { } else {
this->pp_values.emplace_back(el); this->pp_values.emplace_back(el);
@ -64,6 +80,7 @@ pretty_printer::append_to(attr_line_t& al)
case DT_XML_CLOSE_TAG: case DT_XML_CLOSE_TAG:
this->flush_values(); this->flush_values();
this->ascend(); this->ascend();
this->append_child_node();
this->write_element(el); this->write_element(el);
this->start_new_line(); this->start_new_line();
continue; continue;
@ -73,6 +90,8 @@ pretty_printer::append_to(attr_line_t& al)
this->flush_values(true); this->flush_values(true);
this->pp_values.emplace_back(el); this->pp_values.emplace_back(el);
this->descend(); this->descend();
this->pp_interval_state.back().is_start
= this->pp_stream.tellp();
continue; continue;
case DT_RCURLY: case DT_RCURLY:
case DT_RSQUARE: case DT_RSQUARE:
@ -87,8 +106,11 @@ pretty_printer::append_to(attr_line_t& al)
case DT_COMMA: case DT_COMMA:
if (this->pp_depth > 0) { if (this->pp_depth > 0) {
this->flush_values(true); this->flush_values(true);
this->append_child_node();
this->write_element(el); this->write_element(el);
this->start_new_line(); this->start_new_line();
this->pp_interval_state.back().is_start
= this->pp_stream.tellp();
continue; continue;
} }
break; break;
@ -117,6 +139,21 @@ pretty_printer::append_to(attr_line_t& al)
al.append("\n"); al.append("\n");
} }
al.append(combined); al.append(combined);
if (this->pp_hier_stage != nullptr) {
this->pp_hier_stage->hn_parent = this->pp_hier_nodes.back().get();
this->pp_hier_nodes.back()->hn_children.push_back(
std::move(this->pp_hier_stage));
}
this->pp_hier_stage = std::move(this->pp_hier_nodes.back());
this->pp_hier_nodes.pop_back();
if (this->pp_hier_stage->hn_children.size() == 1
&& this->pp_hier_stage->hn_named_children.empty())
{
this->pp_hier_stage
= std::move(this->pp_hier_stage->hn_children.front());
this->pp_hier_stage->hn_parent = nullptr;
}
} }
void void
@ -142,7 +179,7 @@ pretty_printer::write_element(const pretty_printer::element& el)
} }
return; return;
} }
pcre_input& pi = this->pp_scanner->get_input(); auto& pi = this->pp_scanner->get_input();
if (this->pp_line_length == 0) { if (this->pp_line_length == 0) {
this->append_indent(); this->append_indent();
} }
@ -208,12 +245,36 @@ pretty_printer::append_indent()
bool bool
pretty_printer::flush_values(bool start_on_depth) pretty_printer::flush_values(bool start_on_depth)
{ {
nonstd::optional<pcre_context::capture_t> last_key;
auto& pi = this->pp_scanner->get_input();
bool retval = false; bool retval = false;
while (!this->pp_values.empty()) { while (!this->pp_values.empty()) {
{ {
element& el = this->pp_values.front(); auto& el = this->pp_values.front();
this->write_element(this->pp_values.front()); this->write_element(this->pp_values.front());
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->pp_interval_state.back().is_name
= pi.get_substr(&last_key.value());
if (!this->pp_interval_state.back().is_name.empty()) {
this->pp_interval_state.back().is_start
= static_cast<ssize_t>(this->pp_stream.tellp());
}
last_key = nonstd::nullopt;
}
break;
default:
break;
}
if (start_on_depth if (start_on_depth
&& (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY)) { && (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY)) {
if (this->pp_line_length > 0) { if (this->pp_line_length > 0) {
@ -253,6 +314,11 @@ pretty_printer::ascend()
this->pp_depth -= 1; this->pp_depth -= 1;
this->pp_body_lines.pop(); this->pp_body_lines.pop();
this->pp_body_lines.top() += lines; this->pp_body_lines.top() += lines;
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();
} else { } else {
this->pp_body_lines.top() = 0; this->pp_body_lines.top() = 0;
} }
@ -263,4 +329,78 @@ pretty_printer::descend()
{ {
this->pp_depth += 1; this->pp_depth += 1;
this->pp_body_lines.push(0); 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>());
}
void
pretty_printer::append_child_node()
{
auto& ivstate = this->pp_interval_state.back();
if (!ivstate.is_start) {
return;
}
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};
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>();
auto* retval = new_node.get();
new_node->hn_parent = top_node;
new_node->hn_start = this->pp_intervals.back().start;
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_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<const 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;
} }

@ -31,27 +31,62 @@
#define pretty_printer_hh #define pretty_printer_hh
#include <deque> #include <deque>
#include <map>
#include <sstream> #include <sstream>
#include <stack> #include <stack>
#include <utility> #include <utility>
#include <vector>
#include <sys/types.h> #include <sys/types.h>
#include "base/attr_line.hh" #include "base/attr_line.hh"
#include "base/file_range.hh"
#include "base/opt_util.hh"
#include "data_scanner.hh" #include "data_scanner.hh"
#include "intervaltree/IntervalTree.h"
class pretty_printer { class pretty_printer {
public: 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 { struct element {
element(data_token_t token, pcre_context& pc) element(data_token_t token, pcre_context& pc)
: e_token(token), e_capture(*pc.all()) : 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; data_token_t e_token;
pcre_context::capture_t e_capture; 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<const 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) pretty_printer(data_scanner* ds, string_attrs_t sa, int leading_indent = 0)
: pp_leading_indent(leading_indent), pp_scanner(ds), : pp_leading_indent(leading_indent), pp_scanner(ds),
pp_attrs(std::move(sa)) pp_attrs(std::move(sa))
@ -63,14 +98,28 @@ public:
this->pp_scanner->reset(); this->pp_scanner->reset();
while (this->pp_scanner->tokenize2(pc, dt)) { while (this->pp_scanner->tokenize2(pc, dt)) {
if (dt == DT_XML_CLOSE_TAG) { if (dt == DT_XML_CLOSE_TAG || dt == DT_XML_DECL_TAG) {
pp_is_xml = true; pp_is_xml = true;
break;
} }
} }
this->pp_interval_state.resize(1);
this->pp_hier_nodes.push_back(std::make_unique<hier_node>());
} }
void append_to(attr_line_t& al); void append_to(attr_line_t& al);
std::vector<pretty_interval> take_intervals()
{
return std::move(this->pp_intervals);
}
std::unique_ptr<hier_node> take_hier_root()
{
return std::move(this->pp_hier_stage);
}
private: private:
void descend(); void descend();
@ -84,6 +133,13 @@ private:
void write_element(const element& el); void write_element(const element& el);
void append_child_node();
struct interval_state {
nonstd::optional<file_off_t> is_start;
std::string is_name;
};
int pp_leading_indent; int pp_leading_indent;
int pp_depth{0}; int pp_depth{0};
int pp_line_length{0}; int pp_line_length{0};
@ -95,6 +151,10 @@ private:
std::deque<element> pp_values{}; std::deque<element> pp_values{};
int pp_shift_accum{0}; int pp_shift_accum{0};
bool pp_is_xml{false}; 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;
}; };
#endif #endif

@ -486,6 +486,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
return; return;
} }
case ln_mode_t::BREADCRUMBS:
case ln_mode_t::PAGING: case ln_mode_t::PAGING:
case ln_mode_t::FILTER: case ln_mode_t::FILTER:
case ln_mode_t::FILES: case ln_mode_t::FILES:
@ -578,6 +579,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
auto old_mode = std::exchange(lnav_data.ld_mode, new_mode); auto old_mode = std::exchange(lnav_data.ld_mode, new_mode);
switch (old_mode) { switch (old_mode) {
case ln_mode_t::BREADCRUMBS:
case ln_mode_t::PAGING: case ln_mode_t::PAGING:
case ln_mode_t::FILTER: case ln_mode_t::FILTER:
case ln_mode_t::FILES: case ln_mode_t::FILES:
@ -596,10 +598,6 @@ rl_callback_int(readline_curses* rc, bool is_alt)
lnav_data.ld_user_message_source.replace_with( lnav_data.ld_user_message_source.replace_with(
um.to_attr_line().rtrim()); um.to_attr_line().rtrim());
for (const auto& line :
lnav_data.ld_user_message_source.get_lines()) {
log_debug("line -- %s", lnav::to_json(line).c_str());
}
lnav_data.ld_user_message_view.reload_data(); lnav_data.ld_user_message_view.reload_data();
lnav_data.ld_user_message_expiration lnav_data.ld_user_message_expiration
= std::chrono::steady_clock::now() + 20s; = std::chrono::steady_clock::now() + 20s;

@ -194,14 +194,18 @@ add_view_text_possibilities(readline_curses* rlc,
rlc->clear_possibilities(context, type); rlc->clear_possibilities(context, type);
for (vis_line_t curr_line = tc->get_top(); curr_line <= tc->get_bottom(); if (tc->get_inner_height() > 0_vl) {
++curr_line) for (vis_line_t curr_line = tc->get_top();
{ curr_line <= tc->get_bottom();
std::string line; ++curr_line)
{
std::string line;
tss->text_value_for_line(*tc, curr_line, line, text_sub_source::RF_RAW); tss->text_value_for_line(
*tc, curr_line, line, text_sub_source::RF_RAW);
add_text_possibilities(rlc, context, type, line, tq); add_text_possibilities(rlc, context, type, line, tq);
}
} }
rlc->add_possibility(context, type, bookmark_metadata::KNOWN_TAGS); rlc->add_possibility(context, type, bookmark_metadata::KNOWN_TAGS);

@ -520,10 +520,14 @@ load_time_bookmarks()
} }
for (const char* upgrade_stmt : UPGRADE_STMTS) { for (const char* upgrade_stmt : UPGRADE_STMTS) {
if (sqlite3_exec(db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out()) auto rc = sqlite3_exec(
!= SQLITE_OK) db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out());
{ if (rc != SQLITE_OK) {
log_error("unable to upgrade bookmark table -- %s", errmsg.in()); auto exterr = sqlite3_extended_errcode(db.in());
log_error("unable to upgrade bookmark table -- (%d/%d) %s",
rc,
exterr,
errmsg.in());
} }
} }

@ -40,6 +40,7 @@
#include <sys/types.h> #include <sys/types.h>
#include "base/auto_mem.hh" #include "base/auto_mem.hh"
#include "base/intern_string.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
class shared_buffer; class shared_buffer;
@ -47,12 +48,11 @@ class shared_buffer;
struct shared_buffer_ref { struct shared_buffer_ref {
public: public:
shared_buffer_ref(char* data = nullptr, size_t len = 0) shared_buffer_ref(char* data = nullptr, size_t len = 0)
: sb_owner(nullptr), sb_data(data), sb_length(len){}; : sb_owner(nullptr), sb_data(data), sb_length(len)
~shared_buffer_ref()
{ {
this->disown(); }
};
~shared_buffer_ref() { this->disown(); }
shared_buffer_ref(const shared_buffer_ref& other) shared_buffer_ref(const shared_buffer_ref& other)
{ {
@ -61,7 +61,7 @@ public:
this->sb_length = 0; this->sb_length = 0;
this->copy_ref(other); this->copy_ref(other);
}; }
shared_buffer_ref(shared_buffer_ref&& other) noexcept; shared_buffer_ref(shared_buffer_ref&& other) noexcept;
@ -73,27 +73,21 @@ public:
} }
return *this; return *this;
}; }
bool empty() const bool empty() const
{ {
return this->sb_data == nullptr || this->sb_length == 0; return this->sb_data == nullptr || this->sb_length == 0;
}; }
const char* get_data() const const char* get_data() const { return this->sb_data; }
{
return this->sb_data;
};
const char* get_data_at(off_t offset) const const char* get_data_at(off_t offset) const
{ {
return &this->sb_data[offset]; return &this->sb_data[offset];
}; }
size_t length() const size_t length() const { return this->sb_length; }
{
return this->sb_length;
};
shared_buffer_ref& rtrim(bool pred(char)) shared_buffer_ref& rtrim(bool pred(char))
{ {
@ -110,7 +104,7 @@ public:
const char* buffer_end = this->sb_data + this->sb_length; const char* buffer_end = this->sb_data + this->sb_length;
return (this->sb_data <= ptr && ptr < buffer_end); return (this->sb_data <= ptr && ptr < buffer_end);
}; }
char* get_writable_data() char* get_writable_data()
{ {
@ -119,7 +113,13 @@ public:
} }
return nullptr; return nullptr;
}; }
string_fragment to_string_fragment(off_t offset, size_t len)
{
return string_fragment{
this->sb_data, (int) offset, (int) (offset + len)};
}
void share(shared_buffer& sb, char* data, size_t len); void share(shared_buffer& sb, char* data, size_t len);
@ -140,15 +140,9 @@ private:
class shared_buffer { class shared_buffer {
public: public:
~shared_buffer() ~shared_buffer() { this->invalidate_refs(); }
{
this->invalidate_refs();
}
void add_ref(shared_buffer_ref& ref) void add_ref(shared_buffer_ref& ref) { this->sb_refs.push_back(&ref); }
{
this->sb_refs.push_back(&ref);
};
bool invalidate_refs() bool invalidate_refs()
{ {
@ -161,7 +155,7 @@ public:
} }
return retval; return retval;
}; }
std::vector<shared_buffer_ref*> sb_refs; std::vector<shared_buffer_ref*> sb_refs;
}; };

@ -208,6 +208,7 @@ struct lnav_theme {
style_config lt_style_file; style_config lt_style_file;
style_config lt_style_header[6]; style_config lt_style_header[6];
style_config lt_style_list_glyph; style_config lt_style_list_glyph;
style_config lt_style_breadcrumb;
std::map<log_level_t, style_config> lt_level_styles; std::map<log_level_t, style_config> lt_level_styles;
std::map<std::string, highlighter_config> lt_highlights; std::map<std::string, highlighter_config> lt_highlights;
}; };

@ -29,6 +29,7 @@
#include "textfile_sub_source.hh" #include "textfile_sub_source.hh"
#include "base/itertools.hh"
#include "config.h" #include "config.h"
size_t size_t
@ -235,3 +236,42 @@ textfile_sub_source::get_text_format() const
return this->tss_files.front()->get_text_format(); return this->tss_files.front()->get_text_format();
} }
void
textfile_sub_source::text_crumbs_for_line(
int line, std::vector<breadcrumb::crumb>& crumbs)
{
text_sub_source::text_crumbs_for_line(line, crumbs);
if (this->empty()) {
return;
}
auto lf = this->current_file();
crumbs.emplace_back(
lf->get_unique_path(),
attr_line_t().append(lnav::roles::identifier(lf->get_unique_path())),
[this]() {
return this->tss_files | lnav::itertools::map([](const auto& lf) {
return breadcrumb::possibility{
lf->get_unique_path(),
attr_line_t(lf->get_unique_path()),
};
});
},
[this](const auto& key) {
auto lf_opt = this->tss_files
| lnav::itertools::find_if([&key](const auto& elem) {
return key.template get<std::string>()
== elem->get_unique_path();
})
| lnav::itertools::deref();
if (!lf_opt) {
return;
}
this->to_front(lf_opt.value());
this->tss_view->reload_data();
});
}

@ -40,19 +40,19 @@ class textfile_sub_source
: public text_sub_source : public text_sub_source
, public vis_location_history { , public vis_location_history {
public: public:
typedef std::deque<std::shared_ptr<logfile>>::iterator file_iterator; using file_iterator = std::deque<std::shared_ptr<logfile>>::iterator;
textfile_sub_source() { this->tss_supports_filtering = true; } textfile_sub_source() { this->tss_supports_filtering = true; }
~textfile_sub_source() = default; ~textfile_sub_source() override = default;
bool empty() const { return this->tss_files.empty(); } bool empty() const { return this->tss_files.empty(); }
size_t size() const { return this->tss_files.size(); } size_t size() const { return this->tss_files.size(); }
size_t text_line_count(); size_t text_line_count() override;
size_t text_line_width(textview_curses& curses) size_t text_line_width(textview_curses& curses) override
{ {
return this->tss_files.empty() return this->tss_files.empty()
? 0 ? 0
@ -62,15 +62,15 @@ public:
void text_value_for_line(textview_curses& tc, void text_value_for_line(textview_curses& tc,
int line, int line,
std::string& value_out, std::string& value_out,
line_flags_t flags); line_flags_t flags) override;
void text_attrs_for_line(textview_curses& tc, void text_attrs_for_line(textview_curses& tc,
int row, int row,
string_attrs_t& value_out); string_attrs_t& value_out) override;
size_t text_size_for_line(textview_curses& tc, size_t text_size_for_line(textview_curses& tc,
int line, int line,
line_flags_t flags); line_flags_t flags) override;
std::shared_ptr<logfile> current_file() const std::shared_ptr<logfile> current_file() const
{ {
@ -81,7 +81,7 @@ public:
return this->tss_files.front(); return this->tss_files.front();
} }
std::string text_source_name(const textview_curses& tv) std::string text_source_name(const textview_curses& tv) override
{ {
if (this->tss_files.empty()) { if (this->tss_files.empty()) {
return ""; return "";
@ -179,19 +179,22 @@ public:
return retval; return retval;
} }
void text_filters_changed(); void text_filters_changed() override;
int get_filtered_count() const; int get_filtered_count() const override;
int get_filtered_count_for(size_t filter_index) const; int get_filtered_count_for(size_t filter_index) const override;
text_format_t get_text_format() const; text_format_t get_text_format() const override;
nonstd::optional<location_history*> get_location_history() nonstd::optional<location_history*> get_location_history() override
{ {
return this; return this;
} }
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override;
private: private:
void detach_observer(std::shared_ptr<logfile> lf) void detach_observer(std::shared_ptr<logfile> lf)
{ {

@ -972,3 +972,16 @@ vis_location_history::loc_history_forward(vis_line_t current_top)
return this->current_position(); return this->current_position();
} }
void
text_sub_source::toggle_apply_filters()
{
this->tss_apply_filters = !this->tss_apply_filters;
this->text_filters_changed();
}
void
text_sub_source::text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs)
{
}

@ -38,6 +38,7 @@
#include "base/func_util.hh" #include "base/func_util.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "bookmarks.hh" #include "bookmarks.hh"
#include "breadcrumb.hh"
#include "grep_proc.hh" #include "grep_proc.hh"
#include "highlighter.hh" #include "highlighter.hh"
#include "listview_curses.hh" #include "listview_curses.hh"
@ -385,14 +386,16 @@ public:
*/ */
virtual void text_mark(const bookmark_type_t* bm, virtual void text_mark(const bookmark_type_t* bm,
vis_line_t line, vis_line_t line,
bool added){}; bool added)
{
}
/** /**
* Clear the bookmarks for a particular type in the text source. * Clear the bookmarks for a particular type in the text source.
* *
* @param bm The type of bookmarks to clear. * @param bm The type of bookmarks to clear.
*/ */
virtual void text_clear_marks(const bookmark_type_t* bm){}; virtual void text_clear_marks(const bookmark_type_t* bm) {}
/** /**
* Get the attributes for a line of text. * Get the attributes for a line of text.
@ -404,7 +407,9 @@ public:
*/ */
virtual void text_attrs_for_line(textview_curses& tc, virtual void text_attrs_for_line(textview_curses& tc,
int line, int line,
string_attrs_t& value_out){}; string_attrs_t& value_out)
{
}
/** /**
* Update the bookmarks used by the text view based on the bookmarks * Update the bookmarks used by the text view based on the bookmarks
@ -412,7 +417,7 @@ public:
* *
* @param bm The bookmarks data structure used by the text view. * @param bm The bookmarks data structure used by the text view.
*/ */
virtual void text_update_marks(vis_bookmarks& bm){}; virtual void text_update_marks(vis_bookmarks& bm) {}
virtual std::string text_source_name(const textview_curses& tv) virtual std::string text_source_name(const textview_curses& tv)
{ {
@ -444,11 +449,10 @@ public:
return nonstd::nullopt; return nonstd::nullopt;
} }
void toggle_apply_filters() void toggle_apply_filters();
{
this->tss_apply_filters = !this->tss_apply_filters; virtual void text_crumbs_for_line(int line,
this->text_filters_changed(); std::vector<breadcrumb::crumb>& crumbs);
}
bool tss_supports_filtering{false}; bool tss_supports_filtering{false};
bool tss_apply_filters{true}; bool tss_apply_filters{true};

@ -79,6 +79,9 @@
}, },
"list-glyph": { "list-glyph": {
"color": "Yellow" "color": "Yellow"
},
"breadcrumb": {
"color": "Teal"
} }
}, },
"syntax-styles": { "syntax-styles": {
@ -118,6 +121,9 @@
}, },
"file": { "file": {
"color": "Blue" "color": "Blue"
},
"number": {
"bold": true
} }
}, },
"status-styles": { "status-styles": {
@ -199,7 +205,13 @@
} }
}, },
"xml": { "xml": {
"pattern": "</?([^ >=]+)[^>]*>", "pattern": "</?([^ >=!]+)[^>]*>",
"style": {
"color": "${semantic_highlight_color}"
}
},
"xml-decl": {
"pattern": "<!([^ >=!]+)[^>]*>",
"style": { "style": {
"color": "${semantic_highlight_color}" "color": "${semantic_highlight_color}"
} }

@ -134,19 +134,19 @@
}, },
"text": { "text": {
"color": "$black", "color": "$black",
"background-color": "$white" "background-color": "#999"
}, },
"warn": { "warn": {
"color": "$yellow", "color": "$yellow",
"background-color": "$white" "background-color": "#999"
}, },
"alert": { "alert": {
"color": "$red", "color": "$red",
"background-color": "$white" "background-color": "#999"
}, },
"active": { "active": {
"color": "$green", "color": "$green",
"background-color": "$white" "background-color": "#999"
}, },
"inactive": { "inactive": {
"color": "$black", "color": "$black",

@ -16,7 +16,6 @@
}, },
"styles": { "styles": {
"identifier": { "identifier": {
"background-color": "$black",
"color": "semantic()" "color": "semantic()"
}, },
"text": { "text": {
@ -92,6 +91,9 @@
}, },
"list-glyph": { "list-glyph": {
"color": "$yellow" "color": "$yellow"
},
"breadcrumb": {
"color": "#99a"
} }
}, },
"syntax-styles": { "syntax-styles": {
@ -132,6 +134,9 @@
}, },
"file": { "file": {
"color": "$blue" "color": "$blue"
},
"number": {
"bold": true
} }
}, },
"status-styles": { "status-styles": {

@ -0,0 +1,346 @@
/**
* Origin: https://github.com/ekg/intervaltree
*/
#ifndef __INTERVAL_TREE_H
#define __INTERVAL_TREE_H
#include <algorithm>
#include <cassert>
#include <iostream>
#include <limits>
#include <memory>
#include <vector>
namespace interval_tree {
template <class Scalar, typename Value>
class Interval {
public:
Scalar start;
Scalar stop;
Value value;
Interval(const Scalar& s, const Scalar& e, const Value& v)
: start(std::min(s, e))
, stop(std::max(s, e))
, value(v)
{}
};
template <class Scalar, typename Value>
Value intervalStart(const Interval<Scalar,Value>& i) {
return i.start;
}
template <class Scalar, typename Value>
Value intervalStop(const Interval<Scalar, Value>& i) {
return i.stop;
}
template <class Scalar, typename Value>
std::ostream& operator<<(std::ostream& out, const Interval<Scalar, Value>& i) {
out << "Interval(" << i.start << ", " << i.stop << "): " << i.value;
return out;
}
template <class Scalar, class Value>
class IntervalTree {
public:
typedef Interval<Scalar, Value> interval;
typedef std::vector<interval> interval_vector;
struct IntervalStartCmp {
bool operator()(const interval& a, const interval& b) {
return a.start < b.start;
}
};
struct IntervalStopCmp {
bool operator()(const interval& a, const interval& b) {
return a.stop < b.stop;
}
};
IntervalTree()
: left(nullptr)
, right(nullptr)
, center(0)
{}
~IntervalTree() = default;
std::unique_ptr<IntervalTree> clone() const {
return std::unique_ptr<IntervalTree>(new IntervalTree(*this));
}
IntervalTree(const IntervalTree& other)
: intervals(other.intervals),
left(other.left ? other.left->clone() : nullptr),
right(other.right ? other.right->clone() : nullptr),
center(other.center)
{}
IntervalTree& operator=(IntervalTree&&) = default;
IntervalTree(IntervalTree&&) = default;
IntervalTree& operator=(const IntervalTree& other) {
center = other.center;
intervals = other.intervals;
left = other.left ? other.left->clone() : nullptr;
right = other.right ? other.right->clone() : nullptr;
return *this;
}
IntervalTree(
interval_vector&& ivals,
std::size_t depth = 16,
std::size_t minbucket = 64,
std::size_t maxbucket = 512,
Scalar leftextent = 0,
Scalar rightextent = 0)
: left(nullptr)
, right(nullptr)
{
--depth;
const auto minmaxStop = std::minmax_element(ivals.begin(), ivals.end(),
IntervalStopCmp());
const auto minmaxStart = std::minmax_element(ivals.begin(), ivals.end(),
IntervalStartCmp());
if (!ivals.empty()) {
center = (minmaxStart.first->start + minmaxStop.second->stop) / 2;
}
if (leftextent == 0 && rightextent == 0) {
// sort intervals by start
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
} else {
assert(std::is_sorted(ivals.begin(), ivals.end(), IntervalStartCmp()));
}
if (depth == 0 || (ivals.size() < minbucket && ivals.size() < maxbucket)) {
std::sort(ivals.begin(), ivals.end(), IntervalStartCmp());
intervals = std::move(ivals);
assert(is_valid().first);
return;
} else {
Scalar leftp = 0;
Scalar rightp = 0;
if (leftextent || rightextent) {
leftp = leftextent;
rightp = rightextent;
} else {
leftp = ivals.front().start;
rightp = std::max_element(ivals.begin(), ivals.end(),
IntervalStopCmp())->stop;
}
interval_vector lefts;
interval_vector rights;
for (typename interval_vector::const_iterator i = ivals.begin();
i != ivals.end(); ++i) {
const interval& interval = *i;
if (interval.stop < center) {
lefts.push_back(interval);
} else if (interval.start > center) {
rights.push_back(interval);
} else {
assert(interval.start <= center);
assert(center <= interval.stop);
intervals.push_back(interval);
}
}
if (!lefts.empty()) {
left.reset(new IntervalTree(std::move(lefts),
depth, minbucket, maxbucket,
leftp, center));
}
if (!rights.empty()) {
right.reset(new IntervalTree(std::move(rights),
depth, minbucket, maxbucket,
center, rightp));
}
}
assert(is_valid().first);
}
// Call f on all intervals near the range [start, stop]:
template <class UnaryFunction>
void visit_near(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
if (!intervals.empty() && ! (stop < intervals.front().start)) {
for (auto & i : intervals) {
f(i);
}
}
if (left && start <= center) {
left->visit_near(start, stop, f);
}
if (right && stop >= center) {
right->visit_near(start, stop, f);
}
}
// Call f on all intervals crossing pos
template <class UnaryFunction>
void visit_overlapping(const Scalar& pos, UnaryFunction f) const {
visit_overlapping(pos, pos, f);
}
// Call f on all intervals overlapping [start, stop]
template <class UnaryFunction>
void visit_overlapping(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
auto filterF = [&](const interval& interval) {
if (interval.stop >= start && interval.start <= stop) {
// Only apply f if overlapping
f(interval);
}
};
visit_near(start, stop, filterF);
}
// Call f on all intervals contained within [start, stop]
template <class UnaryFunction>
void visit_contained(const Scalar& start, const Scalar& stop, UnaryFunction f) const {
auto filterF = [&](const interval& interval) {
if (start <= interval.start && interval.stop <= stop) {
f(interval);
}
};
visit_near(start, stop, filterF);
}
interval_vector findOverlapping(const Scalar& start, const Scalar& stop) const {
interval_vector result;
visit_overlapping(start, stop,
[&](const interval& interval) {
result.emplace_back(interval);
});
return result;
}
interval_vector findContained(const Scalar& start, const Scalar& stop) const {
interval_vector result;
visit_contained(start, stop,
[&](const interval& interval) {
result.push_back(interval);
});
return result;
}
bool empty() const {
if (left && !left->empty()) {
return false;
}
if (!intervals.empty()) {
return false;
}
if (right && !right->empty()) {
return false;
}
return true;
}
template <class UnaryFunction>
void visit_all(UnaryFunction f) const {
if (left) {
left->visit_all(f);
}
std::for_each(intervals.begin(), intervals.end(), f);
if (right) {
right->visit_all(f);
}
}
std::pair<Scalar, Scalar> extentBruitForce() const {
struct Extent {
std::pair<Scalar, Scalar> x = {std::numeric_limits<Scalar>::max(),
std::numeric_limits<Scalar>::min() };
void operator()(const interval & interval) {
x.first = std::min(x.first, interval.start);
x.second = std::max(x.second, interval.stop);
}
};
Extent extent;
visit_all([&](const interval & interval) { extent(interval); });
return extent.x;
}
// Check all constraints.
// If first is false, second is invalid.
std::pair<bool, std::pair<Scalar, Scalar>> is_valid() const {
const auto minmaxStop = std::minmax_element(intervals.begin(), intervals.end(),
IntervalStopCmp());
const auto minmaxStart = std::minmax_element(intervals.begin(), intervals.end(),
IntervalStartCmp());
std::pair<bool, std::pair<Scalar, Scalar>> result = {true, { std::numeric_limits<Scalar>::max(),
std::numeric_limits<Scalar>::min() }};
if (!intervals.empty()) {
result.second.first = std::min(result.second.first, minmaxStart.first->start);
result.second.second = std::min(result.second.second, minmaxStop.second->stop);
}
if (left) {
auto valid = left->is_valid();
result.first &= valid.first;
result.second.first = std::min(result.second.first, valid.second.first);
result.second.second = std::min(result.second.second, valid.second.second);
if (!result.first) { return result; }
if (valid.second.second >= center) {
result.first = false;
return result;
}
}
if (right) {
auto valid = right->is_valid();
result.first &= valid.first;
result.second.first = std::min(result.second.first, valid.second.first);
result.second.second = std::min(result.second.second, valid.second.second);
if (!result.first) { return result; }
if (valid.second.first <= center) {
result.first = false;
return result;
}
}
if (!std::is_sorted(intervals.begin(), intervals.end(), IntervalStartCmp())) {
result.first = false;
}
return result;
}
friend std::ostream& operator<<(std::ostream& os, const IntervalTree& itree) {
return writeOut(os, itree);
}
friend std::ostream& writeOut(std::ostream& os, const IntervalTree& itree,
std::size_t depth = 0) {
auto pad = [&]() { for (std::size_t i = 0; i != depth; ++i) { os << ' '; } };
pad(); os << "center: " << itree.center << '\n';
for (const interval & inter : itree.intervals) {
pad(); os << inter << '\n';
}
if (itree.left) {
pad(); os << "left:\n";
writeOut(os, *itree.left, depth + 1);
} else {
pad(); os << "left: nullptr\n";
}
if (itree.right) {
pad(); os << "right:\n";
writeOut(os, *itree.right, depth + 1);
} else {
pad(); os << "right: nullptr\n";
}
return os;
}
private:
interval_vector intervals;
std::unique_ptr<IntervalTree> left;
std::unique_ptr<IntervalTree> right;
Scalar center;
};
}
#endif

@ -51,10 +51,7 @@ public:
this->ups_unique_path = path; this->ups_unique_path = path;
} }
std::string get_unique_path() const const std::string& get_unique_path() const { return this->ups_unique_path; }
{
return this->ups_unique_path;
}
virtual ghc::filesystem::path get_path() const = 0; virtual ghc::filesystem::path get_path() const = 0;

@ -49,7 +49,8 @@ using namespace std::chrono_literals;
const struct itimerval ui_periodic_timer::INTERVAL = { const struct itimerval ui_periodic_timer::INTERVAL = {
{0, std::chrono::duration_cast<std::chrono::microseconds>(350ms).count()}, {0, std::chrono::duration_cast<std::chrono::microseconds>(350ms).count()},
{0, std::chrono::duration_cast<std::chrono::microseconds>(350ms).count()}}; {0, std::chrono::duration_cast<std::chrono::microseconds>(350ms).count()},
};
ui_periodic_timer::ui_periodic_timer() : upt_counter(0) ui_periodic_timer::ui_periodic_timer() : upt_counter(0)
{ {
@ -188,8 +189,11 @@ view_curses::mvwattrline(WINDOW* window,
full_line = expanded_line; full_line = expanded_line;
auto& vc = view_colors::singleton(); auto& vc = view_colors::singleton();
auto text_attrs = vc.attrs_for_role(base_role); auto text_attrs = vc.attrs_for_role(role_t::VCR_TEXT);
auto attrs = text_attrs; short text_role_fg, text_role_bg;
auto text_color_pair = PAIR_NUMBER(text_attrs);
pair_content(text_color_pair, &text_role_fg, &text_role_bg);
auto attrs = vc.attrs_for_role(base_role);
wmove(window, y, x); wmove(window, y, x);
wattron(window, attrs); wattron(window, attrs);
if (lr_bytes.lr_start < (int) full_line.size()) { if (lr_bytes.lr_start < (int) full_line.size()) {
@ -334,6 +338,27 @@ view_curses::mvwattrline(WINDOW* window,
attrs &= ~(A_LEFT | A_RIGHT); attrs &= ~(A_LEFT | A_RIGHT);
} }
if (color_pair > 0) {
short pair_fg, pair_bg;
pair_content(color_pair, &pair_fg, &pair_bg);
if ((pair_fg == -1 || pair_fg == text_role_fg)
&& (pair_bg == -1 || pair_bg == text_role_bg))
{
color_pair = 0;
} else if (pair_bg == -1 || pair_bg == text_role_bg) {
if (!has_fg) {
memset(
fg_color, -1, line_width_chars * sizeof(short));
}
std::fill(&fg_color[attr_range.lr_start],
&fg_color[attr_range.lr_end],
(short) pair_fg);
has_fg = true;
color_pair = 0;
}
}
mvwin_wchnstr(window, y, x_pos, row_ch, ch_width); mvwin_wchnstr(window, y, x_pos, row_ch, ch_width);
for (int lpc = 0; lpc < ch_width; lpc++) { for (int lpc = 0; lpc < ch_width; lpc++) {
bool clear_rev = false; bool clear_rev = false;
@ -887,6 +912,12 @@ view_colors::init_roles(const lnav_theme& lt,
lt.lt_style_list_glyph, lt.lt_style_list_glyph,
lt.lt_style_text, lt.lt_style_text,
reporter); reporter);
this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_BREADCRUMB)]
= this->to_attrs(color_pair_base,
lt,
lt.lt_style_breadcrumb,
lt.lt_style_text,
reporter);
{ {
style_config stitch_sc; style_config stitch_sc;

@ -349,11 +349,13 @@ public:
*/ */
virtual void do_update() virtual void do_update()
{ {
this->vc_needs_update = false;
if (!this->vc_visible) { if (!this->vc_visible) {
return; return;
} }
for (auto child : this->vc_children) { for (auto* child : this->vc_children) {
child->do_update(); child->do_update();
} }
} }
@ -363,11 +365,13 @@ public:
void set_needs_update() void set_needs_update()
{ {
this->vc_needs_update = true; this->vc_needs_update = true;
for (auto child : this->vc_children) { for (auto* child : this->vc_children) {
child->set_needs_update(); child->set_needs_update();
} }
} }
bool get_needs_update() const { return this->vc_needs_update; }
view_curses& add_child_view(view_curses* child) view_curses& add_child_view(view_curses* child)
{ {
this->vc_children.push_back(child); this->vc_children.push_back(child);
@ -375,10 +379,7 @@ public:
return *this; return *this;
} }
void set_default_role(role_t role) void set_default_role(role_t role) { this->vc_default_role = role; }
{
this->vc_default_role = role;
}
void set_visible(bool value) void set_visible(bool value)
{ {

@ -29,19 +29,25 @@
#include "view_helpers.hh" #include "view_helpers.hh"
#include "base/humanize.hh"
#include "base/itertools.hh"
#include "config.h" #include "config.h"
#include "environ_vtab.hh" #include "environ_vtab.hh"
#include "help-txt.h" #include "help-txt.h"
#include "intervaltree/IntervalTree.h"
#include "lnav.hh" #include "lnav.hh"
#include "lnav.indexing.hh" #include "lnav.indexing.hh"
#include "pretty_printer.hh" #include "pretty_printer.hh"
#include "shlex.hh" #include "shlex.hh"
#include "sql_help.hh" #include "sql_help.hh"
#include "sql_util.hh" #include "sql_util.hh"
#include "view_helpers.crumbs.hh"
#include "view_helpers.examples.hh" #include "view_helpers.examples.hh"
#include "view_helpers.hist.hh" #include "view_helpers.hist.hh"
#include "vtab_module.hh" #include "vtab_module.hh"
using namespace std::chrono_literals;
const char* lnav_view_strings[LNV__MAX + 1] = { const char* lnav_view_strings[LNV__MAX + 1] = {
"log", "log",
"text", "text",
@ -73,7 +79,7 @@ view_from_string(const char* name)
return nonstd::nullopt; return nonstd::nullopt;
} }
auto view_name_iter auto* view_name_iter
= std::find_if(std::begin(lnav_view_strings), = std::find_if(std::begin(lnav_view_strings),
std::end(lnav_view_strings), std::end(lnav_view_strings),
[&](const char* v) { [&](const char* v) {
@ -111,15 +117,174 @@ open_schema_view()
schema_tc->redo_search(); schema_tc->redo_search();
} }
class pretty_sub_source : public plain_text_source {
public:
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override
{
text_sub_source::text_crumbs_for_line(line, crumbs);
if (line < 0 || line > this->tds_lines.size()) {
return;
}
const auto& tl = this->tds_lines[line];
const auto initial_size = crumbs.size();
pretty_printer::hier_node* root_node;
this->pss_hier_tree->template visit_overlapping(
tl.tl_offset,
[&root_node](const auto& hier_iv) { root_node = hier_iv.value; });
this->pss_interval_tree->visit_overlapping(
tl.tl_offset,
tl.tl_offset + tl.tl_value.length(),
[&crumbs, root_node, this, initial_size](const auto& iv) {
auto path = crumbs | lnav::itertools::skip(initial_size)
| lnav::itertools::map(&breadcrumb::crumb::c_key)
| 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(
root_node, 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;
};
auto path_performer =
[this, root_node, path](
const breadcrumb::crumb::key_t& value) {
auto curr_node = pretty_printer::hier_node::lookup_path(
root_node, path);
if (!curr_node) {
return;
}
auto* parent_node = curr_node.value()->hn_parent;
if (parent_node == nullptr) {
return;
}
value.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()) {
this->line_for_offset(
sib_iter->second->hn_start)
| [](const auto new_top) {
lnav_data.ld_views[LNV_PRETTY]
.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) |
[](const auto new_top) {
lnav_data.ld_views[LNV_PRETTY].set_top(
new_top);
};
});
};
crumbs.template emplace_back(iv.value,
std::move(poss_provider),
std::move(path_performer));
auto curr_node
= pretty_printer::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(
root_node, 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 = pretty_printer::hier_node::lookup_path(root_node, 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)
| [](const auto new_top) {
lnav_data.ld_views[LNV_PRETTY].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) |
[](const auto new_top) {
lnav_data.ld_views[LNV_PRETTY].set_top(new_top);
};
});
};
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;
}
}
using hier_tree_t
= interval_tree::IntervalTree<file_off_t, pretty_printer::hier_node*>;
using hier_interval_t
= interval_tree::Interval<file_off_t, pretty_printer::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<hier_tree_t> pss_hier_tree;
};
static void static void
open_pretty_view() open_pretty_view()
{ {
static const char* NOTHING_MSG = "Nothing to pretty-print"; static const char* NOTHING_MSG = "Nothing to pretty-print";
textview_curses* top_tc = *lnav_data.ld_view_stack.top(); auto* top_tc = *lnav_data.ld_view_stack.top();
textview_curses* pretty_tc = &lnav_data.ld_views[LNV_PRETTY]; auto* pretty_tc = &lnav_data.ld_views[LNV_PRETTY];
textview_curses* log_tc = &lnav_data.ld_views[LNV_LOG]; auto* log_tc = &lnav_data.ld_views[LNV_LOG];
textview_curses* text_tc = &lnav_data.ld_views[LNV_TEXT]; auto* text_tc = &lnav_data.ld_views[LNV_TEXT];
attr_line_t full_text; attr_line_t full_text;
delete pretty_tc->get_sub_source(); delete pretty_tc->get_sub_source();
@ -129,6 +294,9 @@ open_pretty_view()
return; return;
} }
std::vector<pretty_printer::pretty_interval> all_intervals;
std::vector<std::unique_ptr<pretty_printer::hier_node>> hier_nodes;
std::vector<pretty_sub_source::hier_interval_t> hier_tree_vec;
if (top_tc == log_tc) { if (top_tc == log_tc) {
logfile_sub_source& lss = lnav_data.ld_log_source; logfile_sub_source& lss = lnav_data.ld_log_source;
bool first_line = true; bool first_line = true;
@ -146,7 +314,7 @@ open_pretty_view()
auto ll_start = lf->message_start(ll); auto ll_start = lf->message_start(ll);
attr_line_t al; attr_line_t al;
vl -= vis_line_t(distance(ll_start, ll)); vl -= vis_line_t(std::distance(ll_start, ll));
lss.text_value_for_line( lss.text_value_for_line(
*log_tc, *log_tc,
vl, vl,
@ -157,21 +325,33 @@ open_pretty_view()
al.apply_hide(); al.apply_hide();
} }
line_range orig_lr const auto orig_lr
= find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE); = find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE);
attr_line_t orig_al const auto body_lr
= al.subline(orig_lr.lr_start, orig_lr.length()); = find_string_attr_range(al.get_attrs(), &SA_BODY);
attr_line_t prefix_al = al.subline(0, orig_lr.lr_start); auto orig_al = al.subline(orig_lr.lr_start, orig_lr.length());
auto prefix_al = al.subline(0, orig_lr.lr_start);
data_scanner ds(orig_al.get_string());
pretty_printer pp(&ds, orig_al.get_attrs());
attr_line_t pretty_al; attr_line_t pretty_al;
std::vector<attr_line_t> pretty_lines; std::vector<attr_line_t> pretty_lines;
data_scanner ds(orig_al.get_string(),
body_lr.is_valid()
? body_lr.lr_start - orig_lr.lr_start
: orig_lr.lr_start);
pretty_printer pp(&ds, orig_al.get_attrs());
auto start_off = full_text.length();
if (body_lr.is_valid()) {
// TODO: dump more details of the line in the output.
pp.append_to(pretty_al);
} else {
pretty_al = orig_al;
}
// TODO: dump more details of the line in the output.
pp.append_to(pretty_al);
pretty_al.split_lines(pretty_lines); pretty_al.split_lines(pretty_lines);
auto curr_intervals = pp.take_intervals();
auto line_hier_root = pp.take_hier_root();
auto line_off = 0;
for (auto& pretty_line : pretty_lines) { for (auto& pretty_line : pretty_lines) {
if (pretty_line.empty() && &pretty_line == &pretty_lines.back()) if (pretty_line.empty() && &pretty_line == &pretty_lines.back())
{ {
@ -179,32 +359,76 @@ open_pretty_view()
} }
pretty_line.insert(0, prefix_al); pretty_line.insert(0, prefix_al);
pretty_line.append("\n"); pretty_line.append("\n");
for (auto& interval : curr_intervals) {
if (line_off <= interval.start) {
interval.start += prefix_al.length();
interval.stop += prefix_al.length();
} else if (line_off < interval.stop) {
interval.stop += prefix_al.length();
}
}
pretty_printer::hier_node::depth_first(
line_hier_root.get(),
[line_off, prefix_len = prefix_al.length()](auto* hn) {
if (line_off <= hn->hn_start) {
hn->hn_start += prefix_len;
}
});
line_off += pretty_line.length();
full_text.append(pretty_line); full_text.append(pretty_line);
} }
first_line = false; first_line = false;
for (auto& interval : curr_intervals) {
interval.start += start_off;
interval.stop += start_off;
}
pretty_printer::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));
hier_tree_vec.emplace_back(
start_off, full_text.length(), hier_nodes.back().get());
all_intervals.insert(
all_intervals.end(),
std::make_move_iterator(curr_intervals.begin()),
std::make_move_iterator(curr_intervals.end()));
} }
if (!full_text.empty()) { if (!full_text.empty()) {
full_text.erase(full_text.length() - 1, 1); full_text.erase(full_text.length() - 1, 1);
} }
} else if (top_tc == text_tc) { } else if (top_tc == text_tc) {
auto lf = lnav_data.ld_text_source.current_file(); if (text_tc->listview_rows(*text_tc)) {
auto lf = lnav_data.ld_text_source.current_file();
for (vis_line_t vl = text_tc->get_top(); vl <= text_tc->get_bottom(); std::string all_lines;
++vl) {
auto ll = lf->begin() + vl; for (vis_line_t vl = text_tc->get_top();
shared_buffer_ref sbr; vl <= text_tc->get_bottom();
++vl) {
lf->read_full_message(ll, sbr); auto ll = lf->begin() + vl;
data_scanner ds(sbr); shared_buffer_ref sbr;
lf->read_full_message(ll, sbr);
all_lines.append(sbr.get_data(), sbr.length());
}
data_scanner ds(all_lines);
string_attrs_t sa; string_attrs_t sa;
pretty_printer pp(&ds, sa); pretty_printer pp(&ds, sa);
pp.append_to(full_text); pp.append_to(full_text);
all_intervals = pp.take_intervals();
hier_nodes.emplace_back(pp.take_hier_root());
hier_tree_vec.emplace_back(
0, full_text.length(), hier_nodes.back().get());
} }
} }
auto* pts = new plain_text_source(); auto* pts = new pretty_sub_source();
pts->pss_interval_tree = std::make_shared<pretty_printer::pretty_tree>(
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>(
std::move(hier_tree_vec));
pts->replace_with(full_text); pts->replace_with(full_text);
pretty_tc->set_sub_source(pts); pretty_tc->set_sub_source(pts);
if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) { if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) {
@ -342,6 +566,7 @@ layout_views()
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS || lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES) || lnav_data.ld_mode == ln_mode_t::SEARCH_FILES)
&& !preview_status_open && !doc_open; && !preview_status_open && !doc_open;
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
int filter_height = filters_open ? 5 : 0; int filter_height = filters_open ? 5 : 0;
int bottom_height = (doc_open ? 1 : 0) + doc_height int bottom_height = (doc_open ? 1 : 0) + doc_height
@ -352,7 +577,6 @@ layout_views()
tc.set_height(vis_line_t(-(bottom_height + (filter_status_open ? 1 : 0) tc.set_height(vis_line_t(-(bottom_height + (filter_status_open ? 1 : 0)
+ (filters_open ? 1 : 0) + filter_height))); + (filters_open ? 1 : 0) + filter_height)));
} }
lnav_data.ld_status[LNS_TOP].set_enabled(!filters_open);
lnav_data.ld_status[LNS_FILTER].set_visible(filter_status_open); lnav_data.ld_status[LNS_FILTER].set_visible(filter_status_open);
lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open); lnav_data.ld_status[LNS_FILTER].set_enabled(filters_open);
lnav_data.ld_status[LNS_FILTER].set_top( lnav_data.ld_status[LNS_FILTER].set_top(
@ -361,6 +585,8 @@ layout_views()
lnav_data.ld_status[LNS_FILTER_HELP].set_top( lnav_data.ld_status[LNS_FILTER_HELP].set_top(
-(bottom_height + filter_height + 1)); -(bottom_height + filter_height + 1));
lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + um_height + 2)); lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + um_height + 2));
lnav_data.ld_status[LNS_BOTTOM].set_enabled(!filters_open
&& !breadcrumb_open);
lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height); lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height);
lnav_data.ld_status[LNS_DOC].set_visible(doc_open); lnav_data.ld_status[LNS_DOC].set_visible(doc_open);
lnav_data.ld_status[LNS_PREVIEW].set_top(height - bottom_height lnav_data.ld_status[LNS_PREVIEW].set_top(height - bottom_height
@ -624,6 +850,9 @@ ensure_view(textview_curses* expected_tc)
bool bool
ensure_view(lnav_view_t expected) ensure_view(lnav_view_t expected)
{ {
require(expected >= 0);
require(expected < LNV__MAX);
return ensure_view(&lnav_data.ld_views[expected]); return ensure_view(&lnav_data.ld_views[expected]);
} }
@ -820,3 +1049,85 @@ hist_index_delegate::index_complete(logfile_sub_source& lss)
{ {
this->hid_view.reload_data(); this->hid_view.reload_data();
} }
static std::vector<breadcrumb::possibility>
view_title_poss()
{
std::vector<breadcrumb::possibility> retval;
for (int view_index = 0; view_index < LNV__MAX; view_index++) {
attr_line_t display_value{lnav_view_titles[view_index]};
nonstd::optional<size_t> quantity;
std::string units;
switch (view_index) {
case LNV_LOG:
quantity = lnav_data.ld_log_source.file_count();
units = "file";
break;
case LNV_TEXT:
quantity = lnav_data.ld_text_source.size();
units = "file";
break;
case LNV_DB:
quantity = lnav_data.ld_db_row_source.dls_rows.size();
units = "row";
break;
}
if (quantity) {
display_value.pad_to(8)
.append(" (")
.append(lnav::roles::number(
quantity.value() == 0 ? "no"
: fmt::to_string(quantity.value())))
.append(FMT_STRING(" {}{})"),
units,
quantity.value() == 1 ? "" : "s");
}
retval.emplace_back(lnav_view_titles[view_index], display_value);
}
return retval;
}
static void
view_performer(const breadcrumb::crumb::key_t& view_name)
{
auto* view_title_iter = std::find_if(
std::begin(lnav_view_titles),
std::end(lnav_view_titles),
[&](const char* v) {
return strcasecmp(v, view_name.get<std::string>().c_str()) == 0;
});
if (view_title_iter != std::end(lnav_view_titles)) {
ensure_view(lnav_view_t(view_title_iter - lnav_view_titles));
}
}
std::vector<breadcrumb::crumb>
lnav_crumb_source()
{
std::vector<breadcrumb::crumb> retval;
auto top_view_opt = lnav_data.ld_view_stack.top();
if (!top_view_opt) {
return retval;
}
auto* top_view = top_view_opt.value();
auto view_index = top_view - lnav_data.ld_views;
retval.emplace_back(
lnav_view_titles[view_index],
attr_line_t().append(lnav::roles::status_title(
fmt::format(FMT_STRING(" {} "), lnav_view_titles[view_index]))),
view_title_poss,
view_performer);
auto* tss = top_view->get_sub_source();
if (tss != nullptr) {
tss->text_crumbs_for_line(top_view->get_top(), retval);
}
return retval;
}

@ -0,0 +1,37 @@
/**
* 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_view_helpers_crumbs_hh
#define lnav_view_helpers_crumbs_hh
#include "breadcrumb_curses.hh"
std::vector<breadcrumb::crumb> lnav_crumb_source();
#endif

@ -58,6 +58,7 @@ typedef enum {
/** The command modes that are available while viewing a file. */ /** The command modes that are available while viewing a file. */
enum class ln_mode_t : int { enum class ln_mode_t : int {
PAGING, PAGING,
BREADCRUMBS,
FILTER, FILTER,
FILES, FILES,
COMMAND, COMMAND,

@ -34,12 +34,15 @@
#include <unistd.h> #include <unistd.h>
#include "base/injector.bind.hh" #include "base/injector.bind.hh"
#include "base/itertools.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "base/opt_util.hh" #include "base/opt_util.hh"
#include "config.h" #include "config.h"
#include "lnav.hh" #include "lnav.hh"
#include "sql_util.hh" #include "sql_util.hh"
#include "view_curses.hh" #include "view_curses.hh"
#include "vtab_module_json.hh"
#include "yajlpp/yajlpp_def.hh"
template<> template<>
struct from_sqlite<lnav_view_t> { struct from_sqlite<lnav_view_t> {
@ -119,6 +122,55 @@ struct from_sqlite<std::pair<std::string, auto_mem<pcre>>> {
} }
}; };
static const typed_json_path_container<breadcrumb::possibility>
breadcrumb_possibility_handlers = {
yajlpp::property_handler("display_value")
.for_field(&breadcrumb::possibility::p_display_value,
&attr_line_t::al_string),
};
struct resolved_crumb {
resolved_crumb() = default;
resolved_crumb(std::string display_value,
std::string search_placeholder,
std::vector<breadcrumb::possibility> possibilities)
: rc_display_value(std::move(display_value)),
rc_search_placeholder(std::move(search_placeholder)),
rc_possibilities(std::move(possibilities))
{
}
std::string rc_display_value;
std::string rc_search_placeholder;
std::vector<breadcrumb::possibility> rc_possibilities;
};
static const typed_json_path_container<resolved_crumb> breadcrumb_crumb_handlers
= {
yajlpp::property_handler("display_value")
.for_field(&resolved_crumb::rc_display_value),
yajlpp::property_handler("search_placeholder")
.for_field(&resolved_crumb::rc_search_placeholder),
yajlpp::property_handler("possibilities#")
.for_field(&resolved_crumb::rc_possibilities)
.with_children(breadcrumb_possibility_handlers),
};
struct top_line_meta {
nonstd::optional<std::string> tlm_time;
nonstd::optional<std::string> tlm_file;
std::vector<resolved_crumb> tlm_crumbs;
};
static const typed_json_path_container<top_line_meta> top_line_meta_handlers = {
yajlpp::property_handler("time").for_field(&top_line_meta::tlm_time),
yajlpp::property_handler("file").for_field(&top_line_meta::tlm_file),
yajlpp::property_handler("breadcrumbs#")
.for_field(&top_line_meta::tlm_crumbs)
.with_children(breadcrumb_crumb_handlers),
};
struct lnav_views : public tvt_iterator_cursor<lnav_views> { struct lnav_views : public tvt_iterator_cursor<lnav_views> {
static constexpr const char* NAME = "lnav_views"; static constexpr const char* NAME = "lnav_views";
static constexpr const char* CREATE_STMT = R"( static constexpr const char* CREATE_STMT = R"(
@ -133,7 +185,8 @@ CREATE TABLE lnav_views (
top_file TEXT, -- The file the top line is from. top_file TEXT, -- The file the top line is from.
paused INTEGER, -- Indicates if the view is paused and will not load new data. paused INTEGER, -- Indicates if the view is paused and will not load new data.
search TEXT, -- The text to search for in the view. search TEXT, -- The text to search for in the view.
filtering INTEGER -- Indicates if the view is applying filters. filtering INTEGER, -- Indicates if the view is applying filters.
top_meta TEXT --
); );
)"; )";
@ -217,7 +270,7 @@ CREATE TABLE lnav_views (
to_sqlite(ctx, tc.get_current_search()); to_sqlite(ctx, tc.get_current_search());
break; break;
case 9: { case 9: {
auto tss = tc.get_sub_source(); auto* tss = tc.get_sub_source();
if (tss != nullptr && tss->tss_supports_filtering) { if (tss != nullptr && tss->tss_supports_filtering) {
sqlite3_result_int(ctx, tss->tss_apply_filters); sqlite3_result_int(ctx, tss->tss_apply_filters);
@ -226,6 +279,53 @@ CREATE TABLE lnav_views (
} }
break; break;
} }
case 10: {
auto* tss = tc.get_sub_source();
if (tss != nullptr && tss->text_line_count() > 0) {
auto* time_source = dynamic_cast<text_time_translator*>(
tc.get_sub_source());
std::vector<breadcrumb::crumb> crumbs;
tss->text_crumbs_for_line(tc.get_top(), crumbs);
top_line_meta tlm;
if (time_source != nullptr) {
auto top_time_opt
= time_source->time_for_row(tc.get_top());
if (top_time_opt) {
char timestamp[64];
sql_strftime(timestamp,
sizeof(timestamp),
top_time_opt.value(),
'T');
tlm.tlm_time = timestamp;
}
}
tlm.tlm_file = tc.map_top_row([](const auto& al) {
return get_string_attr(al.get_attrs(), logline::L_FILE)
| [](const auto wrapper) {
auto lf = wrapper.get();
return nonstd::make_optional(
lf->get_filename());
};
});
for (const auto& crumb : crumbs) {
tlm.tlm_crumbs.emplace_back(
crumb.c_display_value.get_string(),
crumb.c_search_placeholder,
crumb.c_possibility_provider());
}
auto ret = top_line_meta_handlers.to_json_string(tlm);
to_sqlite(ctx, ret);
} else {
sqlite3_result_null(ctx);
}
break;
}
} }
return SQLITE_OK; return SQLITE_OK;
@ -256,7 +356,8 @@ CREATE TABLE lnav_views (
const char* top_file, const char* top_file,
bool is_paused, bool is_paused,
const char* search, const char* search,
bool do_filtering) bool do_filtering,
const char* top_meta)
{ {
textview_curses& tc = lnav_data.ld_views[index]; textview_curses& tc = lnav_data.ld_views[index];
text_time_translator* time_source text_time_translator* time_source

@ -1299,6 +1299,17 @@ struct typed_json_path_container : public json_path_container {
return gen.to_string_fragment().to_string(); return gen.to_string_fragment().to_string();
} }
json_string to_json_string(T& obj) const
{
yajlpp_gen gen;
yajlpp_gen_context ygc(gen, *this);
ygc.template with_obj(obj);
ygc.ygc_depth = 1;
ygc.gen();
return json_string{gen.get_handle()};
}
}; };
namespace yajlpp { namespace yajlpp {

@ -16,6 +16,7 @@ AM_CPPFLAGS = \
-Wall \ -Wall \
-I$(top_srcdir)/src \ -I$(top_srcdir)/src \
-I$(top_srcdir)/src/fmtlib \ -I$(top_srcdir)/src/fmtlib \
-I$(top_srcdir)/src/third-party \
$(CODE_COVERAGE_CPPFLAGS) \ $(CODE_COVERAGE_CPPFLAGS) \
$(LIBARCHIVE_CFLAGS) \ $(LIBARCHIVE_CFLAGS) \
$(READLINE_CFLAGS) \ $(READLINE_CFLAGS) \

@ -37,16 +37,17 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "data_parser.hh"
#include "view_curses.hh"
#include "base/injector.hh" #include "base/injector.hh"
#include "config.h" #include "config.h"
#include "data_parser.hh"
#include "data_scanner.hh" #include "data_scanner.hh"
#include "elem_to_json.hh" #include "elem_to_json.hh"
#include "log_format.hh" #include "log_format.hh"
#include "log_format_loader.hh" #include "log_format_loader.hh"
#include "logfile.hh"
#include "pretty_printer.hh" #include "pretty_printer.hh"
#include "shared_buffer.hh" #include "shared_buffer.hh"
#include "view_curses.hh"
const char* TMP_NAME = "scanned.tmp"; const char* TMP_NAME = "scanned.tmp";
@ -153,11 +154,13 @@ main(int argc, char* argv[])
std::vector<logline> index; std::vector<logline> index;
if (is_log) { if (is_log) {
logfile_open_options loo;
auto open_res = logfile::open(argv[lpc], loo);
auto lf = open_res.unwrap();
for (iter = root_formats.begin(); for (iter = root_formats.begin();
iter != root_formats.end() && !found; iter != root_formats.end() && !found;
++iter) { ++iter) {
line_info li = {{13}}; line_info li = {{13}};
logfile* lf = nullptr; // XXX
(*iter)->clear(); (*iter)->clear();
if ((*iter)->scan(*lf, index, li, sbr) if ((*iter)->scan(*lf, index, li, sbr)

@ -1,13 +1,25 @@
[2013-09-06T20:00:48.124] TRACE trace testbork bork bork
[2013-09-06T20:00:49.124] INFO Starting up servicebork bork bork [2013-09-06T20:00:48.124] TRACE trace testbork bork bork
[2013-09-06T22:00:49.124] INFO Shutting down servicebork bork bork
[2013-09-06T20:00:49.124] INFO Starting up servicebork bork bork
[2013-09-06T22:00:49.124] INFO Shutting down servicebork bork bork
user:mailto:steve@example.com user:mailto:steve@example.com
[2013-09-06T22:00:59.124] DEBUG5 Details...bork bork bork
[2013-09-06T22:00:59.124] DEBUG4 Details...bork bork bork [2013-09-06T22:00:59.124] DEBUG5 Details...bork bork bork
[2013-09-06T22:00:59.124] DEBUG3 Details...bork bork bork
[2013-09-06T22:00:59.124] DEBUG2 Details...bork bork bork [2013-09-06T22:00:59.124] DEBUG4 Details...bork bork bork
[2013-09-06T22:00:59.124] DEBUG Details...bork bork bork
[2013-09-06T22:01:49.124] STATS 1 beat per secondbork bork bork [2013-09-06T22:00:59.124] DEBUG3 Details...bork bork bork
[2013-09-06T22:01:49.124] WARNING not looking goodbork bork bork
[2013-09-06T22:01:49.124] ERROR looking badbork bork bork [2013-09-06T22:00:59.124] DEBUG2 Details...bork bork bork
[2013-09-06T22:01:49.124] CRITICAL sooo badbork bork bork
[2013-09-06T22:00:59.124] DEBUG Details...bork bork bork
[2013-09-06T22:01:49.124] STATS 1 beat per secondbork bork bork
[2013-09-06T22:01:49.124] WARNING not looking goodbork bork bork
[2013-09-06T22:01:49.124] ERROR looking badbork bork bork
[2013-09-06T22:01:49.124] CRITICAL sooo badbork bork bork

@ -457,7 +457,8 @@ run_test ${lnav_test} -n \
${test_dir}/logfile_access_log.0 ${test_dir}/logfile_access_log.0
check_error_output "goto invalid is working" <<EOF check_error_output "goto invalid is working" <<EOF
✘ error: expecting line number/percentage, timestamp, or relative time ✘ error: invalid argument: invalid
reason: expecting line number/percentage, timestamp, or relative time
--> command-option:1 --> command-option:1
| :goto invalid | :goto invalid
= help: Synopsis = help: Synopsis

@ -43,10 +43,10 @@ run_test ${lnav_test} -n \
logfile_syslog_test.2 logfile_syslog_test.2
check_output "all_logs does not work?" <<EOF check_output "all_logs does not work?" <<EOF
log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_format,log_msg_format,log_msg_schema log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_msg_format,log_msg_schema
0,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,syslog_log,# is up,aff2bfc3c61e7b86329b83190f0912b3 0,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,# is up,aff2bfc3c61e7b86329b83190f0912b3
1,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,syslog_log,# is up,aff2bfc3c61e7b86329b83190f0912b3 1,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,# is up,aff2bfc3c61e7b86329b83190f0912b3
2,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,syslog_log,# is down,506560b3c73dee057732e69a3c666718 2,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,# is down,506560b3c73dee057732e69a3c666718
EOF EOF
@ -823,11 +823,11 @@ run_test ${lnav_test} -n \
${test_dir}/logfile_syslog.0 ${test_dir}/logfile_syslog.0
check_output "syslog_log table is not working" <<EOF check_output "syslog_log table is not working" <<EOF
log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_hostname,log_msgid,log_pid,log_pri,log_procname,log_struct,syslog_version log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_hostname,log_msgid,log_pid,log_pri,log_procname,log_struct,log_syslog_tag,syslog_version
0,<NULL>,2007-11-03 09:23:38.000,0,error,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,7998,<NULL>,automount,<NULL>,<NULL> 0,<NULL>,2007-11-03 09:23:38.000,0,error,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,7998,<NULL>,automount,<NULL>,automount[7998],<NULL>
1,<NULL>,2007-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,16442,<NULL>,automount,<NULL>,<NULL> 1,<NULL>,2007-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,16442,<NULL>,automount,<NULL>,automount[16442],<NULL>
2,<NULL>,2007-11-03 09:23:38.000,0,error,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,7999,<NULL>,automount,<NULL>,<NULL> 2,<NULL>,2007-11-03 09:23:38.000,0,error,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,7999,<NULL>,automount,<NULL>,automount[7999],<NULL>
3,<NULL>,2007-11-03 09:47:02.000,1404000,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,<NULL> 3,<NULL>,2007-11-03 09:47:02.000,1404000,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,sudo,<NULL>
EOF EOF
@ -841,13 +841,13 @@ EOF
run_test ${lnav_test} -n \ run_test ${lnav_test} -n \
-c ";select * from syslog_log where log_time >= datetime('2007-11-03T09:47:02.000')" \ -c ";select log_line from syslog_log where log_time >= datetime('2007-11-03T09:47:02.000')" \
-c ':write-csv-to -' \ -c ':write-csv-to -' \
${test_dir}/logfile_syslog.0 ${test_dir}/logfile_syslog.0
check_output "log_time collation is wrong" <<EOF check_output "log_time collation is wrong" <<EOF
log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_hostname,log_msgid,log_pid,log_pri,log_procname,log_struct,syslog_version log_line
3,<NULL>,2007-11-03 09:47:02.000,1404000,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,<NULL> 3
EOF EOF
@ -858,8 +858,8 @@ run_test ${lnav_test} -n \
${test_dir}/logfile_syslog.0 ${test_dir}/logfile_syslog.0
check_output "logline table is not working" <<EOF check_output "logline table is not working" <<EOF
log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_hostname,log_msgid,log_pid,log_pri,log_procname,log_struct,syslog_version,log_msg_instance,col_0,TTY,PWD,USER,COMMAND log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_comment,log_tags,log_filters,log_hostname,log_msgid,log_pid,log_pri,log_procname,log_struct,log_syslog_tag,syslog_version,log_msg_instance,col_0,TTY,PWD,USER,COMMAND
0,<NULL>,2007-11-03 09:47:02.000,0,info,0,<NULL>,<NULL>,[1],veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,<NULL>,0,timstack,pts/6,/auto/wstimstack/rpms/lbuild/test,root,/usr/bin/tail /var/log/messages 0,<NULL>,2007-11-03 09:47:02.000,0,info,0,<NULL>,<NULL>,[1],veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,sudo,<NULL>,0,timstack,pts/6,/auto/wstimstack/rpms/lbuild/test,root,/usr/bin/tail /var/log/messages
EOF EOF

@ -43,8 +43,8 @@ A ················└ carriage-return
S 17 ┋before <123> after ┋ S 17 ┋before <123> after ┋
A ········└ fg(#008080), inverse A ········└ fg(#008080), inverse
A ···········└ normal A ···········└ normal
S 17 ┋ ┋ A ··················└ carriage-return
A ········└ normal A └ normal
CSI Erase all CSI Erase all
CSI Use normal screen buffer CSI Use normal screen buffer
CTRL restore cursor CTRL restore cursor

Loading…
Cancel
Save