1
1
mirror of https://github.com/tstack/lnav.git synced 2024-08-16 16:30:25 +03:00

[files] add a detail view to the files panel

This commit is contained in:
Tim Stack 2024-06-01 09:10:04 -07:00
parent e6aac98193
commit 9694d93ff3
45 changed files with 832 additions and 286 deletions

View File

@ -8,6 +8,10 @@ Features:
so that you can manually set an opid on log messages that
don't have one. Setting an opid allows messages to show
up in the timeline view.
* The Files panel now has a details view on the right side
that shows extra information about the selected file.
You can look here for details of why lnav selected a
particular log format.
* Add support for GitHub Markdown Alerts.
* Added the `:xopen` command that will open the given paths
using an external opener like `open` or `xdg-open`.

View File

@ -104,14 +104,19 @@ To hide the panels again, press :kbd:`q`.
Screenshot of the files panel showing the loaded files.
The Files panel is open initially to display progress in loading files.
The following information can be displayed for each file:
The following information is displayed for each file:
* the "unique" portion of the path relative to the other files and
* the amount of data that has been indexed.
To the right of the file list is a panel that shows details for each
file, such as:
* the "unique" portion of the path relative to the other files;
* the amount of data that has been indexed;
* the date range of log messages contained in the file;
* the errors that were encountered while trying to index the file;
* the notes recorded for files where some automatic action was taken,
like hiding the file if it was seen as a duplicate of another file.
like hiding the file if it was seen as a duplicate of another file;
* the details of the demultiplexing and log format matching process.
.. figure:: lnav-filters-panel.png
:align: center

View File

@ -301,7 +301,7 @@ public:
size_t start_len = this->al_string.length();
this->al_string.append(std::move(value.first));
this->append(std::move(value.first));
line_range lr{(int) start_len, (int) this->al_string.length()};

View File

