[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:
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
log message regular expressions. Using the new "management CLI"
(activated by the -m option), a log format can be created from
a regular expression entry on regex101.com and existing patterns
can be edited.
* 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.
Breaking Changes:

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

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

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

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

@ -36,14 +36,11 @@ static auto intern_lifetime = intern_string::get_table_lifetime();
all_logs_vtab::all_logs_vtab()
: 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(
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(
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_schema_meta.lvm_identifier = true;
}
@ -51,8 +48,6 @@ all_logs_vtab::all_logs_vtab()
void
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(
vtab_column(this->alv_msg_meta.lvm_name.get())
.with_comment(
@ -71,7 +66,6 @@ all_logs_vtab::extract(std::shared_ptr<logfile> lf,
std::vector<logline_value>& values)
{
auto format = lf->get_format();
values.emplace_back(this->alv_value_meta, format->get_name());
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);
}
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
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;
if (lc.is_eof()) {

@ -51,12 +51,9 @@ public:
shared_buffer_ref& line,
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;
private:
logline_value_meta alv_value_meta;
logline_value_meta alv_msg_meta;
logline_value_meta alv_schema_meta;
shared_buffer alv_schema_manager;

@ -526,10 +526,16 @@ public:
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,
const string_attr_pair& sap,
const char* fill)
const F& fill)
{
bool init = true;
for (const auto& elem : container) {
@ -543,6 +549,21 @@ public:
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,
const attr_line_t& al,
text_wrap_settings* tws = nullptr);

@ -88,6 +88,17 @@ invoke(Fn&& f, Args&&... args) noexcept(
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 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
#define lnav_itertools_hh
#include <algorithm>
#include <memory>
#include <set>
#include <type_traits>
#include <vector>
@ -76,6 +105,16 @@ struct append {
T p_value;
};
struct nth {
nonstd::optional<size_t> a_index;
};
struct skip {
size_t a_count;
};
struct unique {};
} // namespace details
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>
inline details::filter_in<F>
filter_in(F func)
@ -163,6 +218,12 @@ map(F func)
return details::mapper<F>{func};
}
inline auto
deref()
{
return map([](auto iter) { return *iter; });
}
template<typename R, typename T>
inline details::folder<R, T>
fold(R func, T init)
@ -170,6 +231,12 @@ fold(R func, T init)
return details::folder<R, T>{func, init};
}
inline details::unique
unique()
{
return details::unique{};
}
inline details::sorted
sorted()
{
@ -195,12 +262,15 @@ chain(const T& value1, const Args&... args)
} // namespace lnav
template<typename C, typename P>
nonstd::optional<typename C::value_type>
operator|(const C& in, const lnav::itertools::details::find_if<P>& finder)
nonstd::optional<std::conditional_t<
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) {
if (lnav::func::invoke(finder.fi_predicate, elem)) {
return nonstd::make_optional(elem);
for (auto iter = in.begin(); iter != in.end(); ++iter) {
if (lnav::func::invoke(finder.fi_predicate, *iter)) {
return nonstd::make_optional(iter);
}
}
@ -222,6 +292,56 @@ operator|(const C& in, const lnav::itertools::details::find<T>& finder)
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>
C
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;
}
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>
T
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;
}
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>
auto
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>
auto
operator|(const std::vector<std::shared_ptr<T>>& in,
operator|(const std::vector<T>& in,
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)())>>
{
using return_type = std::vector<typename std::remove_const_t<decltype((
(*in.front()).*mapper.m_func)())>>;
*(*in.begin()).*mapper.m_func)())>>;
return_type retval;
retval.reserve(in.size());
@ -358,6 +502,25 @@ operator|(const std::vector<std::shared_ptr<T>>& in,
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>
auto
operator|(nonstd::optional<T> in,
@ -372,6 +535,21 @@ operator|(nonstd::optional<T> in,
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>
T
operator|(nonstd::optional<T> in,
@ -383,11 +561,11 @@ operator|(nonstd::optional<T> in,
template<typename T, typename F>
auto
operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<std::remove_const_t<decltype(((typename T::value_type{})
.*mapper.m_func)())>>
-> std::vector<
std::remove_const_t<decltype(((*in.begin()).*mapper.m_func)())>>
{
using return_type = std::vector<std::remove_const_t<decltype((
(typename T::value_type{}).*mapper.m_func)())>>;
using return_type = std::vector<
std::remove_const_t<decltype(((*in.begin()).*mapper.m_func)())>>;
return_type retval;
retval.reserve(in.size());

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

@ -110,6 +110,7 @@ enum class role_t : int32_t {
VCR_H6,
VCR_LIST_GLYPH,
VCR_BREADCRUMB,
VCR__MAX
};
@ -119,7 +120,8 @@ using string_attr_value = mapbox::util::variant<int64_t,
const intern_string_t,
std::string,
std::shared_ptr<logfile>,
bookmark_metadata*>;
bookmark_metadata*,
timespec>;
class string_attr_type_base {
public:
@ -200,6 +202,22 @@ status(S str)
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>
inline std::pair<S, string_attr_pair>
ok(S str)
@ -256,6 +274,30 @@ comment(S str)
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>
inline std::pair<S, string_attr_pair>
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));
}
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 roles

@ -76,7 +76,8 @@ bookmark_type_t::find_type(const std::string& name)
{
return get_all_types()
| 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*>&

@ -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)) {
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();
refresh();
}
@ -98,8 +96,6 @@ sql_progress_finished()
}
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_views[LNV_DB].redo_search();
}
@ -926,6 +922,17 @@ exec_context::exec_context(std::vector<logline_value>* line_values,
this->ec_source.emplace(
lnav::console::snippet::from(COMMAND_SRC, "").with_line(1));
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

@ -56,6 +56,9 @@ int sql_callback(exec_context& ec, sqlite3_stmt* stmt);
using pipe_callback_t
= 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 {
enum class perm_t {
READ_WRITE,
@ -117,14 +120,23 @@ struct exec_context {
void clear_output();
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()
{
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 {
@ -144,7 +156,32 @@ struct exec_context {
{
this->ec_source.emplace(
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()
@ -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};
bool ec_dry_run{false};
perm_t ec_perms{perm_t::READ_WRITE};
@ -174,6 +237,7 @@ struct exec_context {
sql_callback_t ec_sql_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(

@ -330,6 +330,7 @@ data_parser::pairup(data_parser::schema_id_t* schema,
case DT_IPV6_ADDRESS:
case DT_MAC_ADDRESS:
case DT_HEX_DUMP:
case DT_XML_DECL_TAG:
case DT_XML_OPEN_TAG:
case DT_XML_CLOSE_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])+)"),
},
{
"xmld",
pcrepp("\\A(<!\\??[\\w:]+\\s*(?:[\\w:]+(?:\\s*=\\s*"
"(?:\"((?:\\\\.|[^\"])+)\"|'((?:\\\\.|[^'])+)'|[^>]+)"
"))*\\s*>)"),
},
{
"xmlt",
pcrepp("\\A(<\\??[\\w:]+\\s*(?:[\\w:]+(?:\\s*=\\s*"

@ -46,6 +46,7 @@ enum data_token_t {
DT_TIME,
DT_IPV6_ADDRESS,
DT_HEX_DUMP,
DT_XML_DECL_TAG,
DT_XML_EMPTY_TAG,
DT_XML_OPEN_TAG,
DT_XML_CLOSE_TAG,
@ -119,7 +120,7 @@ public:
if (!line.empty() && line[line.length() - 1] == '.') {
this->ds_pcre_input.pi_length -= 1;
}
};
}
data_scanner(shared_buffer_ref& line,
size_t off = 0,
@ -132,20 +133,14 @@ public:
if (line.length() > 0 && line.get_data()[line.length() - 1] == '.') {
this->ds_pcre_input.pi_length -= 1;
}
};
}
bool tokenize(pcre_context& pc, data_token_t& token_out);
bool tokenize2(pcre_context& pc, data_token_t& token_out);
pcre_input& get_input()
{
return this->ds_pcre_input;
};
pcre_input& get_input() { return this->ds_pcre_input; }
void reset()
{
this->ds_pcre_input.reset_next_offset();
};
void reset() { this->ds_pcre_input.reset_next_offset(); }
private:
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); }
"<""?"?[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);
}
"<"[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);
}
"</"[a-zA-Z0-9:\-]+SPACE*">" {
"</"[a-zA-Z0-9_:\-]+SPACE*">" {
RET(DT_XML_CLOSE_TAG);
}

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

@ -6,10 +6,10 @@
"url": "http://en.wikipedia.org/wiki/Syslog",
"regex": {
"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": {
"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",
@ -17,7 +17,7 @@
"error": "(?:(?:(?<![a-zA-Z]))(?:(?i)error(?:s)?)(?:(?![a-zA-Z]))|failed|failure)",
"warning": "(?:(?:(?i)warn)|not responding|init: cannot execute)"
},
"opid-field": "log_pid",
"opid-field": "log_syslog_tag",
"multiline": true,
"module-field": "log_procname",
"value": {
@ -50,6 +50,11 @@
],
"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": {
"kind": "string",
"identifier": true

@ -45,7 +45,6 @@
#include "lnav_util.hh"
#include "log_data_helper.hh"
#include "plain_text_source.hh"
#include "pretty_printer.hh"
#include "readline_highlighters.hh"
#include "shlex.hh"
#include "sql_util.hh"
@ -55,10 +54,7 @@
class logline_helper {
public:
logline_helper(logfile_sub_source& lss)
: lh_sub_source(lss){
};
logline_helper(logfile_sub_source& lss) : lh_sub_source(lss) {}
logline& move_to_msg_start()
{
@ -71,7 +67,7 @@ public:
}
return (*lf)[cl];
};
}
logline& current_line()
{
@ -79,7 +75,7 @@ public:
std::shared_ptr<logfile> lf = this->lh_sub_source.find(cl);
return (*lf)[cl];
};
}
void annotate()
{
@ -95,7 +91,7 @@ public:
this->lh_string_attrs,
this->lh_line_values,
false);
};
}
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(const listview_curses&) = delete;
listview_curses(listview_curses&) = delete;
void set_title(const std::string& title) { this->lv_title = title; }
const std::string& get_title() const { return this->lv_title; }
@ -506,7 +509,7 @@ public:
this->lv_tail_space = space;
return *this;
};
}
void log_state()
{

@ -89,6 +89,7 @@
#include "bookmarks.hh"
#include "bottom_status_source.hh"
#include "bound_tags.hh"
#include "breadcrumb_curses.hh"
#include "CLI/CLI.hpp"
#include "dump_internals.hh"
#include "environ_vtab.hh"
@ -126,6 +127,7 @@
#include "textfile_highlighters.hh"
#include "textview_curses.hh"
#include "top_status_source.hh"
#include "view_helpers.crumbs.hh"
#include "view_helpers.examples.hh"
#include "view_helpers.hist.hh"
#include "views_vtab.hh"
@ -275,6 +277,8 @@ force_linking(services::main_t anno)
}
} // namespace injector
static breadcrumb_curses breadcrumb_view;
bool
setup_logline_table(exec_context& ec)
{
@ -835,8 +839,22 @@ handle_key(int ch)
default: {
switch (lnav_data.ld_mode) {
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);
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::FILES:
return handle_config_ui_key(ch);
@ -1048,6 +1066,15 @@ looper()
view_colors& vc = view_colors::singleton();
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_TEXT].get_highlights());
@ -1092,10 +1119,6 @@ looper()
lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]);
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,
&lnav_data.ld_bottom_source));
sb.push_back(bind_mem(&bottom_status_source::update_percent,
@ -1112,6 +1135,9 @@ looper()
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++) {
lnav_data.ld_views[lpc].set_window(lnav_data.ld_window);
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_status[LNS_TOP].set_top(0);
lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
for (auto& sc : lnav_data.ld_status) {
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_bottom_source);
lnav_data.ld_status[LNS_FILTER].set_data_source(
@ -1256,7 +1280,6 @@ looper()
gettimeofday(&current_time, nullptr);
lnav_data.ld_top_source.update_time(current_time);
lnav_data.ld_preview_view.set_needs_update();
layout_views();
@ -1349,6 +1372,11 @@ looper()
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_doc_view.do_update();
lnav_data.ld_example_view.do_update();
@ -1378,6 +1406,7 @@ looper()
default:
break;
}
breadcrumb_view.do_update();
if (lnav_data.ld_mode != ln_mode_t::FILTER
&& lnav_data.ld_mode != ln_mode_t::FILES)
{
@ -1457,6 +1486,8 @@ looper()
int ch;
while ((ch = getch()) != ERR) {
lnav_data.ld_user_message_source.clear();
alerter::singleton().new_input(ch);
lnav_data.ld_input_dispatcher.new_input(current_time,
@ -1467,8 +1498,6 @@ looper()
ch, tc->get_top());
};
lnav_data.ld_user_message_source.clear();
if (!lnav_data.ld_looping) {
// No reason to keep processing input after the
// user has quit. The view stack will also be
@ -1484,6 +1513,7 @@ looper()
case ln_mode_t::FILES:
next_rescan_time = next_status_update_time + 1s;
break;
case ln_mode_t::BREADCRUMBS:
case ln_mode_t::COMMAND:
case ln_mode_t::SEARCH:
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_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]
.set_sub_source(&lnav_data.ld_help_source)
.set_word_wrap(true);

@ -72,7 +72,6 @@
#include "sql_util.hh"
#include "statusview_curses.hh"
#include "textfile_sub_source.hh"
#include "top_status_source.hh"
#include "view_helpers.hh"
enum {
@ -92,7 +91,6 @@ extern const char* lnav_zoom_strings[];
/** The status bars. */
typedef enum {
LNS_TOP,
LNS_BOTTOM,
LNS_FILTER,
LNS_FILTER_HELP,
@ -195,7 +193,6 @@ struct lnav_data_t {
ln_mode_t ld_last_config_mode{ln_mode_t::FILTER};
statusview_curses ld_status[LNS__MAX];
top_status_source ld_top_source;
bottom_status_source ld_bottom_source;
filter_status_source ld_filter_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()) {
return;
}
lnav_data.ld_top_source.update_time();
for (auto& sc : lnav_data.ld_status) {
sc.do_update();
}

@ -35,7 +35,7 @@
#include "base/result.h"
#include "base/string_util.hh"
#include "fmt/format.h"
#include "fts_fuzzy_match.hh"
#include "itertools.similar.hh"
#include "log_format.hh"
#include "log_format_ext.hh"
#include "mapbox/variant.hpp"
@ -44,69 +44,6 @@
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 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);
} else {
return ec.make_error(
"expecting line number/percentage, timestamp, or relative "
"time");
auto um = lnav::console::user_message::error(
attr_line_t("invalid argument: ").append(args[1]))
.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) {

@ -647,6 +647,13 @@ static const struct json_path_container theme_styles_handlers = {
return &root->lt_style_list_glyph;
})
.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 = {
@ -863,7 +870,7 @@ static const struct json_path_container highlighter_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>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
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 entry.fd == fd;
})
| lnav::itertools::map(&pollfd::revents)
| lnav::itertools::deref() | lnav::itertools::map(&pollfd::revents)
| 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) {
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);
}
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());
}

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

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

