[open] improve file previews

The file preview for :open was not fully fleshed out.

Related to #1254
master
Tim Stack 1 month ago
parent e71ec01ac3
commit e382e3ad84

@ -5,6 +5,8 @@ Features:
from @flicus.
* Added a `stats.hist` PRQL transform that produces a histogram
of values over time.
* The preview for the `:open` command will now show a listing
of archive contents.
Bug Fixes:
* With the recent xz backdoor shenanigans, it seems like a good
@ -17,6 +19,7 @@ Bug Fixes:
displaying JSON-lines logs.
* A crash during initialization on Apple Silicon and MacOS 12
has been fixed.
* A crash when previewing non-text files.
## lnav v0.12.1

@ -74,10 +74,13 @@ enable_desired_archive_formats(archive* arc)
}
#endif
bool
is_archive(const fs::path& filename)
Result<describe_result, std::string>
describe(const fs::path& filename)
{
#if HAVE_ARCHIVE_H
static const auto RAW_FORMAT_NAME = string_fragment::from_const("raw");
static const auto GZ_FILTER_NAME = string_fragment::from_const("gzip");
auto_mem<archive> arc(archive_read_free);
arc = archive_read_new();
@ -96,39 +99,56 @@ is_archive(const fs::path& filename)
if (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
log_debug("read next done %s", filename.c_str());
static const auto RAW_FORMAT_NAME = string_fragment("raw");
static const auto GZ_FILTER_NAME = string_fragment("gzip");
format_name = archive_format_name(arc);
if (RAW_FORMAT_NAME == format_name) {
auto filter_count = archive_filter_count(arc);
if (filter_count == 1) {
return false;
return Ok(describe_result{unknown_file{}});
}
const auto* first_filter_name = archive_filter_name(arc, 0);
if (filter_count == 2 && GZ_FILTER_NAME == first_filter_name) {
return false;
return Ok(describe_result{unknown_file{}});
}
}
log_info(
"detected archive: %s -- %s", filename.c_str(), format_name);
return true;
auto ai = archive_info{
format_name,
};
do {
ai.ai_entries.emplace_back(archive_info::entry{
archive_entry_pathname_utf8(entry),
archive_entry_strmode(entry),
archive_entry_mtime(entry),
archive_entry_size_is_set(entry)
? nonstd::make_optional(archive_entry_size(entry))
: nonstd::nullopt,
});
} while (archive_read_next_header(arc, &entry) == ARCHIVE_OK);
return Ok(describe_result{ai});
}
log_info("archive read header failed: %s -- %s",
filename.c_str(),
archive_error_string(arc));
return Err(fmt::format(FMT_STRING("unable to read header: {} -- {}"),
filename,
archive_error_string(arc)));
} else {
log_info("archive open failed: %s -- %s",
filename.c_str(),
archive_error_string(arc));
return Err(fmt::format(FMT_STRING("unable to open file: {} -- {}"),
filename,
archive_error_string(arc)));
}
#endif
return false;
return Ok(describe_result{unknown_file{}});
}
static fs::path

@ -37,8 +37,11 @@
#include <string>
#include <utility>
#include "base/file_range.hh"
#include "base/result.h"
#include "ghc/filesystem.hpp"
#include "mapbox/variant.hpp"
#include "optional.hpp"
namespace archive_manager {
@ -56,7 +59,22 @@ struct extract_progress {
using extract_cb
= std::function<extract_progress*(const ghc::filesystem::path&, ssize_t)>;
bool is_archive(const ghc::filesystem::path& filename);
struct archive_info {
struct entry {
ghc::filesystem::path e_name;
const char* e_mode;
time_t e_mtime;
nonstd::optional<file_ssize_t> e_size;
};
const char* ai_format_name;
std::vector<entry> ai_entries;
};
struct unknown_file {};
using describe_result = mapbox::util::variant<archive_info, unknown_file>;
Result<describe_result, std::string> describe(
const ghc::filesystem::path& filename);
ghc::filesystem::path filename_to_tmp_path(const std::string& filename);

@ -28,3 +28,57 @@
*/
#include "attr_line.builder.hh"
attr_line_builder&
attr_line_builder::append_as_hexdump(const string_fragment& sf)
{
auto byte_off = size_t{0};
for (auto ch : sf) {
if (byte_off == 8) {
this->append(" ");
}
nonstd::optional<role_t> ro;
if (ch == '\0') {
ro = role_t::VCR_NULL;
} else if (isspace(ch) || iscntrl(ch)) {
ro = role_t::VCR_ASCII_CTRL;
} else if (!isprint(ch)) {
ro = role_t::VCR_NON_ASCII;
}
auto ag = ro.has_value() ? this->with_attr(VC_ROLE.value(ro.value()))
: this->with_default();
this->appendf(FMT_STRING(" {:0>2x}"), ch);
byte_off += 1;
}
for (; byte_off < 16; byte_off++) {
if (byte_off == 8) {
this->append(" ");
}
this->append(" ");
}
this->append(" ");
byte_off = 0;
for (auto ch : sf) {
if (byte_off == 8) {
this->append(" ");
}
if (ch == '\0') {
auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_NULL));
this->append("\u22c4");
} else if (isspace(ch)) {
auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_ASCII_CTRL));
this->append("_");
} else if (iscntrl(ch)) {
auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_ASCII_CTRL));
this->append("\u2022");
} else if (isprint(ch)) {
this->alb_line.get_string().push_back(ch);
} else {
auto ag = this->with_attr(VC_ROLE.value(role_t::VCR_NON_ASCII));
this->append("\u00d7");
}
byte_off += 1;
}
return *this;
}

