mirror of
https://github.com/tstack/lnav.git
synced 2024-10-05 17:17:37 +03:00
[views] add a breadcrumb view
This commit is contained in:
parent
2c4b1d3886
commit
f03f9e704f
15
NEWS
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*>&
|
||||
|
124
src/breadcrumb.hh
Normal file
124
src/breadcrumb.hh
Normal file
@ -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
|
397
src/breadcrumb_curses.cc
Normal file
397
src/breadcrumb_curses.cc
Normal file
@ -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;
|
||||
}
|
100
src/breadcrumb_curses.hh
Normal file
100
src/breadcrumb_curses.hh
Normal file
@ -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;
|
||||
|
98185
src/data_scanner_re.cc
98185
src/data_scanner_re.cc
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
|
||||
{
|
||||
|
133
src/itertools.similar.hh
Normal file
133
src/itertools.similar.hh
Normal file
@ -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()
|
||||
{
|
||||
|
49
src/lnav.cc
49
src/lnav.cc
@ -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(¤t_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};
|
||||
|
202
src/plain_text_source.cc
Normal file
202
src/plain_text_source.cc
Normal file
@ -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:
|
||||
struct text_line {
|
||||
text_line(file_off_t off, attr_line_t value)
|
||||
: tl_offset(off), tl_value(std::move(value))
|
||||
{
|
||||
}
|
||||
|
||||
bool contains_offset(file_off_t off) const
|
||||
{
|
||||
return (this->tl_offset <= off
|
||||
&& off < this->tl_offset + this->tl_value.length());
|
||||
}
|
||||
|
||||
file_off_t tl_offset;
|
||||
attr_line_t tl_value;
|
||||
};
|
||||
|
||||
plain_text_source() = default;
|
||||
|
||||
plain_text_source(const std::string& text)
|
||||
{
|
||||
size_t start = 0, end;
|
||||
plain_text_source(const std::string& text);
|
||||
|
||||
while ((end = text.find('\n', start)) != std::string::npos) {
|
||||
size_t len = (end - start);
|
||||
this->tds_lines.emplace_back(text.substr(start, len));
|
||||
start = end + 1;
|
||||
}
|
||||
if (start < text.length()) {
|
||||
this->tds_lines.emplace_back(text.substr(start));
|
||||
}
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
}
|
||||
plain_text_source(const std::vector<std::string>& text_lines);
|
||||
|
||||
plain_text_source(const std::vector<std::string>& text_lines)
|
||||
{
|
||||
this->replace_with(text_lines);
|
||||
}
|
||||
plain_text_source(const std::vector<attr_line_t>& text_lines);
|
||||
|
||||
plain_text_source(const std::vector<attr_line_t>& text_lines)
|
||||
plain_text_source& set_reverse_selection(bool val)
|
||||
{
|
||||
this->tds_lines = text_lines;
|
||||
this->tds_longest_line = this->compute_longest_line();
|
||||
}
|
||||
|
||||
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();
|
||||
this->tds_reverse_selection = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
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& replace_with(const attr_line_t& 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& replace_with(const std::vector<std::string>& text_lines);
|
||||
|
||||
plain_text_source& truncate_to(size_t max_lines)
|
||||
{
|
||||
while (this->tds_lines.size() > max_lines) {
|
||||
this->tds_lines.pop_back();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
void clear();
|
||||
|
||||
size_t text_line_count()
|
||||
{
|
||||
return this->tds_lines.size();
|
||||
}
|
||||
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();
|
||||
|
||||
std::vector<attr_line_t> tds_lines;
|
||||
nonstd::optional<vis_line_t> line_for_offset(file_off_t off);
|
||||
|
||||
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": {
|
||||
|
346
src/third-party/intervaltree/IntervalTree.h
vendored
Normal file
346
src/third-party/intervaltree/IntervalTree.h
vendored
Normal file
@ -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();
|
||||
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;
|
||||
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);
|
||||
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;
|
||||
}
|
37
src/view_helpers.crumbs.hh
Normal file
37
src/view_helpers.crumbs.hh
Normal file
@ -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…
Reference in New Issue
Block a user