@ -127,7 +127,7 @@ class generic_log_format : public log_format {
};
return log_fmt;
};
}
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
{
return intern_string::lookup("generic_log");
};
}
void scrub(std::string& line) override
{
@ -155,7 +155,7 @@ class generic_log_format : public log_format {
line = new_line;
}
};
}
scan_result_t scan(logfile& lf,
std::vector<logline>& dst,
@ -194,7 +194,7 @@ class generic_log_format : public log_format {
}
return SCAN_NO_MATCH;
};
}
void annotate(uint64_t line_number,
shared_buffer_ref& line,
@ -232,12 +232,12 @@ class generic_log_format : public log_format {
lr.lr_start = prefix_len;
lr.lr_end = line.length();
sa.emplace_back(lr, SA_BODY.value());
};
}
std::shared_ptr<log_format> specialized(int fmt_lock) override
{
return std::make_shared<generic_log_format>(*this);
};
}
};
std::string
@ -320,7 +320,7 @@ struct separated_string {
: i_parent(ss), i_pos(pos), i_next_pos(pos), i_index(0)
{
this->update();
};
}
void update()
{
@ -334,7 +334,7 @@ struct separated_string {
} else {
this->i_next_pos = ss.ss_str + ss.ss_len;
}
};
}
iterator& operator++()
{
@ -343,7 +343,7 @@ struct separated_string {
this->i_index += 1;
return *this;
};
}
string_fragment operator*()
{
@ -356,23 +356,20 @@ struct separated_string {
end = this->i_next_pos - ss.ss_str;
}
return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
};
}
bool operator==(const iterator& other) const
{
return (&this->i_parent == &other.i_parent)
&& (this->i_pos == other.i_pos);
};
}
bool operator!=(const iterator& other) const
{
return !(*this == other);
};
}
size_t index() const
{
return this->i_index;
};
size_t index() const { return this->i_index; }
};
iterator begin()
@ -397,7 +394,9 @@ public:
int col,
log_format* 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,
bool identifier = false,
@ -407,7 +406,7 @@ public:
this->fd_meta.lvm_identifier = identifier;
this->fd_collator = collator;
return *this;
};
}
field_def& with_numeric_index(int index)
{
@ -420,21 +419,21 @@ public:
{
this->lf_is_self_describing = true;
this->lf_time_ordered = false;
};
}
const intern_string_t get_name() const override
{
static const intern_string_t name(intern_string::lookup("bro"));
return this->blf_format_name.empty() ? name : this->blf_format_name;
};
}
void clear() override
{
this->log_format::clear();
this->blf_format_name.clear();
this->blf_field_defs.clear();
};
}
scan_result_t scan_int(std::vector<logline>& dst,
const line_info& li,
@ -664,7 +663,7 @@ public:
this->lf_value_stats.clear();
return SCAN_NO_MATCH;
};
}
void annotate(uint64_t line_number,
shared_buffer_ref& sbr,
@ -707,7 +706,7 @@ public:
values.emplace_back(fd.fd_meta);
}
}
};
}
const logline_value_stats* stats_for_value(
const intern_string_t& name) const override
@ -725,12 +724,12 @@ public:
}
return retval;
};
}
std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
{
return std::make_shared<bro_log_format>(*this);
};
}
class bro_log_table : public log_format_vtab_impl {
public:
@ -753,7 +752,7 @@ public:
"",
type_pair.second);
}
};
}
void get_foreign_keys(
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;
return retval;
};
}
std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
{
@ -794,7 +793,7 @@ public:
}
return retval;
};
}
void get_subline(const logline& ll,
shared_buffer_ref& sbr,
@ -833,7 +832,7 @@ struct ws_separated_string {
: i_parent(ss), i_pos(pos), i_next_pos(pos)
{
this->update();
};
}
void update()
{
@ -859,7 +858,7 @@ struct ws_separated_string {
this->i_next_pos += 1;
}
}
};
}
iterator& operator++()
{
@ -875,7 +874,7 @@ struct ws_separated_string {
this->i_index += 1;
return *this;
};
}
string_fragment operator*()
{
@ -883,34 +882,25 @@ struct ws_separated_string {
int end = this->i_next_pos - ss.ss_str;
return string_fragment(ss.ss_str, this->i_pos - ss.ss_str, end);
};
}
bool operator==(const iterator& other) const
{
return (&this->i_parent == &other.i_parent)
&& (this->i_pos == other.i_pos);
};
}
bool operator!=(const iterator& other) const
{
return !(*this == other);
};
}
size_t index() const
{
return this->i_index;
};
size_t index() const { return this->i_index; }
};
iterator begin()
{
return {*this, this->ss_str};
};
iterator begin() { return {*this, this->ss_str}; }
iterator end()
{
return {*this, this->ss_str + this->ss_len};
};
iterator end() { return {*this, this->ss_str + this->ss_len}; }
};
class w3c_log_format : public log_format {
@ -955,7 +945,7 @@ public:
this->fd_meta.lvm_identifier = identifier;
this->fd_collator = collator;
return *this;
};
}
field_def& with_numeric_index(int index)
{
@ -982,14 +972,14 @@ public:
{
this->lf_is_self_describing = true;
this->lf_time_ordered = false;
};
}
const intern_string_t get_name() const override
{
static const intern_string_t name(intern_string::lookup("w3c"));
return this->wlf_format_name.empty() ? name : this->wlf_format_name;
};
}
void clear() override
{
@ -997,7 +987,7 @@ public:
this->wlf_time_scanner.clear();
this->wlf_format_name.clear();
this->wlf_field_defs.clear();
};
}
scan_result_t scan_int(std::vector<logline>& dst,
const line_info& li,
@ -1255,7 +1245,7 @@ public:
this->lf_value_stats.clear();
return SCAN_NO_MATCH;
};
}
void annotate(uint64_t line_number,
shared_buffer_ref& sbr,
@ -1297,7 +1287,7 @@ public:
values.emplace_back(fd.fd_meta);
}
}
};
}
const logline_value_stats* stats_for_value(
const intern_string_t& name) const override
@ -1315,12 +1305,12 @@ public:
}
return retval;
};
}
std::shared_ptr<log_format> specialized(int fmt_lock = -1) override
{
return std::make_shared<w3c_log_format>(*this);
};
}
class w3c_log_table : public log_format_vtab_impl {
public:
@ -1372,7 +1362,7 @@ public:
static std::map<intern_string_t, std::shared_ptr<w3c_log_table>> retval;
return retval;
};
}
std::shared_ptr<log_vtab_impl> get_vtab_impl() const override
{
@ -1390,7 +1380,7 @@ public:
}
return retval;
};
}
void get_subline(const logline& ll,
shared_buffer_ref& sbr,
@ -1570,7 +1560,7 @@ public:
static const auto FIELDS = std::string("fields");
cols.emplace_back(FIELDS);
};
}
};
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
{
return std::make_shared<logfmt_format>(*this);
};
}
};
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"(
-- 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_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_body TEXT HIDDEN, -- The body of the log message
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;
}
if (lc.lc_opid && lf_iter->get_opid() != lc.lc_opid.value().value) {
return false;
}
return true;
}
@ -300,6 +307,7 @@ vt_open(sqlite3_vtab* p_svt, sqlite3_vtab_cursor** pp_cursor)
*pp_cursor = (sqlite3_vtab_cursor*) p_cur;
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_end_line = vis_line_t(p_vt->lss->text_line_count());
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(
ctx, level_name, strlen(level_name), SQLITE_STATIC);
} break;
break;
}
case VT_COL_MARK: {
sqlite3_result_int(ctx, ll->is_marked());
} break;
break;
}
case VT_COL_LOG_COMMENT: {
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) {
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;
}
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();
sqlite3_result_text(
ctx, fn.c_str(), fn.length(), SQLITE_STATIC);
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;
lf->read_full_message(ll, line);
@ -586,7 +633,7 @@ vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
SQLITE_TRANSIENT);
break;
}
case 3: {
case 6: {
if (vc->line_values.empty()) {
lf->read_full_message(ll, vc->log_msg);
vt->vi->extract(
@ -609,7 +656,7 @@ vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
}
break;
}
case 4: {
case 7: {
auto read_res = lf->read_raw_message(ll);
if (read_res.isErr()) {
@ -827,26 +874,31 @@ vt_rowid(sqlite3_vtab_cursor* cur, sqlite_int64* p_rowid)
}
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) {
vl = -1_vl;
}
this->lc_opid = nonstd::nullopt;
switch (op) {
case SQLITE_INDEX_CONSTRAINT_EQ:
if (vl < this->lc_end_line) {
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;
case SQLITE_INDEX_CONSTRAINT_GE:
this->lc_curr_line = vl;
break;
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;
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;
case SQLITE_INDEX_CONSTRAINT_LT:
this->lc_end_line = vl;
@ -867,6 +919,7 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
= (sqlite3_index_info::sqlite3_index_constraint*) idxStr;
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_end_line = vis_line_t(vt->lss->text_line_count());
vt_next(p_vtc);
@ -876,10 +929,13 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
}
for (int lpc = 0; lpc < idxNum; lpc++) {
switch (index[lpc].iColumn) {
auto col = index[lpc].iColumn;
switch (col) {
case VT_COL_LINE_NUMBER:
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;
case VT_COL_LOG_TIME:
@ -892,7 +948,7 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
dts.scan((const char*) datestr,
strlen((const char*) datestr),
NULL,
nullptr,
&mytm,
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;
} else {
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;
}
}
}
@ -914,6 +1035,10 @@ vt_filter(sqlite3_vtab_cursor* p_vtc,
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;
}
@ -931,24 +1056,50 @@ vt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info)
}
for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
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;
}
switch (p_info->aConstraint[lpc].iColumn) {
case VT_COL_LINE_NUMBER:
auto col = p_info->aConstraint[lpc].iColumn;
switch (col) {
case VT_COL_LINE_NUMBER: {
log_debug("line number index %d", p_info->aConstraint[lpc].op);
argvInUse += 1;
indexes.push_back(p_info->aConstraint[lpc]);
p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
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) {
log_debug("fall back to log_time");
for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
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;
}

@ -57,21 +57,26 @@ enum {
class logfile_sub_source;
struct log_cursor {
struct opid_hash {
unsigned int value : 6;
};
vis_line_t lc_curr_line;
int lc_sub_index;
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()
{
this->lc_curr_line = this->lc_end_line = vis_line_t(0);
enum class constraint_t {
none,
unique,
};
bool is_eof() const
{
return this->lc_curr_line >= this->lc_end_line;
};
void update(unsigned char op, vis_line_t vl, constraint_t cons);
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";

@ -34,6 +34,7 @@
#define logfile_hh
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@ -358,10 +359,12 @@ public:
using note_map = std::map<note_type, std::string>;
using safe_notes = safe::Safe<note_map>;
note_map get_notes() const
{
return *this->lf_notes.readAccess();
}
note_map get_notes() const { 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:
/**
@ -407,6 +410,7 @@ private:
text_format_t lf_text_format{text_format_t::TF_UNKNOWN};
uint32_t lf_out_of_time_order_count{0};
safe_notes lf_notes;
safe_opid_map lf_opids;
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 filt->get_index() == 0;
});
})
| lnav::itertools::deref();
}
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);
}
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 {
public:
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;
@ -150,6 +152,72 @@ private:
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
* source of data for a text view.
@ -332,7 +400,7 @@ public:
const_iterator 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;
}
}
@ -363,7 +431,7 @@ public:
this->lss_line_size_cache[index].first = row;
}
return this->lss_line_size_cache[index].second;
};
}
void text_mark(const bookmark_type_t* bm, vis_line_t line, bool added);
@ -477,6 +545,30 @@ public:
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(
const struct timeval& start) const;
@ -507,7 +599,7 @@ public:
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]];
}
@ -521,6 +613,11 @@ public:
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);
/**
@ -557,7 +654,7 @@ public:
bool is_visible() const
{
return this->ld_visible;
return this->get_file_ptr() != nullptr && this->ld_visible;
}
void set_visibility(bool vis)
@ -649,7 +746,7 @@ public:
: public grep_proc_source<vis_line_t>
, public grep_proc_sink<vis_line_t> {
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,
std::string& value_out) override;
@ -683,21 +780,19 @@ public:
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,
iterator ld,
logfile::const_iterator ll);
void invalidate_sql_filter();
void set_line_meta_changed()
{
this->lss_line_meta_changed = true;
}
void set_line_meta_changed() { this->lss_line_meta_changed = true; }
bool is_line_meta_changed() const
{
return this->lss_line_meta_changed;
}
bool is_line_meta_changed() const { 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_LINES_PER_FILE = 256 * 1024 * 1024;
@ -726,7 +821,7 @@ private:
};
struct __attribute__((__packed__)) indexed_content {
indexed_content() {}
indexed_content() = default;
indexed_content(content_line_t cl) : ic_value(cl) {}
@ -739,7 +834,8 @@ private:
};
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,
const content_line_t& rhs) const
{
@ -789,8 +885,10 @@ private:
};
struct filtered_logline_cmp {
filtered_logline_cmp(const logfile_sub_source& lc)
: llss_controller(lc){};
filtered_logline_cmp(const logfile_sub_source& lc) : llss_controller(lc)
{
}
bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
{
content_line_t cl_lhs
@ -820,7 +918,9 @@ private:
*/
struct logfile_data_eq {
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
{
@ -834,7 +934,7 @@ private:
{
this->lss_line_size_cache.fill(std::make_pair(0, 0));
this->lss_line_size_cache[0].first = -1;
};
}
bool check_extra_filters(iterator ld, logfile::iterator ll);
@ -877,6 +977,7 @@ private:
size_t lss_longest_line{0};
meta_grepper lss_meta_grepper;
log_location_history lss_location_history;
exec_context* lss_exec_context;
bool lss_in_value_for_line{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 "base/attr_line.hh"
#include "base/file_range.hh"
#include "textview_curses.hh"
class plain_text_source
: public text_sub_source
, public vis_location_history {
public:
plain_text_source() = default;
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(text.substr(start, len));
start = end + 1;
struct text_line {
text_line(file_off_t off, attr_line_t value)
: tl_offset(off), tl_value(std::move(value))
{
}
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)
{
this->replace_with(text_lines);
}
file_off_t tl_offset;
attr_line_t tl_value;
};
plain_text_source(const std::vector<attr_line_t>& text_lines)
{
this->tds_lines = text_lines;
this->tds_longest_line = this->compute_longest_line();
}
plain_text_source() = default;
plain_text_source& replace_with(const attr_line_t& text_lines)
{
this->tds_lines.clear();
text_lines.split_lines(this->tds_lines);
this->tds_longest_line = this->compute_longest_line();
return *this;
}
plain_text_source(const std::string& text);
plain_text_source& replace_with(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;
}
plain_text_source(const std::vector<std::string>& text_lines);
void clear()
{
this->tds_lines.clear();
this->tds_longest_line = 0;
this->tds_text_format = text_format_t::TF_UNKNOWN;
}
plain_text_source(const std::vector<attr_line_t>& text_lines);
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_lines.pop_back();
}
this->tds_reverse_selection = val;
return *this;
}
size_t text_line_count()
{
return this->tds_lines.size();
}
plain_text_source& replace_with(const attr_line_t& text_lines);
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
{
return this->tds_lines.empty();
}
size_t text_line_width(textview_curses& curses)
{
return this->tds_longest_line;
}
size_t text_line_width(textview_curses& curses) override;
void text_value_for_line(textview_curses& tc,
int row,
std::string& value_out,
line_flags_t flags)
{
value_out = this->tds_lines[row].get_string();
}
line_flags_t flags) override;
void text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out)
{
value_out = this->tds_lines[line].get_attrs();
}
string_attrs_t& value_out) override;
size_t text_size_for_line(textview_curses& tc, int row, line_flags_t flags)
{
return this->tds_lines[row].length();
}
size_t text_size_for_line(textview_curses& tc,
int row,
line_flags_t flags) override;
text_format_t get_text_format() const
{
return this->tds_text_format;
}
text_format_t get_text_format() const override;
const std::vector<attr_line_t>& get_lines() const
{
return this->tds_lines;
}
const std::vector<text_line>& get_lines() const { return this->tds_lines; }
plain_text_source& set_text_format(text_format_t format)
{
@ -151,24 +111,20 @@ public:
return *this;
}
nonstd::optional<location_history*> get_location_history()
nonstd::optional<location_history*> get_location_history() override
{
return this;
}
private:
size_t compute_longest_line()
{
size_t retval = 0;
for (auto& iter : this->tds_lines) {
retval = std::max(retval, (size_t) iter.length());
}
return retval;
};
protected:
size_t compute_longest_line();
nonstd::optional<vis_line_t> line_for_offset(file_off_t off);
std::vector<attr_line_t> tds_lines;
std::vector<text_line> tds_lines;
text_format_t tds_text_format{text_format_t::TF_UNKNOWN};
size_t tds_longest_line{0};
bool tds_reverse_selection{false};
};
#endif // LNAV_PLAIN_TEXT_SOURCE_HH

@ -35,14 +35,26 @@
void
pretty_printer::append_to(attr_line_t& al)
{
auto& pi = this->pp_scanner->get_input();
pcre_context_static<30> pc;
data_token_t dt;
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)) {
element el(dt, pc);
switch (dt) {
case DT_XML_DECL_TAG:
case DT_XML_EMPTY_TAG:
if (this->pp_is_xml && this->pp_line_length > 0) {
this->start_new_line();
@ -56,6 +68,10 @@ pretty_printer::append_to(attr_line_t& al)
if (this->pp_is_xml) {
this->start_new_line();
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();
} else {
this->pp_values.emplace_back(el);
@ -64,6 +80,7 @@ pretty_printer::append_to(attr_line_t& al)
case DT_XML_CLOSE_TAG:
this->flush_values();
this->ascend();
this->append_child_node();
this->write_element(el);
this->start_new_line();
continue;
@ -73,6 +90,8 @@ pretty_printer::append_to(attr_line_t& al)
this->flush_values(true);
this->pp_values.emplace_back(el);
this->descend();
this->pp_interval_state.back().is_start
= this->pp_stream.tellp();
continue;
case DT_RCURLY:
case DT_RSQUARE:
@ -87,8 +106,11 @@ pretty_printer::append_to(attr_line_t& al)
case DT_COMMA:
if (this->pp_depth > 0) {
this->flush_values(true);
this->append_child_node();
this->write_element(el);
this->start_new_line();
this->pp_interval_state.back().is_start
= this->pp_stream.tellp();
continue;
}
break;
@ -117,6 +139,21 @@ pretty_printer::append_to(attr_line_t& al)
al.append("\n");
}
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
@ -142,7 +179,7 @@ pretty_printer::write_element(const pretty_printer::element& el)
}
return;
}
pcre_input& pi = this->pp_scanner->get_input();
auto& pi = this->pp_scanner->get_input();
if (this->pp_line_length == 0) {
this->append_indent();
}
@ -208,12 +245,36 @@ pretty_printer::append_indent()
bool
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;
while (!this->pp_values.empty()) {
{
element& el = this->pp_values.front();
auto& el = 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
&& (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY)) {
if (this->pp_line_length > 0) {
@ -253,6 +314,11 @@ pretty_printer::ascend()
this->pp_depth -= 1;
this->pp_body_lines.pop();
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 {
this->pp_body_lines.top() = 0;
}
@ -263,4 +329,78 @@ pretty_printer::descend()
{
this->pp_depth += 1;
this->pp_body_lines.push(0);
this->pp_interval_state.resize(this->pp_depth + 1);
this->pp_hier_nodes.push_back(std::make_unique<hier_node>());
}
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
#include <deque>
#include <map>
#include <sstream>
#include <stack>
#include <utility>
#include <vector>
#include <sys/types.h>
#include "base/attr_line.hh"
#include "base/file_range.hh"
#include "base/opt_util.hh"
#include "data_scanner.hh"
#include "intervaltree/IntervalTree.h"
class pretty_printer {
public:
using key_t = mapbox::util::variant<std::string, size_t>;
using pretty_interval = interval_tree::Interval<file_off_t, key_t>;
using pretty_tree = interval_tree::IntervalTree<file_off_t, key_t>;
struct element {
element(data_token_t token, pcre_context& pc)
: e_token(token), e_capture(*pc.all())
{
}
element(data_token_t token, pcre_context::capture_t& cap)
: e_token(token), e_capture(cap)
{
}
data_token_t e_token;
pcre_context::capture_t e_capture;
};
struct 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)
: pp_leading_indent(leading_indent), pp_scanner(ds),
pp_attrs(std::move(sa))
@ -63,14 +98,28 @@ public:
this->pp_scanner->reset();
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;
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);
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:
void descend();
@ -84,6 +133,13 @@ private:
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_depth{0};
int pp_line_length{0};
@ -95,6 +151,10 @@ private:
std::deque<element> pp_values{};
int pp_shift_accum{0};
bool pp_is_xml{false};
std::vector<interval_state> pp_interval_state;
std::vector<pretty_interval> pp_intervals;
std::vector<std::unique_ptr<hier_node>> pp_hier_nodes;
std::unique_ptr<hier_node> pp_hier_stage;
};
#endif

@ -486,6 +486,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
return;
}
case ln_mode_t::BREADCRUMBS:
case ln_mode_t::PAGING:
case ln_mode_t::FILTER:
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);
switch (old_mode) {
case ln_mode_t::BREADCRUMBS:
case ln_mode_t::PAGING:
case ln_mode_t::FILTER:
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(
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_expiration
= std::chrono::steady_clock::now() + 20s;

@ -194,14 +194,18 @@ add_view_text_possibilities(readline_curses* rlc,
rlc->clear_possibilities(context, type);
for (vis_line_t curr_line = tc->get_top(); curr_line <= tc->get_bottom();
++curr_line)
{
std::string line;
if (tc->get_inner_height() > 0_vl) {
for (vis_line_t curr_line = tc->get_top();
curr_line <= tc->get_bottom();
++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);

@ -520,10 +520,14 @@ load_time_bookmarks()
}
for (const char* upgrade_stmt : UPGRADE_STMTS) {
if (sqlite3_exec(db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out())
!= SQLITE_OK)
{
log_error("unable to upgrade bookmark table -- %s", errmsg.in());
auto rc = sqlite3_exec(
db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out());
if (rc != SQLITE_OK) {
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 "base/auto_mem.hh"
#include "base/intern_string.hh"
#include "base/lnav_log.hh"
class shared_buffer;
@ -47,12 +48,11 @@ class shared_buffer;
struct shared_buffer_ref {
public:
shared_buffer_ref(char* data = nullptr, size_t len = 0)
: sb_owner(nullptr), sb_data(data), sb_length(len){};
~shared_buffer_ref()
: sb_owner(nullptr), sb_data(data), sb_length(len)
{
this->disown();
};
}
~shared_buffer_ref() { this->disown(); }
shared_buffer_ref(const shared_buffer_ref& other)
{
@ -61,7 +61,7 @@ public:
this->sb_length = 0;
this->copy_ref(other);
};
}
shared_buffer_ref(shared_buffer_ref&& other) noexcept;
@ -73,27 +73,21 @@ public:
}
return *this;
};
}
bool empty() const
{
return this->sb_data == nullptr || this->sb_length == 0;
};
}
const char* get_data() const
{
return this->sb_data;
};
const char* get_data() const { return this->sb_data; }
const char* get_data_at(off_t offset) const
{
return &this->sb_data[offset];
};
}
size_t length() const
{
return this->sb_length;
};
size_t length() const { return this->sb_length; }
shared_buffer_ref& rtrim(bool pred(char))
{
@ -110,7 +104,7 @@ public:
const char* buffer_end = this->sb_data + this->sb_length;
return (this->sb_data <= ptr && ptr < buffer_end);
};
}
char* get_writable_data()
{
@ -119,7 +113,13 @@ public:
}
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);
@ -140,15 +140,9 @@ private:
class shared_buffer {
public:
~shared_buffer()
{
this->invalidate_refs();
}
~shared_buffer() { this->invalidate_refs(); }
void add_ref(shared_buffer_ref& ref)
{
this->sb_refs.push_back(&ref);
};
void add_ref(shared_buffer_ref& ref) { this->sb_refs.push_back(&ref); }
bool invalidate_refs()
{
@ -161,7 +155,7 @@ public:
}
return retval;
};
}
std::vector<shared_buffer_ref*> sb_refs;
};

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