@ -127,6 +127,8 @@ public:
return *this;
}
attr_line_builder& append_as_hexdump(const string_fragment& sf);
private:
attr_line_t& alb_line;
};

@ -41,7 +41,10 @@
file_format_t
detect_file_format(const ghc::filesystem::path& filename)
{
if (archive_manager::is_archive(filename)) {
auto describe_res = archive_manager::describe(filename);
if (describe_res.isOk()
&& describe_res.unwrap().is<archive_manager::archive_info>())
{
return file_format_t::ARCHIVE;
}

@ -44,6 +44,7 @@
#include "base/attr_line.builder.hh"
#include "base/auto_mem.hh"
#include "base/fs_util.hh"
#include "base/humanize.hh"
#include "base/humanize.network.hh"
#include "base/injector.hh"
#include "base/isc.hh"
@ -3234,31 +3235,32 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
lnav_data.ld_preview_source[0].clear();
if (!fc.fc_file_names.empty()) {
auto iter = fc.fc_file_names.begin();
std::string fn = iter->first;
auto_fd preview_fd;
std::string fn_str = iter->first;
if (fn.find(':') != std::string::npos) {
if (fn_str.find(':') != std::string::npos) {
auto id = lnav_data.ld_preview_generation;
lnav_data.ld_preview_status_source[0]
.get_description()
.set_cylon(true)
.set_value("Loading %s...", fn.c_str());
.set_value("Loading %s...", fn_str.c_str());
lnav_data.ld_preview_view[0].set_sub_source(
&lnav_data.ld_preview_source[0]);
lnav_data.ld_preview_source[0].clear();
isc::to<tailer::looper&, services::remote_tailer_t>().send(
[id, fn](auto& tlooper) {
auto rp_opt = humanize::network::path::from_str(fn);
[id, fn_str](auto& tlooper) {
auto rp_opt = humanize::network::path::from_str(fn_str);
if (rp_opt) {
tlooper.load_preview(id, *rp_opt);
}
});
lnav_data.ld_preview_view[0].set_needs_update();
} else if (lnav::filesystem::is_glob(fn.c_str())) {
} else if (lnav::filesystem::is_glob(fn_str)) {
static_root_mem<glob_t, globfree> gl;
if (glob(fn.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
if (glob(fn_str.c_str(), GLOB_NOCHECK, nullptr, gl.inout())
== 0)
{
attr_line_t al;
for (size_t lpc = 0; lpc < gl->gl_pathc && lpc < 10; lpc++)
@ -3278,42 +3280,124 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
&lnav_data.ld_preview_source[0]);
lnav_data.ld_preview_source[0].replace_with(al);
} else {
return ec.make_error("failed to evaluate glob -- {}", fn);
return ec.make_error("failed to evaluate glob -- {}",
fn_str);
}
} else if ((preview_fd = open(fn.c_str(), O_RDONLY)) == -1) {
return ec.make_error(
"unable to open file3: {} -- {}", fn, strerror(errno));
} else {
line_buffer lb;
auto fn = ghc::filesystem::path(fn_str);
auto detect_res = detect_file_format(fn);
attr_line_t al;
file_range range;
std::string lines;
lb.set_fd(preview_fd);
for (int lpc = 0; lpc < 10; lpc++) {
auto load_result = lb.load_next_line(range);
if (load_result.isErr()) {
attr_line_builder alb(al);
switch (detect_res) {
case file_format_t::ARCHIVE: {
auto describe_res = archive_manager::describe(fn);
if (describe_res.isOk()) {
auto arc_res = describe_res.unwrap();
if (arc_res.is<archive_manager::archive_info>()) {
auto ai
= arc_res
.get<archive_manager::archive_info>();
auto lines_remaining = size_t{9};
al.append("Archive: ")
.append(
lnav::roles::symbol(ai.ai_format_name))
.append("\n");
for (const auto& entry : ai.ai_entries) {
if (lines_remaining == 0) {
break;
}
lines_remaining -= 1;
char timebuf[64];
sql_strftime(timebuf,
sizeof(timebuf),
entry.e_mtime,
0,
'T');
al.append(" ")
.append(entry.e_mode)
.append(" ")
.appendf(
FMT_STRING("{:>8}"),
humanize::file_size(
entry.e_size.value(),
humanize::alignment::columnar))
.append(" ")
.append(timebuf)
.append(" ")
.append(lnav::roles::file(entry.e_name))
.append("\n");
}
}
} else {
al.append(describe_res.unwrapErr());
}
break;
}
case file_format_t::UNKNOWN: {
auto open_res
= lnav::filesystem::open_file(fn, O_RDONLY);
if (open_res.isErr()) {
return ec.make_error("unable to open -- {}", fn);
}
auto preview_fd = open_res.unwrap();
line_buffer lb;
file_range range;
lb.set_fd(preview_fd);
for (int lpc = 0; lpc < 10; lpc++) {
auto load_result = lb.load_next_line(range);
if (load_result.isErr()) {
break;
}
auto li = load_result.unwrap();
auto li = load_result.unwrap();
range = li.li_file_range;
auto read_result = lb.read_range(range);
if (read_result.isErr()) {
range = li.li_file_range;
if (!li.li_utf8_scan_result.is_valid()) {
range.fr_size = 16;
}
auto read_result = lb.read_range(range);
if (read_result.isErr()) {
break;
}
auto sbr = read_result.unwrap();
auto sf = sbr.to_string_fragment();
if (li.li_utf8_scan_result.is_valid()) {
alb.append(sf);
} else {
{
auto ag = alb.with_attr(
VC_ROLE.value(role_t::VCR_FILE_OFFSET));
alb.appendf(FMT_STRING("{: >16x} "),
range.fr_offset);
}
alb.append_as_hexdump(sf);
alb.append("\n");
}
}
break;
}
case file_format_t::SQLITE_DB: {
alb.append(fmt::to_string(detect_res));
break;
}
case file_format_t::REMOTE: {
break;
}
auto sbr = read_result.unwrap();
lines.append(sbr.get_data(), sbr.length());
}
lnav_data.ld_preview_view[0].set_sub_source(
&lnav_data.ld_preview_source[0]);
lnav_data.ld_preview_source[0]
.replace_with(al.with_string(lines))
.set_text_format(detect_text_format(al.get_string()));
lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
detect_text_format(al.get_string()));
lnav_data.ld_preview_status_source[0]
.get_description()
.set_value("For file: %s", fn.c_str());
@ -3719,7 +3803,8 @@ com_clear_comment(exec_context& ec,
if (tc != &lnav_data.ld_views[LNV_LOG]) {
return ec.make_error(
"The :clear-comment command only works in the log view");
"The :clear-comment command only works in the log "
"view");
}
auto& lss = lnav_data.ld_log_source;
@ -3859,7 +3944,8 @@ com_delete_tags(exec_context& ec,
if (tc != &lnav_data.ld_views[LNV_LOG]) {
return ec.make_error(
"The :delete-tag command only works in the log view");
"The :delete-tag command only works in the log "
"view");
}
auto& known_tags = bookmark_metadata::KNOWN_TAGS;
@ -4133,7 +4219,8 @@ com_summarize(exec_context& ec,
query += ",";
}
query_frag = sqlite3_mprintf(
" \"count_%s\" desc, \"c_%s\" collate naturalnocase asc",
" \"count_%s\" desc, \"c_%s\" collate "
"naturalnocase asc",
iter->c_str(),
iter->c_str());
query += query_frag;
@ -4437,7 +4524,8 @@ com_export_session_to(exec_context& ec,
setvbuf(stdout, nullptr, _IONBF, 0);
to_term = true;
fprintf(outfile,
"\n---------------- Press any key to exit lo-fi "
"\n---------------- Press any key to exit "
"lo-fi "
"display "
"----------------\n\n");
} else {
@ -4562,7 +4650,9 @@ com_toggle_field(exec_context& ec,
if (hide) {
if (lnav_data.ld_rl_view != nullptr) {
lnav_data.ld_rl_view->set_alt_value(
HELP_MSG_1(x, "to quickly show hidden fields"));
HELP_MSG_1(x,
"to quickly show hidden "
"fields"));
}
}
tc->set_needs_update();
@ -4613,10 +4703,11 @@ com_hide_line(exec_context& ec,
max_time_str, sizeof(max_time_str), max_time_opt.value());
}
if (min_time_opt && max_time_opt) {
retval = fmt::format(
FMT_STRING("info: hiding lines before {} and after {}"),
min_time_str,
max_time_str);
retval
= fmt::format(FMT_STRING("info: hiding lines before {} and "
"after {}"),
min_time_str,
max_time_str);
} else if (min_time_opt) {
retval = fmt::format(FMT_STRING("info: hiding lines before {}"),
min_time_str);
@ -4625,7 +4716,8 @@ com_hide_line(exec_context& ec,
max_time_str);
} else {
retval
= "info: no lines hidden by time, pass an absolute or "
= "info: no lines hidden by time, pass an "
"absolute or "
"relative time";
}
} else {
@ -4656,7 +4748,8 @@ com_hide_line(exec_context& ec,
}
} else {
return ec.make_error(
"relative time values only work in a time-based view");
"relative time values only work in a "
"time-based view");
}
} else if (dts.convert_to_timeval(all_args, tv_abs)) {
tv_opt = tv_abs;
@ -5569,8 +5662,9 @@ command_prompt(std::vector<std::string>& args)
rollback_lnav_config = lnav_config;
lnav_data.ld_doc_status_source.set_title("Command Help");
lnav_data.ld_doc_status_source.set_description(" See " ANSI_BOLD(
"https://docs.lnav.org/en/latest/commands.html") " for more details");
lnav_data.ld_doc_status_source.set_description(
" See " ANSI_BOLD("https://docs.lnav.org/en/latest/"
"commands.html") " for more details");
add_view_text_possibilities(lnav_data.ld_rl_view,
ln_mode_t::COMMAND,
"filter",
@ -5723,8 +5817,9 @@ sql_prompt(std::vector<std::string>& args)
cget(args, 3).value_or(""));
lnav_data.ld_doc_status_source.set_title("Query Help");
lnav_data.ld_doc_status_source.set_description("See " ANSI_BOLD(
"https://docs.lnav.org/en/latest/sqlext.html") " for more details");
lnav_data.ld_doc_status_source.set_description(
"See " ANSI_BOLD("https://docs.lnav.org/en/latest/"
"sqlext.html") " for more details");
rl_set_help();
lnav_data.ld_bottom_source.update_loading(0, 0);
lnav_data.ld_status[LNS_BOTTOM].do_update();
@ -5819,14 +5914,13 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":prompt")
.with_summary("Open the given prompt")
.with_parameter(
{"type",
"The type of prompt -- command, script, search, sql, user"})
.with_parameter(
help_text(
"--alt",
"Perform the alternate action for this prompt by default")
.optional())
.with_parameter({"type",
"The type of prompt -- command, script, "
"search, sql, user"})
.with_parameter(help_text("--alt",
"Perform the alternate action "
"for this prompt by default")
.optional())
.with_parameter(
help_text("prompt", "The prompt to display").optional())
.with_parameter(
@ -5834,7 +5928,8 @@ readline_context::command_t STD_COMMANDS[] = {
"The initial value to fill in for the prompt")
.optional())
.with_example({
"To open the command prompt with 'filter-in' already filled "
"To open the command prompt with 'filter-in' already "
"filled "
"in",
"command : 'filter-in '",
})
@ -5869,7 +5964,8 @@ readline_context::command_t STD_COMMANDS[] = {
"convert-time-to",
com_convert_time_to,
help_text(":convert-time-to")
.with_summary("Convert the focused timestamp to the given timezone")
.with_summary("Convert the focused timestamp to the "
"given timezone")
.with_parameter(help_text("zone", "The timezone name")),
},
{
@ -5877,7 +5973,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_set_file_timezone,
help_text(":set-file-timezone")
.with_summary("Set the timezone to use for log messages that do "
"not include a timezone. The timezone is applied to "
"not include a timezone. The timezone is applied "
"to "
"the focused file or the given glob pattern.")
.with_parameter({"zone", "The timezone name"})
.with_parameter(help_text{"pattern",
@ -5891,12 +5988,13 @@ readline_context::command_t STD_COMMANDS[] = {
"clear-file-timezone",
com_clear_file_timezone,
help_text(":clear-file-timezone")
.with_summary("Clear the timezone setting for the focused file or "
.with_summary("Clear the timezone setting for the "
"focused file or "
"the given glob pattern.")
.with_parameter(
help_text{"pattern",
"The glob pattern to match against files that should "
"no longer use this timezone"})
.with_parameter(help_text{"pattern",
"The glob pattern to match against files "
"that should "
"no longer use this timezone"})
.with_tags({"file-options"}),
com_clear_file_timezone_prompt,
},
@ -5918,7 +6016,8 @@ readline_context::command_t STD_COMMANDS[] = {
.with_examples(
{{"To go to line 22", "22"},
{"To go to the line 75% of the way into the view", "75%"},
{"To go to the first message on the first day of 2017",
{"To go to the first message on the first day of "
"2017",
"2017-01-01"},
{"To go to the Screenshots section", "#screenshots"}})
.with_tags({"navigation"})},
@ -5939,8 +6038,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_annotate,
help_text(":annotate")
.with_summary(
"Analyze the focused log message and attach annotations")
.with_summary("Analyze the focused log message and "
"attach annotations")
.with_tags({"metadata"}),
},
@ -5957,16 +6056,18 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":mark-expr")
.with_summary("Set the bookmark expression")
.with_parameter(help_text(
"expr",
"The SQL expression to evaluate for each log message. "
"The message values can be accessed using column names "
"prefixed with a colon"))
.with_parameter(help_text("expr",
"The SQL expression to evaluate for each "
"log message. "
"The message values can be accessed "
"using column names "
"prefixed with a colon"))
.with_opposites({"clear-mark-expr"})
.with_tags({"bookmarks"})
.with_example(
{"To mark lines from 'dhclient' that mention 'eth0'",
":log_procname = 'dhclient' AND :log_body LIKE '%eth0%'"}),
.with_example({"To mark lines from 'dhclient' that "
"mention 'eth0'",
":log_procname = 'dhclient' AND "
":log_body LIKE '%eth0%'"}),
com_mark_expr_prompt,
},
@ -5993,7 +6094,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_goto_mark,
help_text(":prev-mark")
.with_summary("Move to the previous bookmark of the given type in the "
.with_summary("Move to the previous bookmark of the given "
"type in the "
"current view")
.with_parameter(help_text("type",
"The type of bookmark -- error, warning, "
@ -6011,7 +6113,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_goto_location,
help_text(":prev-location")
.with_summary("Move to the previous position in the location history")
.with_summary("Move to the previous position in the "
"location history")
.with_tags({"navigation"})},
{
@ -6039,21 +6142,23 @@ readline_context::command_t STD_COMMANDS[] = {
com_toggle_field,
help_text(":hide-fields")
.with_summary(
"Hide log message fields by replacing them with an ellipsis")
.with_summary("Hide log message fields by replacing them "
"with an ellipsis")
.with_parameter(
help_text("field-name",
"The name of the field to hide in the format for the "
"The name of the field to hide in the format for "
"the "
"top log line. "
"A qualified name can be used where the field name is "
"A qualified name can be used where the field "
"name is "
"prefixed "
"by the format name and a dot to hide any field.")
.one_or_more())
.with_example(
{"To hide the log_procname fields in all formats", "log_procname"})
.with_example(
{"To hide only the log_procname field in the syslog format",
"syslog_log.log_procname"})
.with_example({"To hide only the log_procname field in "
"the syslog format",
"syslog_log.log_procname"})
.with_tags({"display"})},
{"show-fields",
com_toggle_field,
@ -6093,8 +6198,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_show_lines,
help_text(":show-lines-before-and-after")
.with_summary(
"Show lines that were hidden by the 'hide-lines' commands")
.with_summary("Show lines that were hidden by the "
"'hide-lines' commands")
.with_opposites({"hide-lines-before", "hide-lines-after"})
.with_tags({"filtering"})},
{"hide-unmarked-lines",
@ -6114,7 +6219,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_highlight,
help_text(":highlight")
.with_summary("Add coloring to log messages fragments that match the "
.with_summary("Add coloring to log messages fragments "
"that match the "
"given regular expression")
.with_parameter(
help_text("pattern", "The regular expression to match"))
@ -6126,9 +6232,9 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":clear-highlight")
.with_summary("Remove a previously set highlight regular expression")
.with_parameter(help_text(
"pattern",
"The regular expression previously used with :highlight"))
.with_parameter(help_text("pattern",
"The regular expression previously used "
"with :highlight"))
.with_tags({"display"})
.with_opposites({"highlight"})
.with_example(
@ -6153,13 +6259,14 @@ readline_context::command_t STD_COMMANDS[] = {
com_filter,
help_text(":filter-out")
.with_summary(
"Remove lines that match the given regular expression "
"in the current view")
.with_summary("Remove lines that match the given "
"regular expression "
"in the current view")
.with_parameter(
help_text("pattern", "The regular expression to match"))
.with_tags({"filtering"})
.with_example({"To filter out log messages that contain the string "
.with_example({"To filter out log messages that "
"contain the string "
"'last message repeated'",
"last message repeated"}),
com_filter_prompt,
@ -6183,22 +6290,25 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":filter-expr")
.with_summary("Set the filter expression")
.with_parameter(help_text(
"expr",
"The SQL expression to evaluate for each log message. "
"The message values can be accessed using column names "
"prefixed with a colon"))
.with_parameter(help_text("expr",
"The SQL expression to evaluate for each "
"log message. "
"The message values can be accessed "
"using column names "
"prefixed with a colon"))
.with_opposites({"clear-filter-expr"})
.with_tags({"filtering"})
.with_example({"To set a filter expression that matched syslog "
"messages from 'syslogd'",
":log_procname = 'syslogd'"})
.with_example(
{"To set a filter expression that matches log messages "
"where "
"'id' is followed by a number and contains the string "
"'foo'",
":log_body REGEXP 'id\\d+' AND :log_body REGEXP 'foo'"}),
.with_example({"To set a filter expression that "
"matches log messages "
"where "
"'id' is followed by a number and "
"contains the string "
"'foo'",
":log_body REGEXP 'id\\d+' AND "
":log_body REGEXP 'foo'"}),
com_filter_expr_prompt,
},
@ -6213,8 +6323,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_save_to,
help_text(":append-to")
.with_summary(
"Append marked lines in the current view to the given file")
.with_summary("Append marked lines in the current view to "
"the given file")
.with_parameter(help_text("path", "The path to the file to append to"))
.with_tags({"io"})
.with_example({"To append marked lines to the file "
@ -6224,7 +6334,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_save_to,
help_text(":write-to")
.with_summary("Overwrite the given file with any marked lines in the "
.with_summary("Overwrite the given file with any marked "
"lines in the "
"current view")
.with_parameter(
help_text("--anonymize", "Anonymize the lines").optional())
@ -6259,20 +6370,21 @@ readline_context::command_t STD_COMMANDS[] = {
com_save_to,
help_text(":write-jsonlines-to")
.with_summary(
"Write SQL results to the given file in JSON Lines format")
.with_summary("Write SQL results to the given file in "
"JSON Lines format")
.with_parameter(
help_text("--anonymize", "Anonymize the JSON values").optional())
.with_parameter(help_text("path", "The path to the file to write"))
.with_tags({"io", "scripting", "sql"})
.with_example({"To write SQL results as JSON Lines to /tmp/table.json",
.with_example({"To write SQL results as JSON Lines to "
"/tmp/table.json",
"/tmp/table.json"})},
{"write-table-to",
com_save_to,
help_text(":write-table-to")
.with_summary(
"Write SQL results to the given file in a tabular format")
.with_summary("Write SQL results to the given file in a "
"tabular format")
.with_parameter(
help_text("--anonymize", "Anonymize the table contents")
.optional())
@ -6284,10 +6396,10 @@ readline_context::command_t STD_COMMANDS[] = {
com_save_to,
help_text(":write-raw-to")
.with_summary(
"In the log view, write the original log file content "
"of the marked messages to the file. In the DB view, "
"the contents of the cells are written to the output file.")
.with_summary("In the log view, write the original log file content "
"of the marked messages to the file. In the DB view, "
"the contents of the cells are written to the output "
"file.")
.with_parameter(help_text("--view={log,db}",
"The view to use as the source of data")
.optional())
@ -6295,9 +6407,9 @@ readline_context::command_t STD_COMMANDS[] = {
help_text("--anonymize", "Anonymize the lines").optional())
.with_parameter(help_text("path", "The path to the file to write"))
.with_tags({"io", "scripting", "sql"})
.with_example(
{"To write the marked lines in the log view to /tmp/table.txt",
"/tmp/table.txt"})},
.with_example({"To write the marked lines in the log view "
"to /tmp/table.txt",
"/tmp/table.txt"})},
{"write-view-to",
com_save_to,
@ -6336,9 +6448,10 @@ readline_context::command_t STD_COMMANDS[] = {
com_pipe_to,
help_text(":pipe-line-to")
.with_summary(
"Pipe the focused line to the given shell command. Any fields "
"defined by the format will be set as environment variables.")
.with_summary("Pipe the focused line to the given shell "
"command. Any fields "
"defined by the format will be set as "
"environment variables.")
.with_parameter(
help_text("shell-cmd", "The shell command-line to execute"))
.with_tags({"io"})
@ -6350,12 +6463,11 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":redirect-to")
.with_summary("Redirect the output of commands that write to "
"stdout to the given file")
.with_parameter(
help_text(
"path",
"The path to the file to write."
" If not specified, the current redirect will be cleared")
.optional())
.with_parameter(help_text("path",
"The path to the file to write."
" If not specified, the current redirect "
"will be cleared")
.optional())
.with_tags({"io", "scripting"})
.with_example({"To write the output of lnav commands to the file "
"/tmp/script-output.txt",
@ -6369,7 +6481,8 @@ readline_context::command_t STD_COMMANDS[] = {
"pattern", "The regular expression used in the filter command"))
.with_tags({"filtering"})
.with_opposites({"disable-filter"})
.with_example({"To enable the disabled filter with the pattern 'last "
.with_example({"To enable the disabled filter with the "
"pattern 'last "
"message repeated'",
"last message repeated"})},
{"disable-filter",
@ -6401,13 +6514,14 @@ readline_context::command_t STD_COMMANDS[] = {
com_create_logline_table,
help_text(":create-logline-table")
.with_summary("Create an SQL table using the top line of the log view "
.with_summary("Create an SQL table using the top line of "
"the log view "
"as a template")
.with_parameter(help_text("table-name", "The name for the new table"))
.with_tags({"vtables", "sql"})
.with_example(
{"To create a logline-style table named 'task_durations'",
"task_durations"})},
.with_example({"To create a logline-style table named "
"'task_durations'",
"task_durations"})},
{"delete-logline-table",
com_delete_logline_table,
@ -6417,9 +6531,9 @@ readline_context::command_t STD_COMMANDS[] = {
help_text("table-name", "The name of the table to delete"))
.with_opposites({"delete-logline-table"})
.with_tags({"vtables", "sql"})
.with_example(
{"To delete the logline-style table named 'task_durations'",
"task_durations"})},
.with_example({"To delete the logline-style table named "
"'task_durations'",
"task_durations"})},
{"create-search-table",
com_create_search_table,
@ -6431,13 +6545,15 @@ readline_context::command_t STD_COMMANDS[] = {
help_text("pattern",
"The regular expression used to capture the table "
"columns. "
"If not given, the current search pattern is used.")
"If not given, the current search pattern is "
"used.")
.optional())
.with_tags({"vtables", "sql"})
.with_example(
{"To create a table named 'task_durations' that matches log "
"messages with the pattern 'duration=(?<duration>\\d+)'",
R"(task_durations duration=(?<duration>\d+))"})},
.with_example({"To create a table named 'task_durations' that "
"matches log "
"messages with the pattern "
"'duration=(?<duration>\\d+)'",
R"(task_durations duration=(?<duration>\d+))"})},
{"delete-search-table",
com_delete_search_table,
@ -6453,10 +6569,10 @@ readline_context::command_t STD_COMMANDS[] = {
com_open,
help_text(":open")
.with_summary(
"Open the given file(s) in lnav. Opening files on machines "
"accessible via SSH can be done using the syntax: "
"[user@]host:/path/to/logs")
.with_summary("Open the given file(s) in lnav. Opening files on "
"machines "
"accessible via SSH can be done using the syntax: "
"[user@]host:/path/to/logs")
.with_parameter(
help_text{"path", "The path to the file to open"}.one_or_more())
.with_example({"To open the file '/path/to/file'", "/path/to/file"})
@ -6469,8 +6585,9 @@ readline_context::command_t STD_COMMANDS[] = {
.with_summary("Hide the given file(s) and skip indexing until it "
"is shown again. If no path is given, the current "
"file in the view is hidden")
.with_parameter(help_text{
"path", "A path or glob pattern that specifies the files to hide"}
.with_parameter(help_text{"path",
"A path or glob pattern that "
"specifies the files to hide"}
.zero_or_more())
.with_opposites({"show-file"})},
{"show-file",
@ -6478,9 +6595,9 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":show-file")
.with_summary("Show the given file(s) and resume indexing.")
.with_parameter(help_text{
"path",
"The path or glob pattern that specifies the files to show"}
.with_parameter(help_text{"path",
"The path or glob pattern that "
"specifies the files to show"}
.zero_or_more())
.with_opposites({"hide-file"})},
{"show-only-this-file",
@ -6494,8 +6611,9 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":close")
.with_summary("Close the given file(s) or the top file in the view")
.with_parameter(help_text{
"path", "A path or glob pattern that specifies the files to close"}
.with_parameter(help_text{"path",
"A path or glob pattern that "
"specifies the files to close"}
.zero_or_more())
.with_opposites({"open"})},
{
@ -6544,7 +6662,8 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":untag")
.with_summary("Detach tags from the top log line")
.with_parameter(help_text("tag", "The tags to detach").one_or_more())
.with_example({"To remove the tags '#BUG123' and '#needs-review' from "
.with_example({"To remove the tags '#BUG123' and "
"'#needs-review' from "
"the top line",
"#BUG123 #needs-review"})
.with_opposites({"tag"})
@ -6555,7 +6674,8 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":delete-tags")
.with_summary("Remove the given tags from all log lines")
.with_parameter(help_text("tag", "The tags to delete").one_or_more())
.with_example({"To remove the tags '#BUG123' and '#needs-review' from "
.with_example({"To remove the tags '#BUG123' and "
"'#needs-review' from "
"all log lines",
"#BUG123 #needs-review"})
.with_opposites({"tag"})
@ -6580,17 +6700,18 @@ readline_context::command_t STD_COMMANDS[] = {
com_session,
help_text(":session")
.with_summary(
"Add the given command to the session file (~/.lnav/session)")
.with_summary("Add the given command to the session file "
"(~/.lnav/session)")
.with_parameter(help_text("lnav-command", "The lnav command to save."))
.with_example(
{"To add the command ':highlight foobar' to the session file",
":highlight foobar"})},
.with_example({"To add the command ':highlight foobar' to "
"the session file",
":highlight foobar"})},
{"summarize",
com_summarize,
help_text(":summarize")
.with_summary("Execute a SQL query that computes the characteristics "
.with_summary("Execute a SQL query that computes the "
"characteristics "
"of the values in the given column")
.with_parameter(
help_text("column-name", "The name of the column to analyze."))
@ -6609,12 +6730,13 @@ readline_context::command_t STD_COMMANDS[] = {
com_switch_to_view,
help_text(":toggle-view")
.with_summary(
"Switch to the given view or, if it is already displayed, "
"switch to the previous view")
.with_summary("Switch to the given view or, if it is "
"already displayed, "
"switch to the previous view")
.with_parameter(help_text(
"view-name", "The name of the view to toggle the display of."))
.with_example({"To switch to the 'schema' view if it is not displayed "
.with_example({"To switch to the 'schema' view if it is "
"not displayed "
"or switch back to the previous view",
"schema"})},
{"toggle-filtering",
@ -6675,15 +6797,17 @@ readline_context::command_t STD_COMMANDS[] = {
com_echo,
help_text(":echo")
.with_summary(
"Echo the given message to the screen or, if :redirect-to has "
"been called, to output file specified in the redirect. "
"Variable substitution is performed on the message. Use a "
"backslash to escape any special characters, like '$'")
.with_parameter(
help_text("-n",
"Do not print a line-feed at the end of the output")
.optional())
.with_summary("Echo the given message to the screen or, if "
":redirect-to has "
"been called, to output file specified in the "
"redirect. "
"Variable substitution is performed on the message. "
"Use a "
"backslash to escape any special characters, like '$'")
.with_parameter(help_text("-n",
"Do not print a line-feed at "
"the end of the output")
.optional())
.with_parameter(help_text("msg", "The message to display"))
.with_tags({"io", "scripting"})
.with_example({"To output 'Hello, World!'", "Hello, World!"})},
@ -6743,9 +6867,9 @@ readline_context::command_t STD_COMMANDS[] = {
"The value to write. If not given, the "
"current value is returned")
.optional())
.with_example(
{"To read the configuration of the '/ui/clock-format' option",
"/ui/clock-format"})
.with_example({"To read the configuration of the "
"'/ui/clock-format' option",
"/ui/clock-format"})
.with_example({"To set the '/ui/dim-text' option to 'false'",
"/ui/dim-text false"})
.with_tags({"configuration"})},
@ -6767,9 +6891,9 @@ readline_context::command_t STD_COMMANDS[] = {
"using a spectrogram")
.with_parameter(help_text(
"field-name", "The name of the numeric field to visualize."))
.with_example(
{"To visualize the sc_bytes field in the access_log format",
"sc_bytes"})},
.with_example({"To visualize the sc_bytes field in the "
"access_log format",
"sc_bytes"})},
{"quit",
com_quit,

@ -100,9 +100,6 @@ textfile_sub_source::text_value_for_line(textview_curses& tc,
if (lf->get_text_format() == text_format_t::TF_BINARY) {
this->tss_hex_line.clear();
attr_line_builder alb(this->tss_hex_line);
auto fsize = lf->get_stat().st_size;
auto fr = file_range{line * 16};
fr.fr_size = std::min((file_ssize_t) 16, fsize - fr.fr_offset);
@ -119,57 +116,12 @@ textfile_sub_source::text_value_for_line(textview_curses& tc,
auto sbr = read_res.unwrap();
auto sf = sbr.to_string_fragment();
attr_line_builder alb(this->tss_hex_line);
{
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_FILE_OFFSET));
alb.appendf(FMT_STRING("{: >16x} "), fr.fr_offset);
}
auto byte_off = size_t{0};
for (auto ch : sf) {
if (byte_off == 8) {
alb.append(" ");
}
nonstd::optional<role_t> ro;
if (ch == '\0') {
ro = role_t::VCR_NULL;
} else if (isspace(ch) || iscntrl(ch)) {
ro = role_t::VCR_ASCII_CTRL;
} else if (!isprint(ch)) {
ro = role_t::VCR_NON_ASCII;
}
auto ag = ro.has_value() ? alb.with_attr(VC_ROLE.value(ro.value()))
: alb.with_default();
alb.appendf(FMT_STRING(" {:0>2x}"), ch);
byte_off += 1;
}
for (; byte_off < 16; byte_off++) {
if (byte_off == 8) {
alb.append(" ");
}
alb.append(" ");
}
alb.append(" ");
byte_off = 0;
for (auto ch : sf) {
if (byte_off == 8) {
alb.append(" ");
}
if (ch == '\0') {
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NULL));
alb.append("\u22c4");
} else if (isspace(ch)) {
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_ASCII_CTRL));
alb.append("_");
} else if (iscntrl(ch)) {
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_ASCII_CTRL));
alb.append("\u2022");
} else if (isprint(ch)) {
this->tss_hex_line.get_string().push_back(ch);
} else {
auto ag = alb.with_attr(VC_ROLE.value(role_t::VCR_NON_ASCII));
alb.append("\u00d7");
}
byte_off += 1;
}
alb.append_as_hexdump(sf);
auto alt_row_index = line % 4;
if (alt_row_index == 2 || alt_row_index == 3) {
this->tss_hex_line.with_attr_for_all(

Loading…
Cancel
Save