@ -180,6 +180,30 @@ string_fragment::trim() const
return this->trim(" \t\r\n");
}
string_fragment
string_fragment::rtrim(const char* tokens) const
{
string_fragment retval = *this;
while (retval.sf_begin < retval.sf_end) {
bool found = false;
for (int lpc = 0; tokens[lpc] != '\0'; lpc++) {
if (retval.sf_string[retval.sf_end - 1] == tokens[lpc]) {
found = true;
break;
}
}
if (!found) {
break;
}
retval.sf_end -= 1;
}
return retval;
}
std::optional<string_fragment>
string_fragment::consume_n(int amount) const
{

View File

@ -281,6 +281,12 @@ struct string_fragment {
string_fragment sub_range(int begin, int end) const
{
if (this->sf_begin + begin > this->sf_end) {
begin = this->sf_end - this->sf_begin;
}
if (this->sf_begin + end > this->sf_end) {
end = this->sf_end - this->sf_begin;
}
return string_fragment{
this->sf_string, this->sf_begin + begin, this->sf_begin + end};
}
@ -575,6 +581,7 @@ struct string_fragment {
}
string_fragment trim(const char* tokens) const;
string_fragment rtrim(const char* tokens) const;
string_fragment trim() const;
string_fragment prepend(const char* str, int amount) const

View File

@ -35,7 +35,11 @@
#include "base/injector.hh"
#include "base/lnav_log.hh"
#include "base/paths.hh"
#include "base/snippet_highlighters.hh"
#include "piper.looper.cfg.hh"
#include "readline_highlighters.hh"
using namespace lnav::roles::literals;
namespace lnav {
namespace piper {
@ -88,7 +92,7 @@ multiplex_matcher::match(const string_fragment& line)
for (const auto& demux_pair : cfg.c_demux_definitions) {
const auto& df = demux_pair.second;
if (!df.dd_valid || !df.dd_enabled) {
if (!df.dd_valid) {
continue;
}
@ -109,24 +113,86 @@ multiplex_matcher::match(const string_fragment& line)
log_info(" demuxer pattern matched");
if (!md[df.dd_muxid_capture_index].has_value()) {
log_info(" however, mux_id was not captured");
auto match_um = lnav::console::user_message::warning(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched, however the ")
.append("mux_id"_symbol)
.append(" was not captured"));
this->mm_details.emplace_back(match_um);
continue;
}
if (!md[df.dd_body_capture_index].has_value()) {
log_info(" however, body was not captured");
auto match_um = lnav::console::user_message::warning(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched, however the ")
.append("body"_symbol)
.append(" was not captured"));
this->mm_details.emplace_back(match_um);
continue;
}
log_info(" and required captures were found, using demuxer");
return found{demux_pair.first};
if (df.dd_enabled) {
auto match_um = lnav::console::user_message::ok(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched line ")
.append(lnav::roles::number(
fmt::to_string(this->mm_line_count))));
this->mm_details.emplace_back(match_um);
return found{demux_pair.first};
}
auto config_al = attr_line_t().append(
fmt::format(FMT_STRING(":config /log/demux/{}/enabled "
"true"),
demux_pair.first));
readline_lnav_highlighter(config_al, -1);
auto match_um
= lnav::console::user_message::info(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched line ")
.append(lnav::roles::number(
fmt::to_string(this->mm_line_count)))
.append(", however, it is disabled"))
.with_help(
attr_line_t("Use ")
.append_quoted(
lnav::roles::quoted_code(config_al))
.append(" to enable this demuxer"));
this->mm_details.emplace_back(match_um);
}
auto partial_size = df.dd_pattern.pp_value->match_partial(
line.sub_range(0, 1024));
if (partial_size > 0) {
log_info(" only partial match by pattern: %s",
df.dd_pattern.pp_value->get_pattern().c_str());
log_info(" %s",
line.sub_range(0, partial_size).to_string().c_str());
auto regex_al = attr_line_t(df.dd_pattern.pp_value->get_pattern());
lnav::snippets::regex_highlighter(
regex_al, -1, line_range{0, (int) regex_al.length()});
auto in_line = line.sub_range(0, 1024).rtrim("\n").to_string();
auto esc_res
= fmt::v10::detail::find_escape(&in_line[0], &(*in_line.end()));
if (esc_res.end != nullptr) {
in_line = fmt::format(FMT_STRING("{:?}"), in_line);
}
auto note = attr_line_t("pattern: ")
.append(regex_al)
.append("\n ")
.append(lnav::roles::quoted_code(in_line))
.append("\n")
.append(partial_size + 2, ' ')
.append("^ matched up to here");
auto match_um = lnav::console::user_message::info(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" did not match line ")
.append(lnav::roles::number(
fmt::to_string(this->mm_line_count))))
.with_note(note);
this->mm_details.emplace_back(match_um);
}
if (df.dd_control_pattern.pp_value) {
md = df.dd_control_pattern.pp_value->create_match_data();
@ -141,6 +207,7 @@ multiplex_matcher::match(const string_fragment& line)
}
}
this->mm_line_count += 1;
if (this->mm_partial_match_ids.empty()) {
return not_found{};
}

View File

@ -30,6 +30,7 @@
#ifndef lnav_piper_file_hh
#define lnav_piper_file_hh
#include <filesystem>
#include <map>
#include <optional>
#include <set>
@ -39,7 +40,7 @@
#include "auto_mem.hh"
#include "base/intern_string.hh"
#include <filesystem>
#include "lnav.console.hh"
#include "mapbox/variant_io.hpp"
#include "time_util.hh"
@ -96,8 +97,11 @@ public:
match_result match(const string_fragment& line);
std::vector<lnav::console::user_message> mm_details;
private:
std::set<std::string> mm_partial_match_ids;
size_t mm_line_count{0};
};
} // namespace piper

View File

@ -79,6 +79,16 @@ strftime_rfc3339(
return index;
}
std::string
to_rfc3339_string(time64_t tim, int millis, char sep)
{
char buf[64];
strftime_rfc3339(buf, sizeof(buf), tim, millis, sep);
return buf;
}
static std::optional<Posix::time_zone>
get_posix_zone(const char* name)
{

View File

@ -46,11 +46,16 @@ namespace lnav {
using time64_t = uint64_t;
ssize_t strftime_rfc3339(char* buffer,
size_t buffer_size,
lnav::time64_t tim,
int millis,
char sep = ' ');
ssize_t strftime_rfc3339(
char* buffer, size_t buffer_size, time64_t tim, int millis, char sep = ' ');
std::string to_rfc3339_string(time64_t tim, int millis, char sep = ' ');
inline std::string
to_rfc3339_string(struct timeval tv, char sep = ' ')
{
return to_rfc3339_string(tv.tv_sec, tv.tv_usec / 1000, sep);
}
date::sys_info sys_time_to_info(date::sys_seconds secs);

View File

@ -245,13 +245,11 @@ bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt)
static void
execute_search(const std::string& search_cmd)
{
lnav_data.ld_view_stack.top() | [&search_cmd](auto tc) {
auto search_term
= string_fragment(search_cmd)
.find_right_boundary(0, string_fragment::tag1{'\n'})
.to_string();
tc->execute_search(search_term);
};
textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
auto search_term = string_fragment(search_cmd)
.find_right_boundary(0, string_fragment::tag1{'\n'})
.to_string();
tc->execute_search(search_term);
}
Result<std::string, lnav::console::user_message>

View File

@ -406,12 +406,15 @@ file_collection::watch_logfile(const std::string& filename,
}
}
auto ff = detect_file_format(filename);
auto ff_res = detect_file_format(filename);
loo.loo_file_format = ff;
switch (ff) {
loo.loo_file_format = ff_res.dffr_file_format;
switch (ff_res.dffr_file_format) {
case file_format_t::SQLITE_DB:
retval.fc_other_files[filename].ofd_format = ff;
retval.fc_other_files[filename].ofd_format
= ff_res.dffr_file_format;
retval.fc_other_files[filename].ofd_details
= ff_res.dffr_details;
break;
case file_format_t::MULTIPLEXED: {
@ -430,7 +433,10 @@ file_collection::watch_logfile(const std::string& filename,
looper_options);
if (create_res.isOk()) {
retval.fc_other_files[filename] = ff;
auto& ofd = retval.fc_other_files[filename];
ofd.ofd_format = ff_res.dffr_file_format;
ofd.ofd_details = ff_res.dffr_details;
retval.fc_file_names[filename] = loo;
retval.fc_file_names[filename].with_piper(
create_res.unwrap());
@ -498,7 +504,9 @@ file_collection::watch_logfile(const std::string& filename,
res.unwrapErr(),
});
} else {
retval.fc_other_files[filename] = ff;
auto& ofd = retval.fc_other_files[filename];
ofd.ofd_format = ff_res.dffr_file_format;
ofd.ofd_details = ff_res.dffr_details;
}
{
prog_iter_opt | [&prog](auto prog_iter) {
@ -512,6 +520,7 @@ file_collection::watch_logfile(const std::string& filename,
default: {
auto filename_to_open = filename;
loo.loo_match_details = ff_res.dffr_details;
auto eff = detect_mime_type(filename);
if (eff) {
@ -790,6 +799,14 @@ file_collection::rescan_files(bool required)
pair.second.loo_piper->get_out_pattern().string(),
pair.second,
required);
if (!pair.second.loo_piper.value().get_demux_id().empty()
&& this->fc_other_files.count(pair.first) == 0)
{
auto& ofd = retval.fc_other_files[pair.first];
ofd.ofd_format = file_format_t::MULTIPLEXED;
ofd.ofd_details
= pair.second.loo_piper.value().get_demux_details();
}
} else {
this->expand_filename(fq, pair.first, pair.second, required);
if (this->fc_rotated) {
@ -872,6 +889,18 @@ file_collection::copy()
return retval;
}
void
file_collection::clear()
{
this->fc_name_to_errors->writeAccess()->clear();
this->fc_file_names.clear();
this->fc_files.clear();
this->fc_renamed_files.clear();
this->fc_closed_files.clear();
this->fc_other_files.clear();
this->fc_new_stats.clear();
}
size_t
file_collection::other_file_format_count(file_format_t ff) const
{

View File

@ -67,6 +67,7 @@ using safe_scan_progress = safe::Safe<scan_progress>;
struct other_file_descriptor {
file_format_t ofd_format;
std::string ofd_description;
std::vector<lnav::console::user_message> ofd_details;
other_file_descriptor(file_format_t format = file_format_t::UNKNOWN,
std::string description = "")
@ -188,15 +189,7 @@ struct file_collection {
&& this->fc_other_files.empty();
}
void clear()
{
this->fc_name_to_errors->writeAccess()->clear();
this->fc_file_names.clear();
this->fc_files.clear();
this->fc_closed_files.clear();
this->fc_other_files.clear();
this->fc_new_stats.clear();
}
void clear();
bool is_below_open_file_limit() const
{

View File

@ -39,17 +39,17 @@
#include "config.h"
#include "line_buffer.hh"
file_format_t
detect_file_format_result
detect_file_format(const std::filesystem::path& filename)
{
detect_file_format_result retval = {file_format_t::UNKNOWN};
auto describe_res = archive_manager::describe(filename);
if (describe_res.isOk()
&& describe_res.unwrap().is<archive_manager::archive_info>())
{
return file_format_t::ARCHIVE;
return {file_format_t::ARCHIVE};
}
file_format_t retval = file_format_t::UNKNOWN;
auto open_res = lnav::filesystem::open_file(filename, O_RDONLY);
if (open_res.isErr()) {
log_error("unable to open file for format detection: %s -- %s",
@ -70,7 +70,7 @@ detect_file_format(const std::filesystem::path& filename)
if (header_frag.startswith(SQLITE3_HEADER)) {
log_info("%s: appears to be a SQLite DB", filename.c_str());
retval = file_format_t::SQLITE_DB;
retval.dffr_file_format = file_format_t::SQLITE_DB;
} else {
auto looping = true;
lnav::piper::multiplex_matcher mm;
@ -118,7 +118,8 @@ detect_file_format(const std::filesystem::path& filename)
log_info("%s: is multiplexed using %s",
filename.c_str(),
f.f_id.c_str());
retval = file_format_t::MULTIPLEXED;
retval.dffr_file_format
= file_format_t::MULTIPLEXED;
return false;
},
[](lnav::piper::multiplex_matcher::not_found nf) {
@ -130,6 +131,7 @@ detect_file_format(const std::filesystem::path& filename)
next_range = li.li_file_range;
}
retval.dffr_details = std::move(mm.mm_details);
}
}
}

View File

@ -32,10 +32,11 @@
#ifndef lnav_file_format_hh
#define lnav_file_format_hh
#include <filesystem>
#include <optional>
#include "base/lnav.console.hh"
#include "fmt/format.h"
#include <filesystem>
enum class file_format_t : int {
UNKNOWN,
@ -51,7 +52,13 @@ struct external_file_format {
std::filesystem::path eff_source_path;
};
file_format_t detect_file_format(const std::filesystem::path& filename);
struct detect_file_format_result {
file_format_t dffr_file_format{file_format_t::UNKNOWN};
std::vector<lnav::console::user_message> dffr_details;
};
detect_file_format_result detect_file_format(
const std::filesystem::path& filename);
std::optional<external_file_format> detect_mime_type(
const std::filesystem::path& filename);

View File

@ -34,6 +34,9 @@
#include "base/fs_util.hh"
#include "base/humanize.hh"
#include "base/humanize.network.hh"
#include "base/humanize.time.hh"
#include "base/itertools.enumerate.hh"
#include "base/itertools.hh"
#include "base/opt_util.hh"
#include "base/string_util.hh"
#include "config.h"
@ -48,7 +51,7 @@ files_list_selection
from_selection(vis_line_t sel_vis)
{
auto& fc = lnav_data.ld_active_files;
int sel = (int) sel_vis;
int sel = sel_vis;
{
safe::ReadAccess<safe_name_to_errors> errs(*fc.fc_name_to_errors);
@ -57,7 +60,8 @@ from_selection(vis_line_t sel_vis)
auto iter = errs->begin();
std::advance(iter, sel);
return error_selection::build(sel, iter->first);
return error_selection::build(
sel, std::make_pair(iter->first, iter->second.fei_description));
}
sel -= errs->size();
@ -83,7 +87,10 @@ from_selection(vis_line_t sel_vis)
}
} // namespace files_model
files_sub_source::files_sub_source() {}
files_sub_source::
files_sub_source()
{
}
bool
files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
@ -181,11 +188,11 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
[&](files_model::error_selection& es) {
auto& fc = lnav_data.ld_active_files;
fc.fc_file_names.erase(es.sb_iter);
fc.fc_file_names.erase(es.sb_iter.first);
auto name_iter = fc.fc_file_names.begin();
while (name_iter != fc.fc_file_names.end()) {
if (name_iter->first == es.sb_iter) {
if (name_iter->first == es.sb_iter.first) {
name_iter = fc.fc_file_names.erase(name_iter);
continue;
}
@ -196,7 +203,7 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
if (rp_opt) {
auto rp = *rp_opt;
if (fmt::to_string(rp.home()) == es.sb_iter) {
if (fmt::to_string(rp.home()) == es.sb_iter.first) {
fc.fc_other_files.erase(name_iter->first);
name_iter = fc.fc_file_names.erase(name_iter);
continue;
@ -205,7 +212,8 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
++name_iter;
}
fc.fc_name_to_errors->writeAccess()->erase(es.sb_iter);
fc.fc_name_to_errors->writeAccess()->erase(
es.sb_iter.first);
fc.fc_invalidate_merge = true;
lv.reload_data();
},
@ -246,8 +254,10 @@ files_sub_source::text_value_for_line(textview_curses& tc,
std::string& value_out,
text_sub_source::line_flags_t flags)
{
bool selected
= lnav_data.ld_mode == ln_mode_t::FILES && line == tc.get_selection();
bool selected = line == tc.get_selection();
role_t cursor_role = lnav_data.ld_mode == ln_mode_t::FILES
? role_t::VCR_CURSOR_LINE
: role_t::VCR_DISABLED_CURSOR_LINE;
const auto dim = tc.get_dimensions();
const auto& fc = lnav_data.ld_active_files;
auto filename_width
@ -280,8 +290,7 @@ files_sub_source::text_value_for_line(textview_curses& tc,
}
al.append(" ").append(iter->second.fei_description);
if (selected) {
al.with_attr_for_all(
VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED));
al.with_attr_for_all(VC_ROLE.value(cursor_role));
}
value_out = al.get_string();
@ -308,7 +317,7 @@ files_sub_source::text_value_for_line(textview_curses& tc,
.append(" ")
.append(iter->second.ofd_description);
if (selected) {
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED));
al.with_attr_for_all(VC_ROLE.value(cursor_role));
}
if (line == fc.fc_other_files.size() - 1) {
al.with_attr_for_all(VC_STYLE.value(text_attrs{A_UNDERLINE}));
@ -323,13 +332,8 @@ files_sub_source::text_value_for_line(textview_curses& tc,
const auto& lf = fc.fc_files[line];
auto ld_opt = lnav_data.ld_log_source.find_data(lf);
auto fn = fmt::to_string(std::filesystem::path(lf->get_unique_path()));
char start_time[64] = "", end_time[64] = "";
std::vector<std::string> file_notes;
if (lf->get_format() != nullptr) {
sql_strftime(start_time, sizeof(start_time), lf->front().get_timeval());
sql_strftime(end_time, sizeof(end_time), lf->back().get_timeval());
}
truncate_to(fn, filename_width);
for (const auto& pair : lf->get_notes()) {
file_notes.push_back(pair.second);
@ -355,19 +359,13 @@ files_sub_source::text_value_for_line(textview_curses& tc,
humanize::file_size(lf->get_index_size(),
humanize::alignment::columnar));
}
al.append(" ")
.append(start_time)
.append(" \u2014 ")
.append(end_time)
.append(" ")
.appendf(FMT_STRING("{}"), fmt::join(file_notes, "; "));
al.append(" ").appendf(FMT_STRING("{}"), fmt::join(file_notes, "; "));
if (selected) {
al.with_attr_for_all(VC_ROLE.value(role_t::VCR_FOCUSED));
al.with_attr_for_all(VC_ROLE.value(cursor_role));
}
value_out = al.get_string();
this->fss_last_line_len
= filename_width + 23 + strlen(start_time) + strlen(end_time);
this->fss_last_line_len = value_out.length();
}
void
@ -455,3 +453,179 @@ files_sub_source::text_handle_mouse(
return false;
}
void
files_sub_source::text_selection_changed(textview_curses& tc)
{
auto sel = files_model::from_selection(tc.get_selection());
std::vector<attr_line_t> details;
sel.match(
[](files_model::no_selection) {},
[&details](const files_model::error_selection& es) {
auto path = std::filesystem::path(es.sb_iter.first);
details.emplace_back(
attr_line_t().appendf(FMT_STRING("Full path: {}"), path));
details.emplace_back(attr_line_t(" ").append(
lnav::roles::error(es.sb_iter.second)));
},
[&details](const files_model::other_selection& os) {
auto path = std::filesystem::path(os.sb_iter->first);
details.emplace_back(
attr_line_t().appendf(FMT_STRING("Full path: {}"), path));
details.emplace_back(attr_line_t(" ").append("Match Details"_h3));
for (const auto& msg : os.sb_iter->second.ofd_details) {
auto msg_al = msg.to_attr_line();
for (auto& msg_line : msg_al.rtrim().split_lines()) {
msg_line.insert(0, 4, ' ');
details.emplace_back(msg_line);
}
}
},
[&details](const files_model::file_selection& fs) {
static constexpr auto NAME_WIDTH = 17;
auto lf = *fs.sb_iter;
auto path = lf->get_filename();
auto actual_path = lf->get_actual_path();
auto format = lf->get_format();
details.emplace_back(
attr_line_t()
.appendf(FMT_STRING("{}"), path)
.with_attr_for_all(VC_ROLE.value(role_t::VCR_IDENTIFIER)));
const auto notes = lf->get_notes();
if (!notes.empty()) {
details.emplace_back(
attr_line_t(" ").append("Notes"_h2).append(":"));
for (const auto& pair : notes) {
details.emplace_back(attr_line_t(" ").append(
lnav::roles::warning(pair.second)));
}
}
details.emplace_back(attr_line_t(" ").append("General"_h2));
if (actual_path) {
if (path != actual_path.value()) {
auto line = attr_line_t()
.append("Actual Path"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(lnav::roles::file(
fmt::to_string(actual_path.value())));
details.emplace_back(line);
}
} else {
details.emplace_back(attr_line_t().append(" Piped"));
}
details.emplace_back(
attr_line_t()
.append("MIME Type"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(fmt::to_string(lf->get_text_format())));
details.emplace_back(
attr_line_t()
.append("Last Modified"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(lnav::to_rfc3339_string(
convert_log_time_to_local(lf->get_modified_time()),
0,
'T')));
details.emplace_back(
attr_line_t()
.append("Size"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(humanize::file_size(lf->get_index_size(),
humanize::alignment::none)));
details.emplace_back(attr_line_t()
.append("Lines"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(lnav::roles::number(fmt::format(
FMT_STRING("{:L}"), lf->size()))));
if (format != nullptr) {
details.emplace_back(attr_line_t()
.append("Time Range"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(lnav::to_rfc3339_string(
lf->front().get_timeval(), 'T'))
.append(" - ")
.append(lnav::to_rfc3339_string(
lf->back().get_timeval(), 'T')));
details.emplace_back(
attr_line_t()
.append("Duration"_h3)
.right_justify(NAME_WIDTH)
.append(": ")
.append(humanize::time::duration::from_tv(
lf->back().get_timeval()
- lf->front().get_timeval())
.to_string()));
}
{
auto line
= attr_line_t(" ").append("Log Format"_h2).append(": ");
if (format != nullptr) {
line.append(lnav::roles::identifier(
format->get_name().to_string()));
} else {
line.append("(none)"_comment);
}
details.emplace_back(line);
}
auto match_msgs = lf->get_format_match_messages();
details.emplace_back(
attr_line_t(" ").append("Match Details"_h3));
for (const auto& msg : match_msgs) {
auto msg_al = msg.to_attr_line();
for (auto& msg_line : msg_al.rtrim().split_lines()) {
msg_line.insert(0, 6, ' ');
details.emplace_back(msg_line);
}
}
{
const auto& meta = lf->get_embedded_metadata();
if (!meta.empty()) {
details.emplace_back(
attr_line_t(" ").append("Embedded Metadata:"_h2));
for (const auto& [index, meta_pair] :
lnav::itertools::enumerate(meta))
{
details.emplace_back(
attr_line_t(" ")
.appendf(FMT_STRING("[{}]"), index)
.append(" ")
.append(lnav::roles::h3(meta_pair.first)));
details.emplace_back(
attr_line_t(" MIME Type: ")
.append(lnav::roles::symbol(fmt::to_string(
meta_pair.second.m_format))));
line_range lr{6, -1};
details.emplace_back(attr_line_t().with_attr(
string_attr{lr, VC_GRAPHIC.value(ACS_HLINE)}));
const auto val_sf = string_fragment::from_str(
meta_pair.second.m_value);
for (const auto val_line : val_sf.split_lines()) {
details.emplace_back(
attr_line_t(" ").append(val_line));
}
}
}
}
});
this->fss_details_source->replace_with(details);
}

View File

@ -31,6 +31,7 @@
#define files_sub_source_hh
#include "file_collection.hh"
#include "plain_text_source.hh"
#include "textview_curses.hh"
class files_sub_source
@ -65,8 +66,11 @@ public:
const listview_curses::display_line_content_t&,
mouse_event& me) override;
void text_selection_changed(textview_curses& tc) override;
size_t fss_last_line_len{0};
attr_line_t fss_curr_line;
plain_text_source* fss_details_source{nullptr};
};
struct files_overlay_source : public list_overlay_source {
@ -95,7 +99,9 @@ struct selection_base {
}
};
struct error_selection : public selection_base<error_selection, std::string> {};
struct error_selection
: public selection_base<error_selection,
std::pair<std::string, std::string>> {};
struct other_selection
: public selection_base<

View File

@ -47,8 +47,10 @@ static auto DELETE_HELP = ANSI_BOLD("D") ": Delete";
static auto FILTERING_HELP = ANSI_BOLD("f") ": ";
static auto JUMP_HELP = ANSI_BOLD("ENTER") ": Jump To";
static auto CLOSE_HELP = ANSI_BOLD("X") ": Close";
static auto FOCUS_DETAILS_HELP = ANSI_BOLD("CTRL-]") ": Focus on details view";
filter_status_source::filter_status_source()
filter_status_source::
filter_status_source()
{
this->tss_fields[TSF_TITLE].set_width(14);
this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE);
@ -102,6 +104,7 @@ filter_status_source::statusview_fields()
break;
case ln_mode_t::FILTER:
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
this->tss_fields[TSF_HELP].set_value(EXIT_MSG);
break;
default:
@ -110,6 +113,7 @@ filter_status_source::statusview_fields()
}
if (lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::FILE_DETAILS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES)
{
this->tss_fields[TSF_FILES_TITLE].set_value(
@ -231,7 +235,8 @@ filter_status_source::update_filtered(text_sub_source* tss)
}
}
filter_help_status_source::filter_help_status_source()
filter_help_status_source::
filter_help_status_source()
{
this->fss_help.set_min_width(10);
this->fss_help.set_share(1);
@ -292,10 +297,11 @@ filter_help_status_source::statusview_fields()
tss->tss_apply_filters ? "Disable Filtering"
: "Enable Filtering");
}
} else if (lnav_data.ld_mode == ln_mode_t::FILES
} else if ((lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::FILE_DETAILS)
&& lnav_data.ld_session_loaded)
{
auto& lv = lnav_data.ld_files_view;
const auto& lv = lnav_data.ld_files_view;
auto sel = files_model::from_selection(lv.get_selection());
sel.match(
@ -313,9 +319,16 @@ filter_help_status_source::statusview_fields()
if (ld_opt && !ld_opt.value()->ld_visible) {
vis_help = "Show";
}
const auto* focus_details_help
= lnav_data.ld_mode == ln_mode_t::FILES
? FOCUS_DETAILS_HELP
: "";
this->fss_help.set_value(
" %s%s %s", ENABLE_HELP, vis_help, JUMP_HELP);
this->fss_help.set_value(" %s%s %s %s",
ENABLE_HELP,
vis_help,
JUMP_HELP,
focus_details_help);
});
}
};

View File

@ -29,7 +29,6 @@ FORMAT_FILES = \
$(srcdir)/%reldir%/openamdb_log.json \
$(srcdir)/%reldir%/openstack_log.json \
$(srcdir)/%reldir%/page_log.json \
$(srcdir)/%reldir%/papertrail_log.json \
$(srcdir)/%reldir%/pcap_log.json \
$(srcdir)/%reldir%/procstate_log.json \
$(srcdir)/%reldir%/redis_log.json \

View File

@ -1,52 +0,0 @@
{
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
"papertrail_log": {
"title": "Papertrail Service",
"url": "https://papertrailapp.com/",
"description": "Log format for the papertrail log management service",
"json": true,
"hide-extra": true,
"file-pattern": "pt:.*",
"line-format": [
{
"field": "display_received_at"
},
" ",
{
"field": "hostname"
},
" ",
{
"field": "program"
},
": ",
{
"field": "message"
}
],
"level-field": "severity",
"level": {
"error": "Error",
"debug": "Debug",
"warning": "Warning",
"info": "Info(?:rmational)?|Notice",
"critical": "Crit(?:ical)?",
"fatal": "Emerg(?:ency)?|Alert"
},
"timestamp-field": "generated_at",
"body-field": "message",
"value": {
"display_received_at": {
"kind": "string"
},
"program": {
"kind": "string",
"identifier": true
},
"hostname": {
"kind": "string",
"identifier": true
}
}
}
}

View File

@ -244,6 +244,13 @@ line_buffer::gz_indexed::open(int fd, lnav::gzip::header& hd)
hd.h_mtime.tv_sec = gz_hd.time;
hd.h_name = std::string((char*) name);
hd.h_comment = std::string((char*) comment);
log_info(
"%d: read gzip header (mtime=%d; name='%s'; "
"comment='%s')",
fd,
hd.h_mtime.tv_sec,
hd.h_name.c_str(),
hd.h_comment.c_str());
break;
default:
log_error("%d: failed to read gzip header data", fd);

View File

@ -429,7 +429,7 @@ listview_curses::do_update()
}
wrap_width = width;
if (this->lv_show_scrollbar) {
if (this->lv_show_scrollbar && wrap_width > 0) {
wrap_width -= 1;
}
@ -541,6 +541,7 @@ listview_curses::do_update()
row_overlay_content[overlay_row].with_attr_for_all(
VC_ROLE.value(role_t::VCR_CURSOR_LINE));
}
this->lv_display_lines.push_back(
overlay_content{row, overlay_row});
mvwattrline(this->lv_window,

View File

@ -689,7 +689,21 @@ handle_config_ui_key(int ch, const char* keyseq)
switch (lnav_data.ld_mode) {
case ln_mode_t::FILES:
retval = lnav_data.ld_files_view.handle_key(ch);
if (ch == KEY_CTRL(']')) {
set_view_mode(ln_mode_t::FILE_DETAILS);
lnav_data.ld_mode = ln_mode_t::FILE_DETAILS;
retval = true;
} else {
retval = lnav_data.ld_files_view.handle_key(ch);
}
break;
case ln_mode_t::FILE_DETAILS:
if (ch == KEY_ESCAPE || ch == KEY_CTRL(']')) {
set_view_mode(ln_mode_t::FILES);
retval = true;
} else {
retval = lnav_data.ld_file_details_view.handle_key(ch);
}
break;
case ln_mode_t::FILTER:
retval = lnav_data.ld_filter_view.handle_key(ch);
@ -727,6 +741,7 @@ handle_config_ui_key(int ch, const char* keyseq)
}
lnav_data.ld_mode = new_mode.value();
lnav_data.ld_files_view.reload_data();
lnav_data.ld_file_details_view.reload_data();
lnav_data.ld_filter_view.reload_data();
lnav_data.ld_status[LNS_FILTER].set_needs_update();
} else {
@ -760,6 +775,7 @@ handle_key(int ch, const char* keyseq)
case ln_mode_t::FILTER:
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
return handle_config_ui_key(ch, keyseq);
case ln_mode_t::SPECTRO_DETAILS: {
@ -1349,6 +1365,18 @@ VALUES ('org.lnav.mouse-support', -1, DATETIME('now', '+1 minute'),
highlight_source_t::THEME);
lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay);
lnav_data.ld_file_details_view.set_title("File Details");
lnav_data.ld_file_details_view.set_selectable(true);
lnav_data.ld_file_details_view.set_window(lnav_data.ld_window);
lnav_data.ld_file_details_view.set_show_scrollbar(true);
lnav_data.ld_file_details_view.set_supports_marks(true);
lnav_data.ld_file_details_view.get_disabled_highlights().insert(
highlight_source_t::THEME);
lnav_data.ld_file_details_view.tc_cursor_role
= role_t::VCR_DISABLED_CURSOR_LINE;
lnav_data.ld_file_details_view.tc_disabled_cursor_role
= role_t::VCR_DISABLED_CURSOR_LINE;
lnav_data.ld_user_message_view.set_window(lnav_data.ld_window);
lnav_data.ld_spectro_details_view.set_title("spectro-details");
@ -1674,8 +1702,11 @@ VALUES ('org.lnav.mouse-support', -1, DATETIME('now', '+1 minute'),
break;
case ln_mode_t::SEARCH_FILES:
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
lnav_data.ld_files_view.set_needs_update();
lnav_data.ld_file_details_view.set_needs_update();
lnav_data.ld_files_view.do_update();
lnav_data.ld_file_details_view.do_update();
break;
default:
break;
@ -1785,6 +1816,7 @@ VALUES ('org.lnav.mouse-support', -1, DATETIME('now', '+1 minute'),
case ln_mode_t::PAGING:
case ln_mode_t::FILTER:
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
case ln_mode_t::SPECTRO_DETAILS:
case ln_mode_t::BUSY:
if (old_gen
@ -2054,6 +2086,10 @@ VALUES ('org.lnav.mouse-support', -1, DATETIME('now', '+1 minute'),
}
}
}
if (rescan_future.valid()) {
rescan_future.get();
}
} catch (readline_curses::error& e) {
log_error("error: %s", strerror(e.e_err));
}
@ -2303,13 +2339,19 @@ main(int argc, char* argv[])
SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
)";
log_info("performing cleanup");
lnav_data.ld_child_pollers.clear();
log_info("marking files as closed");
for (auto& lf : lnav_data.ld_active_files.fc_files) {
lf->close();
}
log_info("rebuilding after closures");
rebuild_indexes(ui_clock::now());
log_info("clearing file collection");
lnav_data.ld_active_files.clear();
log_info("dropping tables");
lnav_data.ld_vtab_manager = nullptr;
std::vector<std::string> tables_to_drop;
@ -2367,6 +2409,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
#endif
lnav_data.ld_db.reset();
log_info("cleanup finished");
});
#ifdef HAVE_LIBCURL
@ -2882,8 +2926,14 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
.add_input_delegate(*filter_source);
lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
.add_input_delegate(lnav_data.ld_files_source);
lnav_data.ld_file_details_view.set_sub_source(
&lnav_data.ld_file_details_source);
lnav_data.ld_files_source.fss_details_source
= &lnav_data.ld_file_details_source;
lnav_data.ld_user_message_view.set_sub_source(
&lnav_data.ld_user_message_source);
auto overlay_menu = std::make_shared<text_overlay_menu>();
lnav_data.ld_file_details_view.set_overlay_source(overlay_menu.get());
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
@ -3541,6 +3591,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
save_session();
}
log_info("exiting main loop");
} catch (const std::system_error& e) {
if (e.code().value() != EPIPE) {
fprintf(stderr, "error: %s\n", e.what());

View File

@ -197,8 +197,10 @@ struct lnav_data_t {
textview_curses ld_doc_view;
textview_curses ld_filter_view;
files_sub_source ld_files_source;
plain_text_source ld_file_details_source;
files_overlay_source ld_files_overlay;
textview_curses ld_files_view;
textview_curses ld_file_details_view;
plain_text_source ld_example_source;
textview_curses ld_example_view;
plain_text_source ld_match_source;

View File

@ -3352,7 +3352,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
attr_line_t al;
attr_line_builder alb(al);
switch (detect_res) {
switch (detect_res.dffr_file_format) {
case file_format_t::ARCHIVE: {
auto describe_res = archive_manager::describe(fn);
@ -3450,7 +3450,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
break;
}
case file_format_t::SQLITE_DB: {
alb.append(fmt::to_string(detect_res));
alb.append(fmt::to_string(detect_res.dffr_file_format));
break;
}
case file_format_t::REMOTE: {

View File

@ -333,9 +333,10 @@ logline_value::origin_in_full_msg(const char* msg, ssize_t len) const
return retval;
}
logline_value::logline_value(logline_value_meta lvm,
shared_buffer_ref& sbr,
struct line_range origin)
logline_value::
logline_value(logline_value_meta lvm,
shared_buffer_ref& sbr,
struct line_range origin)
: lv_meta(std::move(lvm)), lv_origin(origin)
{
if (sbr.get_data() == nullptr) {
@ -4028,16 +4029,24 @@ external_log_format::specialized(int fmt_lock)
return retval;
}
bool
log_format::match_name_result
external_log_format::match_name(const std::string& filename)
{
if (this->elf_filename_pcre.pp_value == nullptr) {
return true;
return name_matched{};
}
return this->elf_filename_pcre.pp_value->find_in(filename)
.ignore_error()
.has_value();
if (this->elf_filename_pcre.pp_value->find_in(filename)
.ignore_error()
.has_value())
{
return name_matched{};
}
return name_mismatched{
this->elf_filename_pcre.pp_value->match_partial(filename),
this->elf_filename_pcre.pp_value->get_pattern(),
};
}
auto
@ -4294,8 +4303,8 @@ log_format::find_root_format(const char* name)
return nullptr;
}
log_format::pattern_for_lines::pattern_for_lines(uint32_t pfl_line,
uint32_t pfl_pat_index)
log_format::pattern_for_lines::
pattern_for_lines(uint32_t pfl_line, uint32_t pfl_pat_index)
: pfl_line(pfl_line), pfl_pat_index(pfl_pat_index)
{
}

View File

@ -406,7 +406,19 @@ public:
*/
virtual const intern_string_t get_name() const = 0;
virtual bool match_name(const std::string& filename) { return true; }
struct name_matched {};
struct name_mismatched {
size_t nm_partial;
std::string nm_pattern;
};
using match_name_result
= mapbox::util::variant<name_matched, name_mismatched>;
virtual match_name_result match_name(const std::string& filename)
{
return name_matched{};
}
struct scan_match {
uint32_t sm_quality;

View File

@ -140,7 +140,7 @@ public:
const intern_string_t get_name() const override { return this->elf_name; }
bool match_name(const std::string& filename) override;
match_name_result match_name(const std::string& filename) override;
scan_result_t scan(logfile& lf,
std::vector<logline>& dst,
@ -446,7 +446,6 @@ public:
bool jlf_hide_extra{false};
std::vector<json_format_element> jlf_line_format;
int jlf_line_format_init_count{0};
shared_buffer jlf_share_manager;
logline_value_vector jlf_line_values;
off_t jlf_cached_offset{-1};
@ -457,6 +456,7 @@ public:
string_attrs_t jlf_line_attrs;
std::shared_ptr<yajlpp_parse_context> jlf_parse_context;
std::shared_ptr<yajl_handle_t> jlf_yajl_handle;
shared_buffer jlf_share_manager;
private:
const intern_string_t elf_name;

View File

@ -45,6 +45,7 @@
#include "base/date_time_scanner.cfg.hh"
#include "base/fs_util.hh"
#include "base/injector.hh"
#include "base/snippet_highlighters.hh"
#include "base/string_util.hh"
#include "config.h"
#include "file_options.hh"
@ -56,6 +57,8 @@
#include "piper.looper.hh"
#include "yajlpp/yajlpp_def.hh"
using namespace lnav::roles::literals;
static auto intern_lifetime = intern_string::get_table_lifetime();
static const size_t INDEX_RESERVE_INCREMENT = 1024;
@ -140,6 +143,7 @@ logfile::open(std::filesystem::path filename,
lf->lf_indexing = lf->lf_options.loo_is_visible;
lf->lf_text_format
= lf->lf_options.loo_text_format.value_or(text_format_t::TF_UNKNOWN);
lf->lf_format_match_messages = loo.loo_match_details;
const auto& hdr = lf->lf_line_buffer.get_header_data();
if (hdr.valid()) {
@ -149,7 +153,9 @@ logfile::open(std::filesystem::path filename,
if (!gzhdr.empty()) {
lf->lf_embedded_metadata["net.zlib.gzip.header"] = {
text_format_t::TF_JSON,
file_header_handlers.to_string(gzhdr),
file_header_handlers.formatter_for(gzhdr)
.with_config(yajl_gen_beautify, 1)
.to_string(),
};
}
},
@ -159,7 +165,9 @@ logfile::open(std::filesystem::path filename,
lf->lf_embedded_metadata["org.lnav.piper.header"] = {
text_format_t::TF_JSON,
lnav::piper::header_handlers.to_string(phdr),
lnav::piper::header_handlers.formatter_for(phdr)
.with_config(yajl_gen_beautify, 1)
.to_string(),
};
log_info("setting file name from piper header: %s",
phdr.h_name.c_str());
@ -196,14 +204,15 @@ logfile::open(std::filesystem::path filename,
return Ok(lf);
}
logfile::logfile(std::filesystem::path filename,
const logfile_open_options& loo)
logfile::
logfile(std::filesystem::path filename, const logfile_open_options& loo)
: lf_filename(std::move(filename)), lf_options(loo)
{
this->lf_opids.writeAccess()->los_opid_ranges.reserve(64);
}
logfile::~logfile()
logfile::~
logfile()
{
log_info("destructing logfile: %s", this->lf_filename.c_str());
}
@ -360,12 +369,33 @@ logfile::process_prefix(shared_buffer_ref& sbr,
continue;
}
if (!curr->match_name(this->lf_filename)) {
auto match_res = curr->match_name(this->lf_filename);
if (match_res.is<log_format::name_mismatched>()) {
auto nm = match_res.get<log_format::name_mismatched>();
if (li.li_file_range.fr_offset == 0) {
log_debug("(%s) does not match file name: %s",
curr->get_name().get(),
this->lf_filename.c_str());
}
auto regex_al = attr_line_t(nm.nm_pattern);
lnav::snippets::regex_highlighter(
regex_al, -1, line_range{0, (int) regex_al.length()});
auto note = attr_line_t("pattern: ")
.append(regex_al)
.append("\n ")
.append(lnav::roles::quoted_code(
fmt::to_string(this->get_filename())))
.append("\n")
.append(nm.nm_partial + 2, ' ')
.append("^ matched up to here"_snippet_border);
auto match_um = lnav::console::user_message::info(
attr_line_t()
.append(lnav::roles::identifier(
curr->get_name().to_string()))
.append(" file name pattern required "
"by format does not match"))
.with_note(note);
this->lf_format_match_messages.emplace_back(match_um);
this->lf_mismatched_formats.insert(curr->get_name());
continue;
}
@ -414,6 +444,20 @@ logfile::process_prefix(shared_buffer_ref& sbr,
" scan with format (%s) matched with quality (%d)",
curr->get_name().c_str(),
sm.sm_quality);
auto match_um
= lnav::console::user_message::info(
attr_line_t()
.append(lnav::roles::identifier(
curr->get_name().to_string()))
.append(" matched line ")
.append(lnav::roles::number(
fmt::to_string(starting_index_size))))
.with_note(
attr_line_t("match quality is ")
.append(lnav::roles::number(
fmt::to_string(sm.sm_quality))));
this->lf_format_match_messages.emplace_back(match_um);
if (best_match) {
auto starting_iter = std::next(
this->lf_index.begin(), starting_index_size);
@ -469,6 +513,14 @@ logfile::process_prefix(shared_buffer_ref& sbr,
this->lf_index.size(),
curr->get_name().get());
auto match_um = lnav::console::user_message::ok(
attr_line_t()
.append(lnav::roles::identifier(
winner.first->get_name().to_string()))
.append(" is the best match for line ")
.append(lnav::roles::number(
fmt::to_string(starting_index_size))));
this->lf_format_match_messages.emplace_back(match_um);
this->lf_text_format = text_format_t::TF_LOG;
this->lf_format = curr->specialized();
this->lf_format_quality = winner.second.sm_quality;

View File

@ -32,6 +32,7 @@
#ifndef logfile_hh
#define logfile_hh
#include <filesystem>
#include <set>
#include <string>
#include <utility>
@ -49,7 +50,6 @@
#include "bookmarks.hh"
#include "byte_array.hh"
#include "file_options.hh"
#include <filesystem>
#include "line_buffer.hh"
#include "log_format_fwd.hh"
#include "logfile_fwd.hh"
@ -420,6 +420,16 @@ public:
return this->lf_file_options;
}
const std::set<intern_string_t>& get_mismatched_formats()
{
return this->lf_mismatched_formats;
}
const std::vector<lnav::console::user_message>& get_format_match_messages()
{
return this->lf_format_match_messages;
}
protected:
/**
* Process a line from the file.
@ -489,6 +499,7 @@ private:
std::map<std::string, metadata> lf_embedded_metadata;
size_t lf_file_options_generation{0};
std::optional<std::pair<std::string, lnav::file_options>> lf_file_options;
std::vector<lnav::console::user_message> lf_format_match_messages;
};
class logline_observer {

View File

@ -75,6 +75,7 @@ struct logfile_open_options_base {
std::optional<text_format_t> loo_text_format;
std::optional<lnav::piper::running_handle> loo_piper;
file_location_t loo_init_location{mapbox::util::no_init{}};
std::vector<lnav::console::user_message> loo_match_details;
};
struct logfile_open_options : public logfile_open_options_base {

View File

@ -146,7 +146,8 @@ pretty_pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
return retval;
}
logfile_sub_source::logfile_sub_source()
logfile_sub_source::
logfile_sub_source()
: text_sub_source(1), lss_meta_grepper(*this), lss_location_history(*this)
{
this->tss_supports_filtering = true;
@ -2007,6 +2008,7 @@ logfile_sub_source::remove_file(std::shared_ptr<logfile> lf)
this->lss_force_rebuild = true;
}
this->lss_token_file = nullptr;
}
std::optional<vis_line_t>
@ -2303,7 +2305,8 @@ logline_window::end()
return {this->lw_source, vl};
}
logline_window::logmsg_info::logmsg_info(logfile_sub_source& lss, vis_line_t vl)
logline_window::logmsg_info::
logmsg_info(logfile_sub_source& lss, vis_line_t vl)
: li_source(lss), li_line(vl)
{
if (this->li_line < vis_line_t(this->li_source.text_line_count())) {
@ -2394,7 +2397,8 @@ logline_window::logmsg_info::get_metadata() const
return &bm_iter->second;
}
logline_window::logmsg_info::metadata_edit_guard::~metadata_edit_guard()
logline_window::logmsg_info::metadata_edit_guard::~
metadata_edit_guard()
{
auto line_number = std::distance(this->meg_logmsg_info.li_file->begin(),
this->meg_logmsg_info.li_logline);

View File

@ -187,6 +187,14 @@ environ_to_map()
return retval;
}
auto_pipe&
looper::get_wakeup_pipe()
{
static auto retval = auto_pipe::for_child_fd(-1).unwrap();
return retval;
}
looper::
looper(std::string name, auto_fd stdout_fd, auto_fd stderr_fd, options opts)
: l_name(std::move(name)), l_cwd(std::filesystem::current_path().string()),
@ -210,8 +218,11 @@ looper(std::string name, auto_fd stdout_fd, auto_fd stderr_fd, options opts)
looper::~
looper()
{
char ch = '\0';
log_info("piper destructed, shutting down: %s", this->l_name.c_str());
this->l_looping = false;
log_perror(write(get_wakeup_pipe().write_end(), &ch, 1));
this->l_future.wait();
}
@ -231,7 +242,7 @@ looper::loop()
static constexpr auto FILE_TIMEOUT_MAX = 1000ms;
const auto& cfg = injector::get<const config&>();
struct pollfd pfd[2];
struct pollfd pfd[3];
struct {
line_buffer lb;
file_range last_range;
@ -311,6 +322,11 @@ looper::loop()
break;
}
auto& wakeup_pfd = pfd[used_pfds++];
wakeup_pfd.fd = get_wakeup_pipe().read_end().get();
wakeup_pfd.events = POLLIN;
wakeup_pfd.revents = 0;
auto poll_rc = poll(pfd, used_pfds, poll_timeout);
if (poll_rc == 0) {
// update the timestamp to keep the file alive from any
@ -329,6 +345,11 @@ looper::loop()
} else {
last_write = std::chrono::system_clock::now();
}
if (!this->l_looping.load()) {
char ch;
read(get_wakeup_pipe().read_end(), &ch, 1);
}
for (auto& cap : captured_fds) {
if (cap.lb.get_fd() == -1) {
continue;
@ -469,16 +490,20 @@ looper::loop()
fmt::format(FMT_STRING("{:?}"), body_sf).c_str());
auto match_res = mmatcher.match(body_sf);
if (!mmatcher.mm_details.empty()) {
this->l_demux_info.writeAccess()->di_details
= mmatcher.mm_details;
}
demux_attempted = match_res.match(
[this, &curr_demux_def, &cfg](
multiplex_matcher::found f) {
curr_demux_def
= cfg.c_demux_definitions.find(f.f_id)->second;
{
safe::WriteAccess<safe_demux_id> di(
this->l_demux_id);
safe::WriteAccess<safe_demux_info> di(
this->l_demux_info);
di->assign(f.f_id);
di->di_name = f.f_id;
}
return true;
},
@ -755,7 +780,7 @@ cleanup()
}
for (auto& entry : to_remove) {
log_debug("removing piper directory: %s", entry.c_str());
log_info("removing piper directory: %s", entry.c_str());
std::filesystem::remove_all(entry);
}
});

View File

@ -30,6 +30,7 @@
#ifndef piper_looper_hh
#define piper_looper_hh
#include <filesystem>
#include <future>
#include <memory>
#include <string>
@ -37,7 +38,6 @@
#include "base/auto_fd.hh"
#include "base/piper.file.hh"
#include "base/result.h"
#include <filesystem>
#include "safe/safe.h"
#include "yajlpp/yajlpp_def.hh"
@ -49,7 +49,12 @@ enum class state {
finished,
};
using safe_demux_id = safe::Safe<std::string>;
struct demux_info {
std::string di_name;
std::vector<lnav::console::user_message> di_details;
};
using safe_demux_info = safe::Safe<demux_info>;
struct options {
bool o_demux{false};
@ -86,7 +91,10 @@ public:
return this->l_out_dir / "out.*";
}
std::string get_demux_id() const { return *this->l_demux_id.readAccess(); }
demux_info get_demux_info() const
{
return *this->l_demux_info.readAccess();
}
std::string get_url() const
{
@ -94,10 +102,7 @@ public:
this->l_out_dir.filename().string());
}
int get_loop_count() const
{
return this->l_loop_count.load();
}
int get_loop_count() const { return this->l_loop_count.load(); }
bool is_finished() const
{
@ -120,6 +125,8 @@ public:
private:
void loop();
static auto_pipe& get_wakeup_pipe();
std::atomic<bool> l_looping{true};
const std::string l_name;
const std::string l_cwd;
@ -131,7 +138,7 @@ private:
std::future<void> l_future;
std::atomic<int> l_finished{0};
std::atomic<int> l_loop_count{0};
safe_demux_id l_demux_id;
safe_demux_info l_demux_info;
};
template<state LooperState>
@ -154,7 +161,15 @@ public:
return this->h_looper->get_out_pattern();
}
std::string get_demux_id() const { return this->h_looper->get_demux_id(); }
std::string get_demux_id() const
{
return this->h_looper->get_demux_info().di_name;
}
std::vector<lnav::console::user_message> get_demux_details() const
{
return this->h_looper->get_demux_info().di_details;
}
std::string get_url() const { return this->h_looper->get_url(); }

View File

@ -143,6 +143,7 @@ plain_text_source&
plain_text_source::replace_with(const std::vector<attr_line_t>& text_lines)
{
file_off_t off = 0;
this->tds_lines.clear();
for (const auto& al : text_lines) {
this->tds_lines.emplace_back(off, al);
off += al.length() + 1;

View File

@ -784,6 +784,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
case ln_mode_t::PAGING:
case ln_mode_t::FILTER:
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
case ln_mode_t::EXEC:
case ln_mode_t::USER:
case ln_mode_t::SPECTRO_DETAILS:
@ -876,6 +877,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
case ln_mode_t::PAGING:
case ln_mode_t::FILTER:
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
case ln_mode_t::SPECTRO_DETAILS:
case ln_mode_t::BUSY:
require(0);

View File

@ -33,6 +33,7 @@
"pattern": "^(?<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}(?:Z|[+\\-]\\d{2}:\\d{2})) source=[a-zA-Z0-9][a-zA-Z0-9_\\.\\-]* (?<body>.*) kubernetes_host=(?<k8s_host>[a-zA-Z0-9][a-zA-Z0-9_\\.\\-]*) kubernetes_pod_name=(?<mux_id>[a-zA-Z0-9][a-zA-Z0-9_\\.\\-]*)"
},
"container-with-type": {
"enabled": false,
"pattern": "^(?<mux_id>[a-zA-Z][\\w\\-]{3,}) (?<container_type>[a-zA-Z][\\w\\-]{3,}) (?<body>.*)"
}
}

View File

@ -33,6 +33,7 @@
#include "config.h"
#include "lnav.hh"
#include "md4cpp.hh"
#include "sysclip.hh"
#include "textview_curses.hh"
using namespace md4cpp::literals;
@ -50,7 +51,10 @@ text_overlay_menu::list_overlay_menu(const listview_curses& lv, vis_line_t row)
return retval;
}
const auto* tss = tc->get_sub_source();
const auto& sti = tc->tc_selected_text.value();
const auto supports_filtering
= tss != nullptr && tss->tss_supports_filtering;
if (sti.sti_line != row) {
return retval;
@ -85,25 +89,29 @@ text_overlay_menu::list_overlay_menu(const listview_curses& lv, vis_line_t row)
attr_line_t al;
int start = left;
if (is_link) {
al.append(":floppy_disk:"_emoji)
.append(" Open in lnav")
.append(" ");
} else {
al.append(" ").append("\u2714 Filter-in"_ok).append(" ");
if (is_link || supports_filtering) {
if (is_link) {
al.append(":floppy_disk:"_emoji)
.append(" Open in lnav")
.append(" ");
} else {
al.append(" ").append("\u2714 Filter-in"_ok).append(" ");
}
this->los_menu_items.emplace_back(
menu_line,
line_range{start, start + (int) al.length()},
[is_link, sti](const std::string& value) {
const auto cmd = is_link
? ":open $href"
: fmt::format(FMT_STRING(":filter-in {}"),
lnav::pcre2pp::quote(value));
lnav_data.ld_exec_context
.with_provenance(exec_context::mouse_input{})
->execute_with(cmd,
std::make_pair("href", sti.sti_href));
});
start += al.length();
}
this->los_menu_items.emplace_back(
menu_line,
line_range{start, start + (int) al.length()},
[is_link, sti](const std::string& value) {
auto cmd = is_link ? ":open $href"
: fmt::format(FMT_STRING(":filter-in {}"),
lnav::pcre2pp::quote(value));
lnav_data.ld_exec_context
.with_provenance(exec_context::mouse_input{})
->execute_with(cmd, std::make_pair("href", sti.sti_href));
});
start += al.length();
if (is_link) {
al.append(" ");
@ -125,28 +133,32 @@ text_overlay_menu::list_overlay_menu(const listview_curses& lv, vis_line_t row)
}
retval.emplace_back(attr_line_t().pad_to(left).append(al));
}
menu_line += 1_vl;
{
attr_line_t al;
if (is_link) {
al.append(":globe_with_meridians:"_emoji).append(" Open ");
} else {
al.append(" ").append("\u2718 Filter-out"_error).append(" ");
}
menu_line += 1_vl;
int start = left;
this->los_menu_items.emplace_back(
menu_line,
line_range{start, start + (int) al.length()},
[is_link, sti](const std::string& value) {
auto cmd = is_link ? ":xopen $href"
: fmt::format(FMT_STRING(":filter-out {}"),
lnav::pcre2pp::quote(value));
lnav_data.ld_exec_context
.with_provenance(exec_context::mouse_input{})
->execute_with(cmd, std::make_pair("href", sti.sti_href));
});
start += al.length();
if (is_link || supports_filtering) {
if (is_link) {
al.append(":globe_with_meridians:"_emoji).append(" Open ");
} else {
al.append(" ").append("\u2718 Filter-out"_error).append(" ");
}
this->los_menu_items.emplace_back(
menu_line,
line_range{start, start + (int) al.length()},
[is_link, sti](const std::string& value) {
auto cmd = is_link
? ":xopen $href"
: fmt::format(FMT_STRING(":filter-out {}"),
lnav::pcre2pp::quote(value));
lnav_data.ld_exec_context
.with_provenance(exec_context::mouse_input{})
->execute_with(cmd,
std::make_pair("href", sti.sti_href));
});
start += al.length();
}
al.append(":clipboard:"_emoji)
.append(is_link ? " Copy link " : " Copy ")
.with_attr_for_all(VC_ROLE.value(role_t::VCR_STATUS));
@ -154,7 +166,15 @@ text_overlay_menu::list_overlay_menu(const listview_curses& lv, vis_line_t row)
menu_line,
line_range{start, start + (int) al.length()},
[](const std::string& value) {
lnav_data.ld_exec_context.execute("|lnav-copy-text");
auto clip_res = sysclip::open(sysclip::type_t::GENERAL);
if (clip_res.isErr()) {
log_error("unable to open clipboard: %s",
clip_res.unwrapErr().c_str());
return;
}
auto clip_pipe = clip_res.unwrap();
fwrite(value.c_str(), 1, value.length(), clip_pipe.in());
});
retval.emplace_back(attr_line_t().pad_to(left).append(al));
}

View File

@ -462,7 +462,6 @@ textview_curses::handle_mouse(mouse_event& me)
switch (me.me_state) {
case mouse_button_state_t::BUTTON_STATE_PRESSED: {
this->tc_text_selection_active = true;
this->tc_press_line = mouse_line;
this->tc_press_left = this->lv_left + me.me_press_x;
if (!this->lv_selectable) {
@ -470,6 +469,7 @@ textview_curses::handle_mouse(mouse_event& me)
}
mouse_line.match(
[this, &me, sub_delegate, &mouse_line](const main_content& mc) {
this->tc_text_selection_active = true;
if (this->vc_enabled) {
if (this->tc_supports_marks
&& me.me_button == mouse_button_t::BUTTON_LEFT
@ -492,9 +492,7 @@ textview_curses::handle_mouse(mouse_event& me)
}
},
[](const overlay_menu& om) {},
[](const static_overlay_content& soc) {
},
[](const static_overlay_content& soc) {},
[this](const overlay_content& oc) {
this->set_overlay_selection(oc.oc_line);
},
@ -574,15 +572,9 @@ textview_curses::handle_mouse(mouse_event& me)
sub_delegate->text_handle_mouse(*this, mouse_line, me);
}
},
[](const static_overlay_content& soc) {
},
[](const overlay_menu& om) {
},
[](const overlay_content& oc) {
},
[](const static_overlay_content& soc) {},
[](const overlay_menu& om) {},
[](const overlay_content& oc) {},
[](const empty_space& es) {});
break;
}
@ -658,19 +650,16 @@ textview_curses::handle_mouse(mouse_event& me)
if (ov != nullptr && mouse_line.is<overlay_menu>()
&& this->tc_selected_text)
{
auto* los = dynamic_cast<list_overlay_source*>(ov);
if (los != nullptr) {
auto& om = mouse_line.get<overlay_menu>();
auto& sti = this->tc_selected_text.value();
auto& om = mouse_line.get<overlay_menu>();
auto& sti = this->tc_selected_text.value();
for (const auto& mi : los->los_menu_items) {
if (om.om_line == mi.mi_line
&& me.is_click_in(mouse_button_t::BUTTON_LEFT,
mi.mi_range))
{
mi.mi_action(sti.sti_value);
break;
}
for (const auto& mi : ov->los_menu_items) {
if (om.om_line == mi.mi_line
&& me.is_click_in(mouse_button_t::BUTTON_LEFT,
mi.mi_range))
{
mi.mi_action(sti.sti_value);
break;
}
}
}
@ -848,11 +837,11 @@ void
textview_curses::execute_search(const std::string& regex_orig)
{
std::string regex = regex_orig;
std::shared_ptr<lnav::pcre2pp::code> code;
if ((this->tc_search_child == nullptr)
|| (regex != this->tc_current_search))
{
std::shared_ptr<lnav::pcre2pp::code> code;
this->match_reset();
this->tc_search_child.reset();

View File

@ -175,7 +175,7 @@ timeline_preview_overlay::list_overlay_menu(const listview_curses& lv,
}
timeline_header_overlay::
timeline_header_overlay(std::shared_ptr<timeline_source> src)
timeline_header_overlay(const std::shared_ptr<timeline_source>& src)
: gho_src(src)
{
}

View File

@ -187,7 +187,8 @@ public:
class timeline_header_overlay : public text_overlay_menu {
public:
explicit timeline_header_overlay(std::shared_ptr<timeline_source> src);
explicit timeline_header_overlay(
const std::shared_ptr<timeline_source>& src);
bool list_static_overlay(const listview_curses& lv,
int y,

View File

@ -617,6 +617,7 @@ handle_winch()
lnav_data.ld_match_view.set_needs_update();
lnav_data.ld_filter_view.set_needs_update();
lnav_data.ld_files_view.set_needs_update();
lnav_data.ld_file_details_view.set_needs_update();
lnav_data.ld_spectro_details_view.set_needs_update();
lnav_data.ld_timeline_details_view.set_needs_update();
lnav_data.ld_user_message_view.set_needs_update();
@ -627,6 +628,9 @@ handle_winch()
void
layout_views()
{
static constexpr auto FILES_FOCUSED_WIDTH = 40;
static constexpr auto FILES_BLURRED_WIDTH = 20;
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
int width, height;
getmaxyx(lnav_data.ld_window, height, width);
@ -698,13 +702,31 @@ layout_views()
auto config_panel_open = (lnav_data.ld_mode == ln_mode_t::FILTER
|| lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::FILE_DETAILS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES);
auto filters_open = (lnav_data.ld_mode == ln_mode_t::FILTER
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILTERS);
auto files_open = (lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::FILE_DETAILS
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES);
int filter_height = config_panel_open ? 5 : 0;
auto files_width = lnav_data.ld_mode == ln_mode_t::FILES
? FILES_FOCUSED_WIDTH
: FILES_BLURRED_WIDTH;
int filter_height;
switch (lnav_data.ld_mode) {
case ln_mode_t::FILES:
case ln_mode_t::FILTER:
filter_height = 5;
break;
case ln_mode_t::FILE_DETAILS:
filter_height = 15;
break;
default:
filter_height = 0;
break;
}
bool breadcrumb_open = (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS);
@ -809,9 +831,16 @@ layout_views()
lnav_data.ld_files_view.set_height(vis_line_t(filter_height));
lnav_data.ld_files_view.set_y(bottom + 2);
lnav_data.ld_files_view.set_width(width);
lnav_data.ld_files_view.set_width(files_width);
lnav_data.ld_files_view.set_visible(files_open && vis);
lnav_data.ld_file_details_view.set_height(vis_line_t(filter_height));
lnav_data.ld_file_details_view.set_y(bottom + 2);
lnav_data.ld_file_details_view.set_x(files_width);
lnav_data.ld_file_details_view.set_width(
std::clamp(width - files_width, 0, width));
lnav_data.ld_file_details_view.set_visible(files_open && vis);
lnav_data.ld_status[LNS_FILTER_HELP].set_visible(config_panel_open && vis);
lnav_data.ld_status[LNS_FILTER_HELP].set_y(bottom + 1);
lnav_data.ld_status[LNS_FILTER_HELP].set_width(width);
@ -1265,6 +1294,8 @@ get_textview_for_mode(ln_mode_t mode)
case ln_mode_t::SEARCH_FILES:
case ln_mode_t::FILES:
return &lnav_data.ld_files_view;
case ln_mode_t::FILE_DETAILS:
return &lnav_data.ld_file_details_view;
case ln_mode_t::SPECTRO_DETAILS:
case ln_mode_t::SEARCH_SPECTRO_DETAILS:
return &lnav_data.ld_spectro_details_view;
@ -1435,6 +1466,11 @@ set_view_mode(ln_mode_t mode)
lnav_data.ld_view_stack.set_needs_update();
break;
}
case ln_mode_t::FILE_DETAILS: {
lnav_data.ld_file_details_view.tc_cursor_role
= role_t::VCR_DISABLED_CURSOR_LINE;
break;
}
default:
break;
}
@ -1443,6 +1479,12 @@ set_view_mode(ln_mode_t mode)
breadcrumb_view->focus();
break;
}
case ln_mode_t::FILE_DETAILS: {
lnav_data.ld_status[LNS_FILTER].set_needs_update();
lnav_data.ld_file_details_view.tc_cursor_role
= role_t::VCR_CURSOR_LINE;
break;
}
default:
break;
}
@ -1464,6 +1506,7 @@ all_views()
retval.push_back(&lnav_data.ld_example_view);
retval.push_back(&lnav_data.ld_preview_view[0]);
retval.push_back(&lnav_data.ld_preview_view[1]);
retval.push_back(&lnav_data.ld_file_details_view);
retval.push_back(&lnav_data.ld_files_view);
retval.push_back(&lnav_data.ld_filter_view);
retval.push_back(&lnav_data.ld_user_message_view);
@ -1478,7 +1521,7 @@ void
lnav_behavior::mouse_event(int button, bool release, int x, int y)
{
static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
static const std::vector<view_curses*> VIEWS = all_views();
static const auto VIEWS = all_views();
static const auto CLICK_INTERVAL
= std::chrono::milliseconds(mouseinterval(-1) * 2);
@ -1552,6 +1595,7 @@ lnav_behavior::mouse_event(int button, bool release, int x, int y)
case ln_mode_t::PAGING:
break;
case ln_mode_t::FILES:
case ln_mode_t::FILE_DETAILS:
case ln_mode_t::FILTER:
// Clicking on the main view when the config panels are
// open should return us to paging.

View File

@ -64,6 +64,7 @@ enum class ln_mode_t : int {
BREADCRUMBS,
FILTER,
FILES,
FILE_DETAILS,
SPECTRO_DETAILS,
SEARCH_SPECTRO_DETAILS,
COMMAND,
@ -96,8 +97,8 @@ std::optional<vis_line_t> next_cluster(
const,
const bookmark_type_t* bt,
vis_line_t top);
bool moveto_cluster(std::optional<vis_line_t> (
bookmark_vector<vis_line_t>::*f)(vis_line_t) const,
bool moveto_cluster(std::optional<vis_line_t> (bookmark_vector<vis_line_t>::*f)(
vis_line_t) const,
const bookmark_type_t* bt,
vis_line_t top);
vis_line_t search_forward_from(textview_curses* tc);

View File

@ -5256,7 +5256,7 @@
"control-pattern": ""
},
"container-with-type": {
"enabled": true,
"enabled": false,
"pattern": "^(?<mux_id>[a-zA-Z][\\w\\-]{3,}) (?<container_type>[a-zA-Z][\\w\\-]{3,}) (?<body>.*)",
"control-pattern": ""
},

View File

@ -19,49 +19,50 @@
/log/annotations/org.lnav.test/description -> {test_dir}/configs/installed/anno-test.json:6
/log/annotations/org.lnav.test/handler -> {test_dir}/configs/installed/anno-test.json:8
/log/date-time/convert-zoned-to-local -> root-config.json:18
/log/demux/container-with-type/pattern -> root-config.json:36
/log/demux/container-with-type/enabled -> root-config.json:36
/log/demux/container-with-type/pattern -> root-config.json:37
/log/demux/container/pattern -> root-config.json:29
/log/demux/recv-with-pod/control-pattern -> root-config.json:32
/log/demux/recv-with-pod/pattern -> root-config.json:33
/tuning/archive-manager/cache-ttl -> root-config.json:43
/tuning/archive-manager/min-free-space -> root-config.json:42
/tuning/clipboard/impls/MacOS/find/read -> root-config.json:71
/tuning/clipboard/impls/MacOS/find/write -> root-config.json:70
/tuning/clipboard/impls/MacOS/general/read -> root-config.json:67
/tuning/clipboard/impls/MacOS/general/write -> root-config.json:66
/tuning/clipboard/impls/MacOS/test -> root-config.json:64
/tuning/clipboard/impls/NeoVim/general/read -> root-config.json:99
/tuning/clipboard/impls/NeoVim/general/write -> root-config.json:98
/tuning/clipboard/impls/NeoVim/test -> root-config.json:96
/tuning/clipboard/impls/Wayland/general/read -> root-config.json:78
/tuning/clipboard/impls/Wayland/general/write -> root-config.json:77
/tuning/clipboard/impls/Wayland/test -> root-config.json:75
/tuning/clipboard/impls/Windows/general/write -> root-config.json:105
/tuning/clipboard/impls/Windows/test -> root-config.json:103
/tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:85
/tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:84
/tuning/clipboard/impls/X11-xclip/test -> root-config.json:82
/tuning/clipboard/impls/tmux/general/read -> root-config.json:92
/tuning/clipboard/impls/tmux/general/write -> root-config.json:91
/tuning/clipboard/impls/tmux/test -> root-config.json:89
/tuning/external-opener/impls/MacOS/command -> root-config.json:114
/tuning/external-opener/impls/MacOS/test -> root-config.json:113
/tuning/external-opener/impls/XDG/command -> root-config.json:118
/tuning/external-opener/impls/XDG/test -> root-config.json:117
/tuning/piper/max-size -> root-config.json:57
/tuning/piper/rotations -> root-config.json:58
/tuning/piper/ttl -> root-config.json:59
/tuning/remote/ssh/command -> root-config.json:47
/tuning/remote/ssh/config/BatchMode -> root-config.json:49
/tuning/remote/ssh/config/ConnectTimeout -> root-config.json:50
/tuning/remote/ssh/start-command -> root-config.json:52
/tuning/remote/ssh/transfer-command -> root-config.json:53
/tuning/url-scheme/docker-compose/handler -> root-config.json:127
/tuning/url-scheme/docker/handler -> root-config.json:124
/tuning/archive-manager/cache-ttl -> root-config.json:44
/tuning/archive-manager/min-free-space -> root-config.json:43
/tuning/clipboard/impls/MacOS/find/read -> root-config.json:72
/tuning/clipboard/impls/MacOS/find/write -> root-config.json:71
/tuning/clipboard/impls/MacOS/general/read -> root-config.json:68
/tuning/clipboard/impls/MacOS/general/write -> root-config.json:67
/tuning/clipboard/impls/MacOS/test -> root-config.json:65
/tuning/clipboard/impls/NeoVim/general/read -> root-config.json:100
/tuning/clipboard/impls/NeoVim/general/write -> root-config.json:99
/tuning/clipboard/impls/NeoVim/test -> root-config.json:97
/tuning/clipboard/impls/Wayland/general/read -> root-config.json:79
/tuning/clipboard/impls/Wayland/general/write -> root-config.json:78
/tuning/clipboard/impls/Wayland/test -> root-config.json:76
/tuning/clipboard/impls/Windows/general/write -> root-config.json:106
/tuning/clipboard/impls/Windows/test -> root-config.json:104
/tuning/clipboard/impls/X11-xclip/general/read -> root-config.json:86
/tuning/clipboard/impls/X11-xclip/general/write -> root-config.json:85
/tuning/clipboard/impls/X11-xclip/test -> root-config.json:83
/tuning/clipboard/impls/tmux/general/read -> root-config.json:93
/tuning/clipboard/impls/tmux/general/write -> root-config.json:92
/tuning/clipboard/impls/tmux/test -> root-config.json:90
/tuning/external-opener/impls/MacOS/command -> root-config.json:115
/tuning/external-opener/impls/MacOS/test -> root-config.json:114
/tuning/external-opener/impls/XDG/command -> root-config.json:119
/tuning/external-opener/impls/XDG/test -> root-config.json:118
/tuning/piper/max-size -> root-config.json:58
/tuning/piper/rotations -> root-config.json:59
/tuning/piper/ttl -> root-config.json:60
/tuning/remote/ssh/command -> root-config.json:48
/tuning/remote/ssh/config/BatchMode -> root-config.json:50
/tuning/remote/ssh/config/ConnectTimeout -> root-config.json:51
/tuning/remote/ssh/start-command -> root-config.json:53
/tuning/remote/ssh/transfer-command -> root-config.json:54
/tuning/url-scheme/docker-compose/handler -> root-config.json:128
/tuning/url-scheme/docker/handler -> root-config.json:125
/tuning/url-scheme/hw/handler -> {test_dir}/configs/installed/hw-url-handler.json:6
/tuning/url-scheme/journald/handler -> root-config.json:130
/tuning/url-scheme/piper/handler -> root-config.json:133
/tuning/url-scheme/podman/handler -> root-config.json:136
/tuning/url-scheme/journald/handler -> root-config.json:131
/tuning/url-scheme/piper/handler -> root-config.json:134
/tuning/url-scheme/podman/handler -> root-config.json:137
/ui/clock-format -> root-config.json:4
/ui/default-colors -> root-config.json:6
/ui/dim-text -> root-config.json:5

View File

@ -1,2 +1,2 @@
basename(filepath)   descriptor   mimetype   content  
logfile_syslog.1.gz net.zlib.gzip.header application/json {"name":"logfile_syslog.1","mtime":"2007-11-03T09:23:00.000","comment":""}
basename(filepath)   descriptor   mimetype   content  
logfile_syslog.1.gz net.zlib.gzip.header application/json {"name": "logfile_syslog.1","mtime": "2007-11-03T09:23:00.000","comment": ""}