@ -29,6 +29,7 @@
#include "textfile_sub_source.hh"
#include "base/itertools.hh"
#include "config.h"
size_t
@ -235,3 +236,42 @@ textfile_sub_source::get_text_format() const
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 vis_location_history {
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() = default;
~textfile_sub_source() override = default;
bool empty() const { return this->tss_files.empty(); }
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()
? 0
@ -62,15 +62,15 @@ public:
void text_value_for_line(textview_curses& tc,
int line,
std::string& value_out,
line_flags_t flags);
line_flags_t flags) override;
void text_attrs_for_line(textview_curses& tc,
int row,
string_attrs_t& value_out);
string_attrs_t& value_out) override;
size_t text_size_for_line(textview_curses& tc,
int line,
line_flags_t flags);
line_flags_t flags) override;
std::shared_ptr<logfile> current_file() const
{
@ -81,7 +81,7 @@ public:
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()) {
return "";
@ -179,19 +179,22 @@ public:
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;
}
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override;
private:
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();
}
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/lnav_log.hh"
#include "bookmarks.hh"
#include "breadcrumb.hh"
#include "grep_proc.hh"
#include "highlighter.hh"
#include "listview_curses.hh"
@ -385,14 +386,16 @@ public:
*/
virtual void text_mark(const bookmark_type_t* bm,
vis_line_t line,
bool added){};
bool added)
{
}
/**
* Clear the bookmarks for a particular type in the text source.
*
* @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.
@ -404,7 +407,9 @@ public:
*/
virtual void text_attrs_for_line(textview_curses& tc,
int line,
string_attrs_t& value_out){};
string_attrs_t& value_out)
{
}
/**
* 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.
*/
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)
{
@ -444,11 +449,10 @@ public:
return nonstd::nullopt;
}
void toggle_apply_filters()
{
this->tss_apply_filters = !this->tss_apply_filters;
this->text_filters_changed();
}
void toggle_apply_filters();
virtual void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs);
bool tss_supports_filtering{false};
bool tss_apply_filters{true};

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

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

@ -16,7 +16,6 @@
},
"styles": {
"identifier": {
"background-color": "$black",
"color": "semantic()"
},
"text": {
@ -92,6 +91,9 @@
},
"list-glyph": {
"color": "$yellow"
},
"breadcrumb": {
"color": "#99a"
}
},
"syntax-styles": {
@ -132,6 +134,9 @@
},
"file": {
"color": "$blue"
},
"number": {
"bold": true
}
},
"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;
}
std::string get_unique_path() const
{
return this->ups_unique_path;
}
const std::string& get_unique_path() const { return this->ups_unique_path; }
virtual ghc::filesystem::path get_path() const = 0;

@ -49,7 +49,8 @@ using namespace std::chrono_literals;
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()},
};
ui_periodic_timer::ui_periodic_timer() : upt_counter(0)
{
@ -188,8 +189,11 @@ view_curses::mvwattrline(WINDOW* window,
full_line = expanded_line;
auto& vc = view_colors::singleton();
auto text_attrs = vc.attrs_for_role(base_role);
auto attrs = text_attrs;
auto text_attrs = vc.attrs_for_role(role_t::VCR_TEXT);
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);
wattron(window, attrs);
if (lr_bytes.lr_start < (int) full_line.size()) {
@ -334,6 +338,27 @@ view_curses::mvwattrline(WINDOW* window,
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);
for (int lpc = 0; lpc < ch_width; lpc++) {
bool clear_rev = false;
@ -887,6 +912,12 @@ view_colors::init_roles(const lnav_theme& lt,
lt.lt_style_list_glyph,
lt.lt_style_text,
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;

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

@ -29,19 +29,25 @@
#include "view_helpers.hh"
#include "base/humanize.hh"
#include "base/itertools.hh"
#include "config.h"
#include "environ_vtab.hh"
#include "help-txt.h"
#include "intervaltree/IntervalTree.h"
#include "lnav.hh"
#include "lnav.indexing.hh"
#include "pretty_printer.hh"
#include "shlex.hh"
#include "sql_help.hh"
#include "sql_util.hh"
#include "view_helpers.crumbs.hh"
#include "view_helpers.examples.hh"
#include "view_helpers.hist.hh"
#include "vtab_module.hh"
using namespace std::chrono_literals;
const char* lnav_view_strings[LNV__MAX + 1] = {
"log",
"text",
@ -73,7 +79,7 @@ view_from_string(const char* name)
return nonstd::nullopt;
}
auto view_name_iter
auto* view_name_iter
= std::find_if(std::begin(lnav_view_strings),
std::end(lnav_view_strings),
[&](const char* v) {
@ -111,15 +117,174 @@ open_schema_view()
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
open_pretty_view()
{
static const char* NOTHING_MSG = "Nothing to pretty-print";
textview_curses* top_tc = *lnav_data.ld_view_stack.top();
textview_curses* pretty_tc = &lnav_data.ld_views[LNV_PRETTY];
textview_curses* log_tc = &lnav_data.ld_views[LNV_LOG];
textview_curses* text_tc = &lnav_data.ld_views[LNV_TEXT];
auto* top_tc = *lnav_data.ld_view_stack.top();
auto* pretty_tc = &lnav_data.ld_views[LNV_PRETTY];
auto* log_tc = &lnav_data.ld_views[LNV_LOG];
auto* text_tc = &lnav_data.ld_views[LNV_TEXT];
attr_line_t full_text;
delete pretty_tc->get_sub_source();
@ -129,6 +294,9 @@ open_pretty_view()
return;
}
std::vector<pretty_printer::pretty_interval> all_intervals;
std::vector<std::unique_ptr<pretty_printer::hier_node>> hier_nodes;
std::vector<pretty_sub_source::hier_interval_t> hier_tree_vec;
if (top_tc == log_tc) {
logfile_sub_source& lss = lnav_data.ld_log_source;
bool first_line = true;
@ -146,7 +314,7 @@ open_pretty_view()
auto ll_start = lf->message_start(ll);
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(
*log_tc,
vl,
@ -157,21 +325,33 @@ open_pretty_view()
al.apply_hide();
}
line_range orig_lr
const auto orig_lr
= find_string_attr_range(al.get_attrs(), &SA_ORIGINAL_LINE);
attr_line_t orig_al
= al.subline(orig_lr.lr_start, orig_lr.length());
attr_line_t prefix_al = al.subline(0, orig_lr.lr_start);
data_scanner ds(orig_al.get_string());
pretty_printer pp(&ds, orig_al.get_attrs());
const auto body_lr
= find_string_attr_range(al.get_attrs(), &SA_BODY);
auto orig_al = al.subline(orig_lr.lr_start, orig_lr.length());
auto prefix_al = al.subline(0, orig_lr.lr_start);
attr_line_t pretty_al;
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);
auto curr_intervals = pp.take_intervals();
auto line_hier_root = pp.take_hier_root();
auto line_off = 0;
for (auto& pretty_line : pretty_lines) {
if (pretty_line.empty() && &pretty_line == &pretty_lines.back())
{
@ -179,32 +359,76 @@ open_pretty_view()
}
pretty_line.insert(0, prefix_al);
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);
}
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()) {
full_text.erase(full_text.length() - 1, 1);
}
} else if (top_tc == 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();
++vl) {
auto ll = lf->begin() + vl;
shared_buffer_ref sbr;
lf->read_full_message(ll, sbr);
data_scanner ds(sbr);
if (text_tc->listview_rows(*text_tc)) {
auto lf = lnav_data.ld_text_source.current_file();
std::string all_lines;
for (vis_line_t vl = text_tc->get_top();
vl <= text_tc->get_bottom();
++vl) {
auto ll = lf->begin() + vl;
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;
pretty_printer pp(&ds, sa);
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);
pretty_tc->set_sub_source(pts);
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_FILES)
&& !preview_status_open && !doc_open;
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
int filter_height = filters_open ? 5 : 0;
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)
+ (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_enabled(filters_open);
lnav_data.ld_status[LNS_FILTER].set_top(
@ -361,6 +585,8 @@ layout_views()
lnav_data.ld_status[LNS_FILTER_HELP].set_top(
-(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_enabled(!filters_open
&& !breadcrumb_open);
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_PREVIEW].set_top(height - bottom_height
@ -624,6 +850,9 @@ ensure_view(textview_curses* expected_tc)
bool
ensure_view(lnav_view_t expected)
{
require(expected >= 0);
require(expected < LNV__MAX);
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();
}
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. */
enum class ln_mode_t : int {
PAGING,
BREADCRUMBS,
FILTER,
FILES,
COMMAND,

@ -34,12 +34,15 @@
#include <unistd.h>
#include "base/injector.bind.hh"
#include "base/itertools.hh"
#include "base/lnav_log.hh"
#include "base/opt_util.hh"
#include "config.h"
#include "lnav.hh"
#include "sql_util.hh"
#include "view_curses.hh"
#include "vtab_module_json.hh"
#include "yajlpp/yajlpp_def.hh"
template<>
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> {
static constexpr const char* NAME = "lnav_views";
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.
paused INTEGER, -- Indicates if the view is paused and will not load new data.
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());
break;
case 9: {
auto tss = tc.get_sub_source();
auto* tss = tc.get_sub_source();
if (tss != nullptr && tss->tss_supports_filtering) {
sqlite3_result_int(ctx, tss->tss_apply_filters);
@ -226,6 +279,53 @@ CREATE TABLE lnav_views (
}
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;
@ -256,7 +356,8 @@ CREATE TABLE lnav_views (
const char* top_file,
bool is_paused,
const char* search,
bool do_filtering)
bool do_filtering,
const char* top_meta)
{
textview_curses& tc = lnav_data.ld_views[index];
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();
}
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 {

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

@ -37,16 +37,17 @@
#include <stdio.h>
#include <stdlib.h>
#include "data_parser.hh"
#include "view_curses.hh"
#include "base/injector.hh"
#include "config.h"
#include "data_parser.hh"
#include "data_scanner.hh"
#include "elem_to_json.hh"
#include "log_format.hh"
#include "log_format_loader.hh"
#include "logfile.hh"
#include "pretty_printer.hh"
#include "shared_buffer.hh"
#include "view_curses.hh"
const char* TMP_NAME = "scanned.tmp";
@ -153,11 +154,13 @@ main(int argc, char* argv[])
std::vector<logline> index;
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();
iter != root_formats.end() && !found;
++iter) {
line_info li = {{13}};
logfile* lf = nullptr; // XXX
(*iter)->clear();
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-06T22:00:49.124] INFO Shutting down servicebork bork bork
[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-06T22:00:49.124] INFO Shutting down servicebork bork bork
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] DEBUG3 Details...bork bork bork
[2013-09-06T22:00:59.124] DEBUG2 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: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
[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] DEBUG3 Details...bork bork bork
[2013-09-06T22:00:59.124] DEBUG2 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: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
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
| :goto invalid
= help: Synopsis

@ -43,10 +43,10 @@ run_test ${lnav_test} -n \
logfile_syslog_test.2
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
0,<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>,syslog_log,# is up,aff2bfc3c61e7b86329b83190f0912b3
2,<NULL>,2015-11-03 09:23:38.000,0,info,0,<NULL>,<NULL>,<NULL>,syslog_log,# is down,506560b3c73dee057732e69a3c666718
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>,# 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>,# is down,506560b3c73dee057732e69a3c666718
EOF
@ -823,11 +823,11 @@ run_test ${lnav_test} -n \
${test_dir}/logfile_syslog.0
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
0,<NULL>,2007-11-03 09:23:38.000,0,error,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,7998,<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>,<NULL>
2,<NULL>,2007-11-03 09:23:38.000,0,error,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,7999,<NULL>,automount,<NULL>,<NULL>
3,<NULL>,2007-11-03 09:47:02.000,1404000,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,<NULL>
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>,automount[7998],<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>,automount[7999],<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
@ -841,13 +841,13 @@ EOF
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 -' \
${test_dir}/logfile_syslog.0
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
3,<NULL>,2007-11-03 09:47:02.000,1404000,info,0,<NULL>,<NULL>,<NULL>,veridian,<NULL>,<NULL>,<NULL>,sudo,<NULL>,<NULL>
log_line
3
EOF
@ -858,8 +858,8 @@ run_test ${lnav_test} -n \
${test_dir}/logfile_syslog.0
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
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
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>,sudo,<NULL>,0,timstack,pts/6,/auto/wstimstack/rpms/lbuild/test,root,/usr/bin/tail /var/log/messages
EOF

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

Loading…
Cancel
Save