summaryrefslogtreecommitdiffstats
path: root/src/lnav_commands.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lnav_commands.cc2258
1 files changed, 1731 insertions, 527 deletions
diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc
index d766d99..18f440b 100644
--- a/src/lnav_commands.cc
+++ b/src/lnav_commands.cc
@@ -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"
@@ -51,20 +52,27 @@
#include "base/paths.hh"
#include "base/string_util.hh"
#include "bound_tags.hh"
+#include "breadcrumb_curses.hh"
#include "command_executor.hh"
#include "config.h"
#include "curl_looper.hh"
+#include "date/tz.h"
#include "db_sub_source.hh"
#include "field_overlay_source.hh"
#include "fmt/printf.h"
+#include "hasher.hh"
+#include "itertools.similar.hh"
#include "lnav.indexing.hh"
#include "lnav_commands.hh"
#include "lnav_config.hh"
#include "lnav_util.hh"
+#include "log.annotate.hh"
#include "log_data_helper.hh"
#include "log_data_table.hh"
+#include "log_format_loader.hh"
#include "log_search_table.hh"
#include "log_search_table_fwd.hh"
+#include "ptimec.hh"
#include "readline_callbacks.hh"
#include "readline_curses.hh"
#include "readline_highlighters.hh"
@@ -80,13 +88,27 @@
#include "sysclip.hh"
#include "tailer/tailer.looper.hh"
#include "text_anonymizer.hh"
+#include "url_handler.cfg.hh"
#include "url_loader.hh"
#include "yajl/api/yajl_parse.h"
#include "yajlpp/json_op.hh"
#include "yajlpp/yajlpp.hh"
+#if !CURL_AT_LEAST_VERSION(7, 80, 0)
+extern "C"
+{
+const char* curl_url_strerror(CURLUcode error);
+}
+#endif
+
using namespace lnav::roles::literals;
+inline attr_line_t&
+symbol_reducer(const std::string& elem, attr_line_t& accum)
+{
+ return accum.append("\n ").append(lnav::roles::symbol(elem));
+}
+
static std::string
remaining_args(const std::string& cmdline,
const std::vector<std::string>& args,
@@ -261,7 +283,10 @@ com_unix_time(exec_context& ec,
char* rest;
u_time = time(nullptr);
- log_time = *localtime(&u_time);
+ if (localtime_r(&u_time, &log_time) == nullptr) {
+ return ec.make_error(
+ "invalid epoch time: {} -- {}", u_time, strerror(errno));
+ }
log_time.tm_isdst = -1;
@@ -282,7 +307,10 @@ com_unix_time(exec_context& ec,
u_time = mktime(&log_time);
parsed = true;
} else if (sscanf(args[1].c_str(), "%ld", &u_time)) {
- log_time = *localtime(&u_time);
+ if (localtime_r(&u_time, &log_time) == nullptr) {
+ return ec.make_error(
+ "invalid epoch time: {} -- {}", args[1], strerror(errno));
+ }
parsed = true;
}
@@ -293,7 +321,7 @@ com_unix_time(exec_context& ec,
strftime(ftime,
sizeof(ftime),
"%a %b %d %H:%M:%S %Y %z %Z",
- localtime(&u_time));
+ localtime_r(&u_time, &log_time));
len = strlen(ftime);
snprintf(ftime + len, sizeof(ftime) - len, " -- %ld", u_time);
retval = std::string(ftime);
@@ -308,6 +336,321 @@ com_unix_time(exec_context& ec,
}
static Result<std::string, lnav::console::user_message>
+com_set_file_timezone(exec_context& ec,
+ std::string cmdline,
+ std::vector<std::string>& args)
+{
+ static const intern_string_t SRC = intern_string::lookup("args");
+ std::string retval;
+
+ if (args.empty()) {
+ args.emplace_back("timezone");
+ return Ok(retval);
+ }
+
+ if (args.size() == 1) {
+ return ec.make_error("expecting a timezone name");
+ }
+
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
+
+ if (lss != nullptr) {
+ if (lss->text_line_count() == 0) {
+ return ec.make_error("no log messages to examine");
+ }
+
+ auto line_pair = lss->find_line_with_file(lss->at(tc->get_selection()));
+ if (!line_pair) {
+ return ec.make_error(FMT_STRING("cannot find line: {}"),
+ (int) tc->get_selection());
+ }
+
+ shlex lexer(cmdline);
+ auto split_res = lexer.split(ec.create_resolver());
+ if (split_res.isErr()) {
+ auto split_err = split_res.unwrapErr();
+ auto um = lnav::console::user_message::error(
+ "unable to parse arguments")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
+ }
+
+ auto split_args
+ = split_res.unwrap() | lnav::itertools::map([](const auto& elem) {
+ return elem.se_value;
+ });
+ try {
+ const auto* tz = date::locate_zone(split_args[1]);
+ auto pattern = split_args.size() == 2
+ ? line_pair->first->get_filename()
+ : ghc::filesystem::path(split_args[2]);
+
+ if (!ec.ec_dry_run) {
+ static auto& safe_options_hier
+ = injector::get<lnav::safe_file_options_hier&>();
+
+ safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
+ safe_options_hier);
+
+ options_hier->foh_generation += 1;
+ auto& coll = options_hier->foh_path_to_collection["/"];
+
+ log_info("setting timezone for %s to %s",
+ pattern.c_str(),
+ args[1].c_str());
+ coll.foc_pattern_to_options[pattern] = lnav::file_options{
+ {intern_string_t{}, source_location{}, tz},
+ };
+
+ auto opt_path = lnav::paths::dotlnav() / "file-options.json";
+ auto coll_str = coll.to_json();
+ lnav::filesystem::write_file(opt_path, coll_str);
+ }
+ } catch (const std::runtime_error& e) {
+ attr_line_t note;
+
+ try {
+ note = (date::get_tzdb().zones
+ | lnav::itertools::map(&date::time_zone::name)
+ | lnav::itertools::similar_to(split_args[1])
+ | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
+ .add_header("did you mean one of the following?");
+ } catch (const std::runtime_error& e) {
+ log_error("unable to get timezones: %s", e.what());
+ }
+ auto um = lnav::console::user_message::error(
+ attr_line_t()
+ .append_quoted(split_args[1])
+ .append(" is not a valid timezone"))
+ .with_reason(e.what())
+ .with_note(note);
+ return Err(um);
+ }
+ } else {
+ return ec.make_error(
+ ":set-file-timezone is only supported for the LOG view");
+ }
+
+ return Ok(retval);
+}
+
+static readline_context::prompt_result_t
+com_set_file_timezone_prompt(exec_context& ec, const std::string& cmdline)
+{
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
+
+ if (lss == nullptr || lss->text_line_count() == 0) {
+ return {};
+ }
+
+ shlex lexer(cmdline);
+ auto split_res = lexer.split(ec.create_resolver());
+ if (split_res.isErr()) {
+ return {};
+ }
+
+ auto line_pair = lss->find_line_with_file(lss->at(tc->get_selection()));
+ if (!line_pair) {
+ return {};
+ }
+
+ auto elems = split_res.unwrap();
+ auto pattern_arg = line_pair->first->get_filename();
+ if (elems.size() == 1) {
+ try {
+ static auto& safe_options_hier
+ = injector::get<lnav::safe_file_options_hier&>();
+
+ safe::ReadAccess<lnav::safe_file_options_hier> options_hier(
+ safe_options_hier);
+ auto file_zone = date::get_tzdb().current_zone()->name();
+ auto match_res = options_hier->match(pattern_arg);
+ if (match_res) {
+ file_zone = match_res->second.fo_default_zone.pp_value->name();
+ pattern_arg = match_res->first;
+
+ auto new_prompt = fmt::format(FMT_STRING("{} {} {}"),
+ trim(cmdline),
+ file_zone,
+ pattern_arg);
+
+ return {new_prompt};
+ }
+
+ return {"", file_zone + " "};
+ } catch (const std::runtime_error& e) {
+ log_error("cannot get timezones: %s", e.what());
+ }
+ }
+ auto arg_path = ghc::filesystem::path(pattern_arg);
+ auto arg_parent = arg_path.parent_path().string() + "/";
+ if (elems.size() == 2 && endswith(cmdline, " ")) {
+ return {"", arg_parent};
+ }
+ if (elems.size() == 3 && elems.back().se_value == arg_parent) {
+ return {"", arg_path.filename().string()};
+ }
+
+ return {};
+}
+
+static readline_context::prompt_result_t
+com_clear_file_timezone_prompt(exec_context& ec, const std::string& cmdline)
+{
+ std::string retval;
+
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
+
+ if (lss != nullptr && lss->text_line_count() > 0) {
+ auto line_pair = lss->find_line_with_file(lss->at(tc->get_selection()));
+ if (line_pair) {
+ try {
+ static auto& safe_options_hier
+ = injector::get<lnav::safe_file_options_hier&>();
+
+ safe::ReadAccess<lnav::safe_file_options_hier> options_hier(
+ safe_options_hier);
+ auto file_zone = date::get_tzdb().current_zone()->name();
+ auto pattern_arg = line_pair->first->get_filename();
+ auto match_res
+ = options_hier->match(line_pair->first->get_filename());
+ if (match_res) {
+ file_zone
+ = match_res->second.fo_default_zone.pp_value->name();
+ pattern_arg = match_res->first;
+ }
+
+ retval = fmt::format(
+ FMT_STRING("{} {}"), trim(cmdline), pattern_arg);
+ } catch (const std::runtime_error& e) {
+ log_error("cannot get timezones: %s", e.what());
+ }
+ }
+ }
+
+ return {retval};
+}
+
+static Result<std::string, lnav::console::user_message>
+com_clear_file_timezone(exec_context& ec,
+ std::string cmdline,
+ std::vector<std::string>& args)
+{
+ std::string retval;
+
+ if (args.empty()) {
+ args.emplace_back("file-with-zone");
+ return Ok(retval);
+ }
+
+ if (args.size() != 2) {
+ return ec.make_error("expecting a single file path or pattern");
+ }
+
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
+
+ if (lss != nullptr) {
+ if (!ec.ec_dry_run) {
+ static auto& safe_options_hier
+ = injector::get<lnav::safe_file_options_hier&>();
+
+ safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
+ safe_options_hier);
+
+ options_hier->foh_generation += 1;
+ auto& coll = options_hier->foh_path_to_collection["/"];
+ const auto iter = coll.foc_pattern_to_options.find(args[1]);
+
+ if (iter == coll.foc_pattern_to_options.end()) {
+ return ec.make_error(FMT_STRING("no timezone set for: {}"),
+ args[1]);
+ }
+
+ log_info("clearing timezone for %s", args[1].c_str());
+ iter->second.fo_default_zone.pp_value = nullptr;
+ if (iter->second.empty()) {
+ coll.foc_pattern_to_options.erase(iter);
+ }
+
+ auto opt_path = lnav::paths::dotlnav() / "file-options.json";
+ auto coll_str = coll.to_json();
+ lnav::filesystem::write_file(opt_path, coll_str);
+ }
+ } else {
+ return ec.make_error(
+ ":clear-file-timezone is only supported for the LOG view");
+ }
+
+ return Ok(retval);
+}
+
+static Result<std::string, lnav::console::user_message>
+com_convert_time_to(exec_context& ec,
+ std::string cmdline,
+ std::vector<std::string>& args)
+{
+ std::string retval;
+
+ if (args.empty()) {
+ args.emplace_back("timezone");
+ return Ok(retval);
+ }
+
+ if (args.size() == 1) {
+ return ec.make_error("expecting a timezone name");
+ }
+
+ const auto* tc = *lnav_data.ld_view_stack.top();
+ auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
+
+ if (lss != nullptr) {
+ if (lss->text_line_count() == 0) {
+ return ec.make_error("no log messages to examine");
+ }
+
+ const auto* ll = lss->find_line(lss->at(tc->get_selection()));
+ try {
+ auto* dst_tz = date::locate_zone(args[1]);
+ auto utime = date::local_time<std::chrono::seconds>{
+ std::chrono::seconds{ll->get_time()}};
+ auto cz_time = lnav::to_sys_time(utime);
+ auto dz_time = date::make_zoned(dst_tz, cz_time);
+ auto etime = std::chrono::duration_cast<std::chrono::seconds>(
+ dz_time.get_local_time().time_since_epoch());
+ char ftime[128];
+ sql_strftime(
+ ftime, sizeof(ftime), etime.count(), ll->get_millis(), 'T');
+ retval = ftime;
+
+ off_t off = 0;
+ exttm tm;
+ tm.et_flags |= ETF_ZONE_SET;
+ tm.et_gmtoff = dz_time.get_info().offset.count();
+ ftime_Z(ftime, off, sizeof(ftime), tm);
+ ftime[off] = '\0';
+ retval.append(" ");
+ retval.append(ftime);
+ } catch (const std::runtime_error& e) {
+ return ec.make_error(FMT_STRING("Unable to get timezone: {} -- {}"),
+ args[1],
+ e.what());
+ }
+ } else {
+ return ec.make_error(
+ ":convert-time-to is only supported for the LOG view");
+ }
+
+ return Ok(retval);
+}
+
+static Result<std::string, lnav::console::user_message>
com_current_time(exec_context& ec,
std::string cmdline,
std::vector<std::string>& args)
@@ -356,6 +699,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
std::string all_args = remaining_args(cmdline, args);
auto* tc = *lnav_data.ld_view_stack.top();
nonstd::optional<vis_line_t> dst_vl;
+ auto is_location = false;
if (startswith(all_args, "#")) {
auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
@@ -368,6 +712,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (!dst_vl) {
return ec.make_error("unable to find anchor: {}", all_args);
}
+ is_location = true;
}
auto* ttt = dynamic_cast<text_time_translator*>(tc->get_sub_source());
@@ -383,7 +728,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
auto top_time_opt = ttt->time_for_row(tc->get_selection());
if (top_time_opt) {
- auto top_time_tv = top_time_opt.value();
+ auto top_time_tv = top_time_opt.value().ri_time;
struct tm top_tm;
localtime_r(&top_time_tv.tv_sec, &top_tm);
@@ -404,7 +749,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (!tv_opt) {
return ec.make_error("cannot get time for the top row");
}
- tv = tv_opt.value();
+ tv = tv_opt.value().ri_time;
vis_line_t vl = tc->get_selection(), new_vl;
bool done = false;
@@ -485,7 +830,10 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
alb.append("^");
if (unmatched_size > 1) {
- alb.append(unmatched_size - 2, '-').append("^");
+ if (unmatched_size > 2) {
+ alb.append(unmatched_size - 2, '-');
+ }
+ alb.append("^");
}
alb.append(" unrecognized input");
}
@@ -509,8 +857,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
{
tm.et_nsec = 0;
}
- tv.tv_sec = tm2sec(&tm.et_tm);
- tv.tv_usec = tm.et_nsec / 1000;
+ tv = tm.to_timeval();
dst_vl = ttt->row_for_time(tv);
} else if (sscanf(args[1].c_str(), "%f%n", &value, &consumed) == 1) {
if (args[1][consumed] == '%') {
@@ -534,7 +881,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
return Err(um);
}
- dst_vl | [&ec, tc, &retval](auto new_top) {
+ dst_vl | [&ec, tc, &retval, is_location](auto new_top) {
if (ec.ec_dry_run) {
retval = "info: will move to line "
+ std::to_string((int) new_top);
@@ -542,6 +889,9 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
tc->get_sub_source()->get_location_history() |
[new_top](auto lh) { lh->loc_history_append(new_top); };
tc->set_selection(new_top);
+ if (tc->is_selectable() && is_location) {
+ tc->set_top(new_top - 2_vl, false);
+ }
retval = "";
}
@@ -576,7 +926,8 @@ com_relative_goto(exec_context& ec,
retval = "info: shifting top by " + std::to_string(line_offset)
+ " lines";
} else {
- tc->shift_top(vis_line_t(line_offset), true);
+ tc->set_selection(tc->get_selection()
+ + vis_line_t(line_offset));
retval = "";
}
@@ -591,6 +942,40 @@ com_relative_goto(exec_context& ec,
}
static Result<std::string, lnav::console::user_message>
+com_annotate(exec_context& ec,
+ std::string cmdline,
+ std::vector<std::string>& args)
+{
+ std::string retval;
+
+ if (args.empty()) {
+ } else if (!ec.ec_dry_run) {
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
+
+ if (lss != nullptr) {
+ auto sel = tc->get_selection();
+ auto applicable_annos = lnav::log::annotate::applicable(sel);
+
+ if (applicable_annos.empty()) {
+ return ec.make_error(
+ "no annotations available for this log message");
+ }
+
+ auto apply_res = lnav::log::annotate::apply(sel, applicable_annos);
+ if (apply_res.isErr()) {
+ return Err(apply_res.unwrapErr());
+ }
+ } else {
+ return ec.make_error(
+ ":annotate is only supported for the LOG view");
+ }
+ }
+
+ return Ok(retval);
+}
+
+static Result<std::string, lnav::console::user_message>
com_mark(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
{
std::string retval;
@@ -660,7 +1045,7 @@ com_mark_expr(exec_context& ec,
if (set_res.isErr()) {
return Err(set_res.unwrapErr());
}
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"Matches are highlighted in the text view");
} else {
auto set_res = lss.set_sql_marker(expr, stmt.release());
@@ -674,18 +1059,20 @@ com_mark_expr(exec_context& ec,
return Ok(retval);
}
-static std::string
+static readline_context::prompt_result_t
com_mark_expr_prompt(exec_context& ec, const std::string& cmdline)
{
textview_curses* tc = *lnav_data.ld_view_stack.top();
if (tc != &lnav_data.ld_views[LNV_LOG]) {
- return "";
+ return {""};
}
- return fmt::format(FMT_STRING("{} {}"),
- trim(cmdline),
- trim(lnav_data.ld_log_source.get_sql_marker_text()));
+ return {
+ fmt::format(FMT_STRING("{} {}"),
+ trim(cmdline),
+ trim(lnav_data.ld_log_source.get_sql_marker_text())),
+ };
}
static Result<std::string, lnav::console::user_message>
@@ -829,13 +1216,80 @@ com_goto_location(exec_context& ec,
? lh->loc_history_back(tc->get_selection())
: lh->loc_history_forward(tc->get_selection());
}
- | [tc](auto new_top) { tc->set_selection(new_top); };
+ | [tc](auto new_top) {
+ tc->set_selection(new_top);
+ if (tc->is_selectable()) {
+ tc->set_top(new_top - 2_vl, false);
+ }
+ };
};
}
return Ok(retval);
}
+static Result<std::string, lnav::console::user_message>
+com_next_section(exec_context& ec,
+ std::string cmdline,
+ std::vector<std::string>& args)
+{
+ std::string retval;
+
+ if (args.empty()) {
+ } else if (!ec.ec_dry_run) {
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
+
+ if (ta == nullptr) {
+ return ec.make_error("view does not support sections");
+ }
+
+ auto adj_opt = ta->adjacent_anchor(tc->get_selection(),
+ text_anchors::direction::next);
+ if (!adj_opt) {
+ return ec.make_error("no next section found");
+ }
+
+ tc->set_selection(adj_opt.value());
+ if (tc->is_selectable()) {
+ tc->set_top(adj_opt.value() - 2_vl, false);
+ }
+ }
+
+ return Ok(retval);
+}
+
+static Result<std::string, lnav::console::user_message>
+com_prev_section(exec_context& ec,
+ std::string cmdline,
+ std::vector<std::string>& args)
+{
+ std::string retval;
+
+ if (args.empty()) {
+ } else if (!ec.ec_dry_run) {
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
+
+ if (ta == nullptr) {
+ return ec.make_error("view does not support sections");
+ }
+
+ auto adj_opt = ta->adjacent_anchor(tc->get_selection(),
+ text_anchors::direction::prev);
+ if (!adj_opt) {
+ return ec.make_error("no previous section found");
+ }
+
+ tc->set_selection(adj_opt.value());
+ if (tc->is_selectable()) {
+ tc->set_top(adj_opt.value() - 2_vl, false);
+ }
+ }
+
+ return Ok(retval);
+}
+
static bool
csv_needs_quoting(const std::string& str)
{
@@ -909,15 +1363,14 @@ json_write_row(yajl_gen handle,
case SQLITE_TEXT:
switch (hm.hm_sub_type) {
case 74: {
- auto_mem<yajl_handle_t> parse_handle(yajl_free);
unsigned char* err;
json_ptr jp("");
json_op jo(jp);
jo.jo_ptr_callbacks = json_op::gen_callbacks;
jo.jo_ptr_data = handle;
- parse_handle.reset(
- yajl_alloc(&json_op::ptr_callbacks, nullptr, &jo));
+ auto parse_handle = yajlpp::alloc_handle(
+ &json_op::ptr_callbacks, &jo);
const unsigned char* json_in
= (const unsigned char*) dls.dls_rows[row][col];
@@ -962,14 +1415,14 @@ json_write_row(yajl_gen handle,
default:
obj_map.gen(anonymize
? ta.next(string_fragment::from_c_str(
- dls.dls_rows[row][col]))
+ dls.dls_rows[row][col]))
: dls.dls_rows[row][col]);
break;
}
break;
default:
obj_map.gen(anonymize ? ta.next(string_fragment::from_c_str(
- dls.dls_rows[row][col]))
+ dls.dls_rows[row][col]))
: dls.dls_rows[row][col]);
break;
}
@@ -981,6 +1434,8 @@ com_save_to(exec_context& ec,
std::string cmdline,
std::vector<std::string>& args)
{
+ static const intern_string_t SRC = intern_string::lookup("path");
+
FILE *outfile = nullptr, *toclose = nullptr;
const char* mode = "";
std::string fn, retval;
@@ -995,13 +1450,22 @@ com_save_to(exec_context& ec,
fn = trim(remaining_args(cmdline, args));
- std::vector<std::string> split_args;
shlex lexer(fn);
- if (!lexer.split(split_args, ec.create_resolver())) {
- return ec.make_error("unable to parse arguments");
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um
+ = lnav::console::user_message::error("unable to parse file name")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
}
+ auto split_args = split_args_res.unwrap()
+ | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
auto anon_iter
= std::find(split_args.begin(), split_args.end(), "--anonymize");
if (anon_iter != split_args.end()) {
@@ -1031,9 +1495,9 @@ com_save_to(exec_context& ec,
}
if (args[0] == "append-to") {
- mode = "a";
+ mode = "ae";
} else {
- mode = "w";
+ mode = "we";
}
auto& dls = lnav_data.ld_db_row_source;
@@ -1259,31 +1723,33 @@ com_save_to(exec_context& ec,
} else if (args[0] == "write-screen-to") {
bool wrapped = tc->get_word_wrap();
vis_line_t orig_top = tc->get_top();
+ auto inner_height = tc->get_inner_height();
tc->set_word_wrap(to_term);
vis_line_t top = tc->get_top();
vis_line_t bottom = tc->get_bottom();
- if (lnav_data.ld_flags & LNF_HEADLESS && tc->get_inner_height() > 0_vl)
- {
- bottom = tc->get_inner_height() - 1_vl;
+ if (lnav_data.ld_flags & LNF_HEADLESS && inner_height > 0_vl) {
+ bottom = inner_height - 1_vl;
}
+ auto screen_height = inner_height == 0 ? 0 : bottom - top + 1;
auto y = 0_vl;
auto wrapped_count = 0_vl;
- std::vector<attr_line_t> rows(bottom - top + 1);
+ std::vector<attr_line_t> rows(screen_height);
auto dim = tc->get_dimensions();
attr_line_t ov_al;
auto* los = tc->get_overlay_source();
+ while (
+ los != nullptr
+ && los->list_static_overlay(*tc, y, tc->get_inner_height(), ov_al))
+ {
+ write_line_to(outfile, ov_al);
+ ov_al.clear();
+ ++y;
+ }
tc->listview_value_for_rows(*tc, top, rows);
for (auto& al : rows) {
- while (los != nullptr
- && los->list_value_for_overlay(
- *tc, y, tc->get_inner_height(), top, ov_al))
- {
- write_line_to(outfile, ov_al);
- ++y;
- }
wrapped_count += vis_line_t((al.length() - 1) / (dim.second - 2));
if (anonymize) {
al.al_attrs.clear();
@@ -1291,17 +1757,18 @@ com_save_to(exec_context& ec,
}
write_line_to(outfile, al);
+ ++y;
+ if (los != nullptr) {
+ std::vector<attr_line_t> row_overlay_content;
+ los->list_value_for_overlay(*tc, top, row_overlay_content);
+ for (const auto& ov_row : row_overlay_content) {
+ write_line_to(outfile, ov_row);
+ line_count += 1;
+ ++y;
+ }
+ }
line_count += 1;
++top;
- ++y;
- }
- while (los != nullptr
- && los->list_value_for_overlay(
- *tc, y, tc->get_inner_height(), top, ov_al)
- && !ov_al.empty())
- {
- write_line_to(outfile, ov_al);
- ++y;
}
tc->set_word_wrap(wrapped);
@@ -1411,10 +1878,25 @@ com_save_to(exec_context& ec,
tc->set_word_wrap(wrapped);
} else {
auto* los = tc->get_overlay_source();
+ auto* fos = dynamic_cast<field_overlay_source*>(los);
std::vector<attr_line_t> rows(1);
attr_line_t ov_al;
size_t count = 0;
+ if (fos != nullptr) {
+ fos->fos_contexts.push(
+ field_overlay_source::context{"", false, false, false});
+ }
+
+ auto y = 0_vl;
+ while (
+ los != nullptr
+ && los->list_static_overlay(*tc, y, tc->get_inner_height(), ov_al))
+ {
+ write_line_to(outfile, ov_al);
+ ov_al.clear();
+ ++y;
+ }
for (auto iter = all_user_marks.begin(); iter != all_user_marks.end();
iter++, count++)
{
@@ -1428,17 +1910,22 @@ com_save_to(exec_context& ec,
}
write_line_to(outfile, rows[0]);
- auto y = 1_vl;
- while (los != nullptr
- && los->list_value_for_overlay(
- *tc, y, tc->get_inner_height(), *iter, ov_al))
- {
- write_line_to(outfile, ov_al);
- ++y;
+ y = 0_vl;
+ if (los != nullptr) {
+ std::vector<attr_line_t> row_overlay_content;
+ los->list_value_for_overlay(*tc, (*iter), row_overlay_content);
+ for (const auto& ov_row : row_overlay_content) {
+ write_line_to(outfile, ov_row);
+ line_count += 1;
+ ++y;
+ }
}
-
line_count += 1;
}
+
+ if (fos != nullptr) {
+ fos->fos_contexts.pop();
+ }
}
fflush(outfile);
@@ -1457,10 +1944,13 @@ com_save_to(exec_context& ec,
attr_line_t al(std::string(buffer, rc));
- lnav_data.ld_preview_source.replace_with(al)
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ lnav_data.ld_preview_source[0]
+ .replace_with(al)
.set_text_format(detect_text_format(al.get_string()))
.truncate_to(10);
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"First lines of file: %s", split_args[0].c_str());
} else {
retval = fmt::format(FMT_STRING("info: Wrote {:L} rows to {}"),
@@ -1502,18 +1992,63 @@ com_pipe_to(exec_context& ec,
auto* tc = *lnav_data.ld_view_stack.top();
auto bv = combined_user_marks(tc->get_bookmarks());
bool pipe_line_to = (args[0] == "pipe-line-to");
+ auto path_v = ec.ec_path_stack;
+ std::map<std::string, std::string> extra_env;
+
+ if (pipe_line_to && tc == &lnav_data.ld_views[LNV_LOG]) {
+ log_data_helper ldh(lnav_data.ld_log_source);
+ char tmp_str[64];
+
+ ldh.parse_line(ec.ec_top_line, true);
+ auto format = ldh.ldh_file->get_format();
+ auto source_path = format->get_source_path();
+ path_v.insert(path_v.end(), source_path.begin(), source_path.end());
+
+ extra_env["log_line"] = fmt::to_string((int) ec.ec_top_line);
+ sql_strftime(tmp_str, sizeof(tmp_str), ldh.ldh_line->get_timeval());
+ extra_env["log_time"] = tmp_str;
+ extra_env["log_path"] = ldh.ldh_file->get_filename();
+ extra_env["log_level"] = ldh.ldh_line->get_level_name();
+ if (ldh.ldh_line_values.lvv_opid_value) {
+ extra_env["log_opid"] = ldh.ldh_line_values.lvv_opid_value.value();
+ }
+ auto read_res = ldh.ldh_file->read_raw_message(ldh.ldh_line);
+ if (read_res.isOk()) {
+ auto raw_text = to_string(read_res.unwrap());
+ extra_env["log_raw_text"] = raw_text;
+ }
+ for (auto& ldh_line_value : ldh.ldh_line_values.lvv_values) {
+ extra_env[ldh_line_value.lv_meta.lvm_name.to_string()]
+ = ldh_line_value.to_string();
+ }
+ auto iter = ldh.ldh_parser->dp_pairs.begin();
+ for (size_t lpc = 0; lpc < ldh.ldh_parser->dp_pairs.size();
+ lpc++, ++iter)
+ {
+ std::string colname = ldh.ldh_parser->get_element_string(
+ iter->e_sub_elements->front());
+ colname = ldh.ldh_namer->add_column(colname).to_string();
+ std::string val = ldh.ldh_parser->get_element_string(
+ iter->e_sub_elements->back());
+ extra_env[colname] = val;
+ }
+ }
std::string cmd = trim(remaining_args(cmdline, args));
- auto_pipe in_pipe(STDIN_FILENO);
- auto_pipe out_pipe(STDOUT_FILENO);
+ auto for_child_res = auto_pipe::for_child_fds(STDIN_FILENO, STDOUT_FILENO);
+
+ if (for_child_res.isErr()) {
+ return ec.make_error(FMT_STRING("unable to open pipe to child: {}"),
+ for_child_res.unwrapErr());
+ }
- in_pipe.open();
- out_pipe.open();
+ auto child_fds = for_child_res.unwrap();
pid_t child_pid = fork();
- in_pipe.after_fork(child_pid);
- out_pipe.after_fork(child_pid);
+ for (auto& child_fd : child_fds) {
+ child_fd.after_fork(child_pid);
+ }
switch (child_pid) {
case -1:
@@ -1521,55 +2056,22 @@ com_pipe_to(exec_context& ec,
strerror(errno));
case 0: {
- const char* args[] = {
+ const char* exec_args[] = {
"sh",
"-c",
cmd.c_str(),
nullptr,
};
- auto path_v = ec.ec_path_stack;
std::string path;
dup2(STDOUT_FILENO, STDERR_FILENO);
path_v.emplace_back(lnav::paths::dotlnav() / "formats/default");
- if (pipe_line_to && tc == &lnav_data.ld_views[LNV_LOG]) {
- logfile_sub_source& lss = lnav_data.ld_log_source;
- log_data_helper ldh(lss);
- char tmp_str[64];
-
- ldh.parse_line(ec.ec_top_line, true);
- auto format = ldh.ldh_file->get_format();
- auto source_path = format->get_source_path();
- path_v.insert(
- path_v.end(), source_path.begin(), source_path.end());
-
- snprintf(tmp_str, sizeof(tmp_str), "%d", (int) ec.ec_top_line);
- setenv("log_line", tmp_str, 1);
- sql_strftime(
- tmp_str, sizeof(tmp_str), ldh.ldh_line->get_timeval());
- setenv("log_time", tmp_str, 1);
- setenv("log_path", ldh.ldh_file->get_filename().c_str(), 1);
- for (auto& ldh_line_value : ldh.ldh_line_values.lvv_values) {
- setenv(ldh_line_value.lv_meta.lvm_name.get(),
- ldh_line_value.to_string().c_str(),
- 1);
- }
- auto iter = ldh.ldh_parser->dp_pairs.begin();
- for (size_t lpc = 0; lpc < ldh.ldh_parser->dp_pairs.size();
- lpc++, ++iter)
- {
- std::string colname = ldh.ldh_parser->get_element_string(
- iter->e_sub_elements->front());
- colname = ldh.ldh_namer->add_column(colname).to_string();
- std::string val = ldh.ldh_parser->get_element_string(
- iter->e_sub_elements->back());
- setenv(colname.c_str(), val.c_str(), 1);
- }
- }
-
setenv("PATH", lnav::filesystem::build_path(path_v).c_str(), 1);
- execvp(args[0], (char* const*) args);
+ for (const auto& pair : extra_env) {
+ setenv(pair.first.c_str(), pair.second.c_str(), 1);
+ }
+ execvp(exec_args[0], (char* const*) exec_args);
_exit(1);
break;
}
@@ -1578,15 +2080,13 @@ com_pipe_to(exec_context& ec,
bookmark_vector<vis_line_t>::iterator iter;
std::string line;
- in_pipe.read_end().close_on_exec();
- in_pipe.write_end().close_on_exec();
-
lnav_data.ld_children.push_back(child_pid);
std::future<std::string> reader;
- if (out_pipe.read_end() != -1) {
- reader = ec.ec_pipe_callback(ec, cmdline, out_pipe.read_end());
+ if (child_fds[1].read_end() != -1) {
+ reader
+ = ec.ec_pipe_callback(ec, cmdline, child_fds[1].read_end());
}
if (pipe_line_to) {
@@ -1599,37 +2099,41 @@ com_pipe_to(exec_context& ec,
shared_buffer_ref sbr;
lf->read_full_message(lf->message_start(lf->begin() + cl),
sbr);
- if (write(in_pipe.write_end(), sbr.get_data(), sbr.length())
+ if (write(child_fds[0].write_end(),
+ sbr.get_data(),
+ sbr.length())
== -1)
{
return ec.make_error("Unable to write to pipe -- {}",
strerror(errno));
}
- log_perror(write(in_pipe.write_end(), "\n", 1));
+ log_perror(write(child_fds[0].write_end(), "\n", 1));
} else {
tc->grep_value_for_line(tc->get_top(), line);
- if (write(in_pipe.write_end(), line.c_str(), line.size())
+ if (write(
+ child_fds[0].write_end(), line.c_str(), line.size())
== -1)
{
return ec.make_error("Unable to write to pipe -- {}",
strerror(errno));
}
- log_perror(write(in_pipe.write_end(), "\n", 1));
+ log_perror(write(child_fds[0].write_end(), "\n", 1));
}
} else {
for (iter = bv.begin(); iter != bv.end(); iter++) {
tc->grep_value_for_line(*iter, line);
- if (write(in_pipe.write_end(), line.c_str(), line.size())
+ if (write(
+ child_fds[0].write_end(), line.c_str(), line.size())
== -1)
{
return ec.make_error("Unable to write to pipe -- {}",
strerror(errno));
}
- log_perror(write(in_pipe.write_end(), "\n", 1));
+ log_perror(write(child_fds[0].write_end(), "\n", 1));
}
}
- in_pipe.write_end().reset();
+ child_fds[0].write_end().reset();
if (reader.valid()) {
retval = reader.get();
@@ -1647,6 +2151,8 @@ com_redirect_to(exec_context& ec,
std::string cmdline,
std::vector<std::string>& args)
{
+ static const intern_string_t SRC = intern_string::lookup("path");
+
if (args.empty()) {
args.emplace_back("filename");
return Ok(std::string());
@@ -1662,16 +2168,21 @@ com_redirect_to(exec_context& ec,
}
std::string fn = trim(remaining_args(cmdline, args));
- std::vector<std::string> split_args;
shlex lexer(fn);
- scoped_resolver scopes = {
- &ec.ec_local_vars.top(),
- &ec.ec_global_vars,
- };
- if (!lexer.split(split_args, scopes)) {
- return ec.make_error("unable to parse arguments");
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um
+ = lnav::console::user_message::error("unable to parse file name")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
}
+ auto split_args = split_args_res.unwrap()
+ | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
if (split_args.size() > 1) {
return ec.make_error("more than one file name was matched");
}
@@ -1750,7 +2261,7 @@ com_highlight(exec_context& ec,
if (ec.ec_dry_run) {
hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"Matches are highlighted in the view");
retval = "";
@@ -1870,9 +2381,12 @@ com_filter(exec_context& ec,
}
if (ec.ec_dry_run) {
if (args[0] == "filter-in" && !fs.empty()) {
- lnav_data.ld_preview_status_source.get_description().set_value(
- "Match preview for :filter-in only works if there are no "
- "other filters");
+ lnav_data.ld_preview_status_source[0]
+ .get_description()
+ .set_value(
+ "Match preview for :filter-in only works if there are "
+ "no "
+ "other filters");
retval = "";
} else {
auto& hm = tc->get_highlights();
@@ -1885,9 +2399,11 @@ com_filter(exec_context& ec,
hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
tc->reload_data();
- lnav_data.ld_preview_status_source.get_description().set_value(
- "Matches are highlighted in %s in the text view",
- role == role_t::VCR_DIFF_DELETE ? "red" : "green");
+ lnav_data.ld_preview_status_source[0]
+ .get_description()
+ .set_value(
+ "Matches are highlighted in %s in the text view",
+ role == role_t::VCR_DIFF_DELETE ? "red" : "green");
retval = "";
}
@@ -1918,6 +2434,24 @@ com_filter(exec_context& ec,
return Ok(retval);
}
+static readline_context::prompt_result_t
+com_filter_prompt(exec_context& ec, const std::string& cmdline)
+{
+ const auto* tc = lnav_data.ld_view_stack.top().value();
+ std::vector<std::string> args;
+
+ split_ws(cmdline, args);
+ if (args.size() > 1) {
+ return {};
+ }
+
+ if (tc->tc_selected_text) {
+ return {"", tc->tc_selected_text->sti_value};
+ }
+
+ return {"", tc->get_current_search()};
+}
+
static Result<std::string, lnav::console::user_message>
com_delete_filter(exec_context& ec,
std::string cmdline,
@@ -2074,7 +2608,7 @@ com_filter_expr(exec_context& ec,
if (set_res.isErr()) {
return Err(set_res.unwrapErr());
}
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"Matches are highlighted in the text view");
} else {
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
@@ -2094,18 +2628,20 @@ com_filter_expr(exec_context& ec,
return Ok(retval);
}
-static std::string
+static readline_context::prompt_result_t
com_filter_expr_prompt(exec_context& ec, const std::string& cmdline)
{
- textview_curses* tc = *lnav_data.ld_view_stack.top();
+ auto* tc = *lnav_data.ld_view_stack.top();
if (tc != &lnav_data.ld_views[LNV_LOG]) {
- return "";
+ return {""};
}
- return fmt::format(FMT_STRING("{} {}"),
- trim(cmdline),
- trim(lnav_data.ld_log_source.get_sql_filter_text()));
+ return {
+ fmt::format(FMT_STRING("{} {}"),
+ trim(cmdline),
+ trim(lnav_data.ld_log_source.get_sql_filter_text())),
+ };
}
static Result<std::string, lnav::console::user_message>
@@ -2187,9 +2723,12 @@ com_create_logline_table(exec_context& ec,
if (ec.ec_dry_run) {
attr_line_t al(ldt->get_table_statement());
- lnav_data.ld_preview_status_source.get_description().set_value(
- "The following table will be created:");
- lnav_data.ld_preview_source.replace_with(al).set_text_format(
+ lnav_data.ld_preview_status_source[0]
+ .get_description()
+ .set_value("The following table will be created:");
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
text_format_t::TF_SQL);
return Ok(std::string());
@@ -2304,10 +2843,12 @@ com_create_search_table(exec_context& ec,
attr_line_t al(lst->get_table_statement());
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"The following table will be created:");
- lnav_data.ld_preview_source.replace_with(al).set_text_format(
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
text_format_t::TF_SQL);
return Ok(std::string());
@@ -2444,6 +2985,7 @@ com_session(exec_context& ec,
static Result<std::string, lnav::console::user_message>
com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
{
+ static const intern_string_t SRC = intern_string::lookup("path");
std::string retval;
if (args.empty()) {
@@ -2465,19 +3007,30 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
pat = trim(remaining_args(cmdline, args));
- std::vector<std::string> split_args;
shlex lexer(pat);
- scoped_resolver scopes = {
- &ec.ec_local_vars.top(),
- &ec.ec_global_vars,
- };
-
- if (!lexer.split(split_args, scopes)) {
- return ec.make_error("unable to parse arguments");
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um
+ = lnav::console::user_message::error("unable to parse file names")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
}
+ auto split_args = split_args_res.unwrap()
+ | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
+
std::vector<std::pair<std::string, file_location_t>> files_to_front;
std::vector<std::string> closed_files;
+ logfile_open_options loo;
+
+ auto prov = ec.get_provenance<exec_context::file_open>();
+ if (prov) {
+ loo.with_filename(prov->fo_name);
+ }
for (auto fn : split_args) {
file_location_t file_loc;
@@ -2498,6 +3051,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
file_loc = fn.substr(hash_index);
fn = fn.substr(0, hash_index);
}
+ loo.with_init_location(file_loc);
}
auto file_iter = lnav_data.ld_active_files.fc_files.begin();
@@ -2520,6 +3074,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (file_iter == lnav_data.ld_active_files.fc_files.end()) {
auto_mem<char> abspath;
struct stat st;
+ size_t url_index;
if (is_url(fn.c_str())) {
#ifndef HAVE_LIBCURL
@@ -2528,8 +3083,9 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (!ec.ec_dry_run) {
auto ul = std::make_shared<url_loader>(fn);
- lnav_data.ld_active_files.fc_file_names[fn].with_fd(
- ul->copy_fd());
+ lnav_data.ld_active_files.fc_file_names[ul->get_path()]
+ .with_filename(fn)
+ .with_init_location(file_loc);
isc::to<curl_looper&, services::curl_streamer_t>().send(
[ul](auto& clooper) { clooper.add_request(ul); });
lnav_data.ld_files_to_front.emplace_back(fn, file_loc);
@@ -2538,12 +3094,69 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
retval = "";
}
#endif
- } else if (is_glob(fn.c_str())) {
- fc.fc_file_names.emplace(fn, logfile_open_options());
+ } else if ((url_index = fn.find("://")) != std::string::npos) {
+ const auto& cfg
+ = injector::get<const lnav::url_handler::config&>();
+ const auto HOST_REGEX
+ = lnav::pcre2pp::code::from_const("://(?:\\?|$)");
+
+ auto find_res = HOST_REGEX.find_in(fn).ignore_error();
+ if (find_res) {
+ fn.insert(url_index + 3, "localhost");
+ }
+
+ auto_mem<CURLU> cu(curl_url_cleanup);
+ cu = curl_url();
+ auto set_rc = curl_url_set(
+ cu, CURLUPART_URL, fn.c_str(), CURLU_NON_SUPPORT_SCHEME);
+ if (set_rc != CURLUE_OK) {
+ return Err(lnav::console::user_message::error(
+ attr_line_t("invalid URL: ")
+ .append(lnav::roles::file(fn)))
+ .with_reason(curl_url_strerror(set_rc)));
+ }
+
+ auto_mem<char> scheme_part(curl_free);
+ auto get_rc
+ = curl_url_get(cu, CURLUPART_SCHEME, scheme_part.out(), 0);
+ if (get_rc != CURLUE_OK) {
+ return Err(lnav::console::user_message::error(
+ attr_line_t("cannot get scheme from URL: ")
+ .append(lnav::roles::file(fn)))
+ .with_reason(curl_url_strerror(set_rc)));
+ }
+
+ auto proto_iter = cfg.c_schemes.find(scheme_part.in());
+ if (proto_iter == cfg.c_schemes.end()) {
+ return Err(
+ lnav::console::user_message::error(
+ attr_line_t("no defined handler for URL scheme: ")
+ .append(lnav::roles::file(scheme_part.in())))
+ .with_reason(curl_url_strerror(set_rc)));
+ }
+
+ auto path_and_args
+ = fmt::format(FMT_STRING("{} {}"),
+ proto_iter->second.p_handler.pp_value,
+ fn);
+
+ exec_context::provenance_guard pg(&ec,
+ exec_context::file_open{fn});
+
+ auto exec_res = execute_file(ec, path_and_args);
+ if (exec_res.isErr()) {
+ return exec_res;
+ }
+
+ retval = "info: watching -- " + fn;
+ } else if (lnav::filesystem::is_glob(fn.c_str())) {
+ fc.fc_file_names.emplace(fn, loo);
+ files_to_front.emplace_back(
+ loo.loo_filename.empty() ? fn : loo.loo_filename, file_loc);
retval = "info: watching -- " + fn;
} else if (stat(fn.c_str(), &st) == -1) {
if (fn.find(':') != std::string::npos) {
- fc.fc_file_names.emplace(fn, logfile_open_options());
+ fc.fc_file_names.emplace(fn, loo);
retval = "info: watching -- " + fn;
} else {
auto um = lnav::console::user_message::error(
@@ -2571,25 +3184,23 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
} else if (ec.ec_dry_run) {
retval = "";
} else {
- auto fifo_piper = std::make_shared<piper_proc>(
- std::move(fifo_fd),
- false,
- lnav::filesystem::open_temp_file(
- ghc::filesystem::temp_directory_path()
- / "lnav.fifo.XXXXXX")
- .map([](auto pair) {
- ghc::filesystem::remove(pair.first);
-
- return pair;
- })
- .expect("Cannot create temporary file for FIFO")
- .second);
- auto fifo_out_fd = fifo_piper->get_fd();
auto desc = fmt::format(FMT_STRING("FIFO [{}]"),
lnav_data.ld_fifo_counter++);
- lnav_data.ld_active_files.fc_file_names[desc].with_fd(
- std::move(fifo_out_fd));
- lnav_data.ld_pipers.push_back(fifo_piper);
+ if (prov) {
+ desc = prov->fo_name;
+ }
+ auto create_piper_res = lnav::piper::create_looper(
+ desc, std::move(fifo_fd), auto_fd{});
+ if (create_piper_res.isErr()) {
+ auto um = lnav::console::user_message::error(
+ attr_line_t("cannot create piper: ")
+ .append(lnav::roles::file(fn)))
+ .with_reason(create_piper_res.unwrapErr())
+ .with_snippets(ec.ec_source);
+ return Err(um);
+ }
+ lnav_data.ld_active_files.fc_file_names[desc].with_piper(
+ create_piper_res.unwrap());
}
} else if ((abspath = realpath(fn.c_str(), nullptr)) == nullptr) {
auto um = lnav::console::user_message::error(
@@ -2607,8 +3218,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (dir_wild[dir_wild.size() - 1] == '/') {
dir_wild.resize(dir_wild.size() - 1);
}
- fc.fc_file_names.emplace(dir_wild + "/*",
- logfile_open_options());
+ fc.fc_file_names.emplace(dir_wild + "/*", loo);
retval = "info: watching -- " + dir_wild;
} else if (!S_ISREG(st.st_mode)) {
auto um = lnav::console::user_message::error(
@@ -2632,7 +3242,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
return Err(um);
} else {
fn = abspath.in();
- fc.fc_file_names.emplace(fn, logfile_open_options());
+ fc.fc_file_names.emplace(fn, loo);
retval = "info: opened -- " + fn;
files_to_front.emplace_back(fn, file_loc);
@@ -2646,31 +3256,37 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
}
if (ec.ec_dry_run) {
- lnav_data.ld_preview_source.clear();
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ 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.get_description()
+ lnav_data.ld_preview_status_source[0]
+ .get_description()
.set_cylon(true)
- .set_value("Loading %s...", fn.c_str());
- lnav_data.ld_preview_source.clear();
+ .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.set_needs_update();
- } else if (is_glob(fn.c_str())) {
+ lnav_data.ld_preview_view[0].set_needs_update();
+ } 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++)
@@ -2683,45 +3299,134 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
std::to_string(gl->gl_pathc - 10)))
.append(" files not shown ...");
}
- lnav_data.ld_preview_status_source.get_description()
+ lnav_data.ld_preview_status_source[0]
+ .get_description()
.set_value("The following files will be loaded:");
- lnav_data.ld_preview_source.replace_with(al);
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &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_source.replace_with(al.with_string(lines))
- .set_text_format(detect_text_format(al.get_string()));
- lnav_data.ld_preview_status_source.get_description().set_value(
- "For file: %s", fn.c_str());
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ 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());
}
}
} else {
@@ -2741,50 +3446,103 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
static Result<std::string, lnav::console::user_message>
com_close(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
{
+ static const intern_string_t SRC = intern_string::lookup("path");
std::string retval;
if (args.empty()) {
- } else {
- textview_curses* tc = *lnav_data.ld_view_stack.top();
- nonstd::optional<ghc::filesystem::path> actual_path;
- std::string fn;
+ args.emplace_back("loaded-files");
+ return Ok(retval);
+ }
- if (tc == &lnav_data.ld_views[LNV_TEXT]) {
- textfile_sub_source& tss = lnav_data.ld_text_source;
+ auto* tc = *lnav_data.ld_view_stack.top();
+ std::vector<nonstd::optional<ghc::filesystem::path>> actual_path_v;
+ std::vector<std::string> fn_v;
- if (tss.empty()) {
- return ec.make_error("no text files are opened");
- } else {
- fn = tss.current_file()->get_filename();
- lnav_data.ld_active_files.request_close(tss.current_file());
+ if (args.size() > 1) {
+ auto lexer = shlex(cmdline);
- if (tss.size() == 1) {
- lnav_data.ld_view_stack.pop_back();
- }
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um = lnav::console::user_message::error(
+ "unable to parse file name")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
+ }
+
+ auto args = split_args_res.unwrap()
+ | lnav::itertools::map(
+ [](const auto& elem) { return elem.se_value; });
+ args.erase(args.begin());
+
+ for (const auto& lf : lnav_data.ld_active_files.fc_files) {
+ if (lf.get() == nullptr) {
+ continue;
}
- } else if (tc == &lnav_data.ld_views[LNV_LOG]) {
- if (tc->get_inner_height() == 0) {
- return ec.make_error("no log files loaded");
- } else {
- logfile_sub_source& lss = lnav_data.ld_log_source;
- vis_line_t vl = tc->get_selection();
- content_line_t cl = lss.at(vl);
- std::shared_ptr<logfile> lf = lss.find(cl);
- actual_path = lf->get_actual_path();
- fn = lf->get_filename();
- if (!ec.ec_dry_run) {
- lnav_data.ld_active_files.request_close(lf);
- }
+ auto find_iter
+ = find_if(args.begin(), args.end(), [&lf](const auto& arg) {
+ return fnmatch(arg.c_str(), lf->get_filename().c_str(), 0)
+ == 0;
+ });
+
+ if (find_iter == args.end()) {
+ continue;
+ }
+
+ actual_path_v.push_back(lf->get_actual_path());
+ fn_v.emplace_back(lf->get_filename());
+ if (!ec.ec_dry_run) {
+ lnav_data.ld_active_files.request_close(lf);
+ }
+ }
+ } else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
+ auto& tss = lnav_data.ld_text_source;
+
+ if (tss.empty()) {
+ return ec.make_error("no text files are opened");
+ } else if (!ec.ec_dry_run) {
+ auto lf = tss.current_file();
+ actual_path_v.emplace_back(lf->get_actual_path());
+ fn_v.emplace_back(lf->get_filename());
+ lnav_data.ld_active_files.request_close(lf);
+
+ if (tss.size() == 1) {
+ lnav_data.ld_view_stack.pop_back();
}
} else {
- return ec.make_error(
- "close must be run in the log or text file views");
+ retval = fmt::format(FMT_STRING("closing -- {}"),
+ tss.current_file()->get_filename());
}
- if (!fn.empty()) {
- if (ec.ec_dry_run) {
- retval = "";
- } else {
+ } else if (tc == &lnav_data.ld_views[LNV_LOG]) {
+ if (tc->get_inner_height() == 0) {
+ return ec.make_error("no log files loaded");
+ } else {
+ auto& lss = lnav_data.ld_log_source;
+ auto vl = tc->get_selection();
+ auto cl = lss.at(vl);
+ auto lf = lss.find(cl);
+
+ actual_path_v.push_back(lf->get_actual_path());
+ fn_v.emplace_back(lf->get_filename());
+ if (!ec.ec_dry_run) {
+ lnav_data.ld_active_files.request_close(lf);
+ }
+ }
+ } else {
+ return ec.make_error("close must be run in the log or text file views");
+ }
+ if (!fn_v.empty()) {
+ if (ec.ec_dry_run) {
+ retval = "";
+ } else {
+ for (size_t lpc = 0; lpc < actual_path_v.size(); lpc++) {
+ const auto& fn = fn_v[lpc];
+ const auto& actual_path = actual_path_v[lpc];
+
if (is_url(fn.c_str())) {
isc::to<curl_looper&, services::curl_streamer_t>().send(
[fn](auto& clooper) { clooper.close_request(fn); });
@@ -2794,8 +3552,9 @@ com_close(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
actual_path.value().string());
}
lnav_data.ld_active_files.fc_closed_files.insert(fn);
- retval = "info: closed -- " + fn;
}
+ retval = fmt::format(FMT_STRING("info: closed -- {}"),
+ fmt::join(fn_v, ", "));
}
}
@@ -2807,6 +3566,7 @@ com_file_visibility(exec_context& ec,
std::string cmdline,
std::vector<std::string>& args)
{
+ static const intern_string_t SRC = intern_string::lookup("path");
bool only_this_file = false;
bool make_visible;
std::string retval;
@@ -2821,11 +3581,11 @@ com_file_visibility(exec_context& ec,
}
if (args.size() == 1 || only_this_file) {
- textview_curses* tc = *lnav_data.ld_view_stack.top();
+ auto* tc = *lnav_data.ld_view_stack.top();
std::shared_ptr<logfile> lf;
if (tc == &lnav_data.ld_views[LNV_TEXT]) {
- textfile_sub_source& tss = lnav_data.ld_text_source;
+ const auto& tss = lnav_data.ld_text_source;
if (tss.empty()) {
return ec.make_error("no text files are opened");
@@ -2848,8 +3608,10 @@ com_file_visibility(exec_context& ec,
if (only_this_file) {
for (const auto& ld : lnav_data.ld_log_source) {
ld->set_visibility(false);
+ ld->get_file_ptr()->set_indexing(false);
}
}
+ lf->set_indexing(make_visible);
lnav_data.ld_log_source.find_data(lf) |
[make_visible](auto ld) { ld->set_visibility(make_visible); };
tc->get_sub_source()->text_filters_changed();
@@ -2858,10 +3620,25 @@ com_file_visibility(exec_context& ec,
make_visible ? "showing" : "hiding",
lf->get_filename());
} else {
+ auto* top_tc = *lnav_data.ld_view_stack.top();
int text_file_count = 0, log_file_count = 0;
auto lexer = shlex(cmdline);
- lexer.split(args, ec.create_resolver());
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um = lnav::console::user_message::error(
+ "unable to parse file name")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
+ }
+
+ auto args = split_args_res.unwrap()
+ | lnav::itertools::map(
+ [](const auto& elem) { return elem.se_value; });
args.erase(args.begin());
for (const auto& lf : lnav_data.ld_active_files.fc_files) {
@@ -2887,6 +3664,7 @@ com_file_visibility(exec_context& ec,
if (!ec.ec_dry_run) {
ld_opt | [make_visible](auto ld) {
+ ld->get_file_ptr()->set_indexing(make_visible);
ld->set_visibility(make_visible);
};
}
@@ -2900,6 +3678,11 @@ com_file_visibility(exec_context& ec,
lnav_data.ld_views[LNV_LOG]
.get_sub_source()
->text_filters_changed();
+ if (top_tc == &lnav_data.ld_views[LNV_GANTT]) {
+ lnav_data.ld_views[LNV_GANTT]
+ .get_sub_source()
+ ->text_filters_changed();
+ }
}
if (!ec.ec_dry_run && text_file_count > 0) {
lnav_data.ld_views[LNV_TEXT]
@@ -3007,13 +3790,13 @@ com_comment(exec_context& ec,
return Ok(retval);
}
-static std::string
+static readline_context::prompt_result_t
com_comment_prompt(exec_context& ec, const std::string& cmdline)
{
- textview_curses* tc = *lnav_data.ld_view_stack.top();
+ auto* tc = *lnav_data.ld_view_stack.top();
if (tc != &lnav_data.ld_views[LNV_LOG]) {
- return "";
+ return {""};
}
auto& lss = lnav_data.ld_log_source;
@@ -3024,10 +3807,10 @@ com_comment_prompt(exec_context& ec, const std::string& cmdline)
auto buf = auto_buffer::alloc(trimmed_comment.size() + 16);
quote_content(buf, trimmed_comment, 0);
- return trim(cmdline) + " " + buf.to_string();
+ return {trim(cmdline) + " " + buf.to_string()};
}
- return "";
+ return {""};
}
static Result<std::string, lnav::console::user_message>
@@ -3046,7 +3829,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;
@@ -3055,10 +3839,12 @@ com_clear_comment(exec_context& ec,
bookmark_metadata& line_meta = *(line_meta_opt.value());
line_meta.bm_comment.clear();
- if (line_meta.empty()) {
- lss.erase_bookmark_metadata(tc->get_selection());
+ if (line_meta.empty(bookmark_metadata::categories::notes)) {
tc->set_user_mark(
&textview_curses::BM_META, tc->get_selection(), false);
+ if (line_meta.empty(bookmark_metadata::categories::any)) {
+ lss.erase_bookmark_metadata(tc->get_selection());
+ }
}
lss.set_line_meta_changed();
@@ -3138,7 +3924,7 @@ com_untag(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
auto line_meta_opt = lss.find_bookmark_metadata(tc->get_selection());
if (line_meta_opt) {
- bookmark_metadata& line_meta = *(line_meta_opt.value());
+ auto& line_meta = *(line_meta_opt.value());
for (size_t lpc = 1; lpc < args.size(); lpc++) {
std::string tag = args[lpc];
@@ -3148,7 +3934,7 @@ com_untag(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
}
line_meta.remove_tag(tag);
}
- if (line_meta.empty()) {
+ if (line_meta.empty(bookmark_metadata::categories::notes)) {
tc->set_user_mark(
&textview_curses::BM_META, tc->get_selection(), false);
}
@@ -3184,7 +3970,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;
@@ -3220,12 +4007,15 @@ com_delete_tags(exec_context& ec,
line_meta->remove_tag(tag);
}
- if (line_meta->empty()) {
- lss.erase_bookmark_metadata(*iter);
- size_t off = distance(vbm.begin(), iter);
+ if (line_meta->empty(bookmark_metadata::categories::notes)) {
+ size_t off = std::distance(vbm.begin(), iter);
+ auto vl = *iter;
+ tc->set_user_mark(&textview_curses::BM_META, vl, false);
+ if (line_meta->empty(bookmark_metadata::categories::any)) {
+ lss.erase_bookmark_metadata(vl);
+ }
- tc->set_user_mark(&textview_curses::BM_META, *iter, false);
- iter = next(vbm.begin(), off);
+ iter = std::next(vbm.begin(), off);
} else {
++iter;
}
@@ -3258,7 +4048,7 @@ com_partition_name(exec_context& ec,
args[1] = trim(remaining_args(cmdline, args));
tc.set_user_mark(
- &textview_curses::BM_META, tc.get_selection(), true);
+ &textview_curses::BM_PARTITION, tc.get_selection(), true);
auto& line_meta = lss.get_bookmark_metadata(tc.get_selection());
@@ -3284,7 +4074,7 @@ com_clear_partition(exec_context& ec,
} else if (args.size() == 1) {
textview_curses& tc = lnav_data.ld_views[LNV_LOG];
logfile_sub_source& lss = lnav_data.ld_log_source;
- auto& bv = tc.get_bookmarks()[&textview_curses::BM_META];
+ auto& bv = tc.get_bookmarks()[&textview_curses::BM_PARTITION];
nonstd::optional<vis_line_t> part_start;
if (binary_search(bv.begin(), bv.end(), tc.get_selection())) {
@@ -3300,10 +4090,12 @@ com_clear_partition(exec_context& ec,
auto& line_meta = lss.get_bookmark_metadata(part_start.value());
line_meta.bm_name.clear();
- if (line_meta.empty()) {
- lss.erase_bookmark_metadata(part_start.value());
+ if (line_meta.empty(bookmark_metadata::categories::partition)) {
tc.set_user_mark(
- &textview_curses::BM_META, part_start.value(), false);
+ &textview_curses::BM_PARTITION, part_start.value(), false);
+ if (line_meta.empty(bookmark_metadata::categories::any)) {
+ lss.erase_bookmark_metadata(part_start.value());
+ }
}
retval = "info: cleared partition name";
@@ -3453,7 +4245,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;
@@ -3638,7 +4431,7 @@ com_zoom_to(exec_context& ec,
auto old_time_opt = lnav_data.ld_hist_source2.time_for_row(
lnav_data.ld_views[LNV_HISTOGRAM].get_top());
if (old_time_opt) {
- old_time = old_time_opt.value();
+ old_time = old_time_opt.value().ri_time;
rebuild_hist();
lnav_data.ld_hist_source2.row_for_time(old_time) |
[](auto new_top) {
@@ -3659,7 +4452,7 @@ com_zoom_to(exec_context& ec,
spectro_view.reload_data();
if (old_time_opt) {
lnav_data.ld_spectro_source->row_for_time(
- old_time_opt.value())
+ old_time_opt.value().ri_time)
| [](auto new_top) {
lnav_data.ld_views[LNV_SPECTRO].set_selection(
new_top);
@@ -3709,6 +4502,7 @@ com_load_session(exec_context& ec,
if (args.empty()) {
} else if (!ec.ec_dry_run) {
load_session();
+ lnav::session::restore_view_states();
lnav_data.ld_views[LNV_LOG].reload_data();
}
@@ -3755,10 +4549,11 @@ com_export_session_to(exec_context& ec,
tcsetattr(1, TCSANOW, &curr_termios);
setvbuf(stdout, nullptr, _IONBF, 0);
to_term = true;
- fprintf(
- outfile,
- "\n---------------- Press any key to exit lo-fi display "
- "----------------\n\n");
+ fprintf(outfile,
+ "\n---------------- Press any key to exit "
+ "lo-fi "
+ "display "
+ "----------------\n\n");
} else {
outfile = auto_mem<FILE>::leak(ec_out.value());
}
@@ -3881,7 +4676,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();
@@ -3915,35 +4712,38 @@ com_hide_line(exec_context& ec,
if (args.empty()) {
args.emplace_back("move-time");
} else if (args.size() == 1) {
- textview_curses* tc = *lnav_data.ld_view_stack.top();
- logfile_sub_source& lss = lnav_data.ld_log_source;
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto& lss = lnav_data.ld_log_source;
if (tc == &lnav_data.ld_views[LNV_LOG]) {
- struct timeval min_time, max_time;
- bool have_min_time = lss.get_min_log_time(min_time);
- bool have_max_time = lss.get_max_log_time(max_time);
+ auto min_time_opt = lss.get_min_log_time();
+ auto max_time_opt = lss.get_max_log_time();
char min_time_str[32], max_time_str[32];
- if (have_min_time) {
- sql_strftime(min_time_str, sizeof(min_time_str), min_time);
+ if (min_time_opt) {
+ sql_strftime(
+ min_time_str, sizeof(min_time_str), min_time_opt.value());
}
- if (have_max_time) {
- sql_strftime(max_time_str, sizeof(max_time_str), max_time);
+ if (max_time_opt) {
+ sql_strftime(
+ max_time_str, sizeof(max_time_str), max_time_opt.value());
}
- if (have_min_time && have_max_time) {
+ if (min_time_opt && max_time_opt) {
retval
- = fmt::format("info: hiding lines before {} and after {}",
+ = fmt::format(FMT_STRING("info: hiding lines before {} and "
+ "after {}"),
min_time_str,
max_time_str);
- } else if (have_min_time) {
- retval
- = fmt::format("info: hiding lines before {}", min_time_str);
- } else if (have_max_time) {
- retval
- = fmt::format("info: hiding lines after {}", max_time_str);
+ } else if (min_time_opt) {
+ retval = fmt::format(FMT_STRING("info: hiding lines before {}"),
+ min_time_str);
+ } else if (max_time_opt) {
+ retval = fmt::format(FMT_STRING("info: hiding lines after {}"),
+ 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 {
@@ -3952,55 +4752,55 @@ com_hide_line(exec_context& ec,
}
} else if (args.size() >= 2) {
std::string all_args = remaining_args(cmdline, args);
- textview_curses* tc = *lnav_data.ld_view_stack.top();
- logfile_sub_source& lss = lnav_data.ld_log_source;
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto* ttt = dynamic_cast<text_time_translator*>(tc->get_sub_source());
+ auto& lss = lnav_data.ld_log_source;
date_time_scanner dts;
- struct timeval tv;
- bool tv_set = false;
+ struct timeval tv_abs;
+ nonstd::optional<timeval> tv_opt;
auto parse_res = relative_time::from_str(all_args);
if (parse_res.isOk()) {
- if (tc == &lnav_data.ld_views[LNV_LOG]) {
+ if (ttt != nullptr) {
if (tc->get_inner_height() > 0) {
- content_line_t cl;
struct exttm tm;
- vis_line_t vl;
- logline* ll;
-
- vl = tc->get_selection();
- cl = lnav_data.ld_log_source.at(vl);
- ll = lnav_data.ld_log_source.find_line(cl);
- ll->to_exttm(tm);
- tv = parse_res.unwrap().adjust(tm).to_timeval();
- tv_set = true;
+ auto vl = tc->get_selection();
+ auto log_vl_ri = ttt->time_for_row(vl);
+ if (log_vl_ri) {
+ tm = exttm::from_tv(log_vl_ri.value().ri_time);
+ tv_opt = parse_res.unwrap().adjust(tm).to_timeval();
+ }
}
} else {
return ec.make_error(
- "relative time values only work in the log view");
- }
- } else if (dts.convert_to_timeval(all_args, tv)) {
- if (tc == &lnav_data.ld_views[LNV_LOG]) {
- tv_set = true;
- } else {
- return ec.make_error("time values only work in the log 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;
}
- if (tv_set && !ec.ec_dry_run) {
+ if (tv_opt && !ec.ec_dry_run) {
char time_text[256];
std::string relation;
- sql_strftime(time_text, sizeof(time_text), tv);
+ sql_strftime(time_text, sizeof(time_text), tv_opt.value());
if (args[0] == "hide-lines-before") {
- lss.set_min_log_time(tv);
+ lss.set_min_log_time(tv_opt.value());
relation = "before";
} else {
- lss.set_max_log_time(tv);
+ lss.set_max_log_time(tv_opt.value());
relation = "after";
}
- retval = "info: hiding lines " + relation + " " + time_text;
+ if (ttt != nullptr && tc != &lnav_data.ld_views[LNV_LOG]) {
+ tc->get_sub_source()->text_filters_changed();
+ tc->reload_data();
+ }
+
+ retval = fmt::format(
+ FMT_STRING("info: hiding lines {} {}"), relation, time_text);
}
}
@@ -4085,6 +4885,219 @@ com_rebuild(exec_context& ec,
}
static Result<std::string, lnav::console::user_message>
+com_cd(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
+{
+ static const intern_string_t SRC = intern_string::lookup("path");
+
+ if (args.empty()) {
+ args.emplace_back("dirname");
+ return Ok(std::string());
+ }
+
+ if (lnav_data.ld_flags & LNF_SECURE_MODE) {
+ return ec.make_error("{} -- unavailable in secure mode", args[0]);
+ }
+
+ std::vector<std::string> word_exp;
+ std::string pat;
+
+ pat = trim(remaining_args(cmdline, args));
+
+ shlex lexer(pat);
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um
+ = lnav::console::user_message::error("unable to parse file name")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
+ }
+
+ auto split_args = split_args_res.unwrap()
+ | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
+
+ if (split_args.size() != 1) {
+ return ec.make_error("expecting a single argument");
+ }
+
+ struct stat st;
+
+ if (stat(split_args[0].c_str(), &st) != 0) {
+ return Err(ec.make_error_msg("cannot access -- {}", split_args[0])
+ .with_errno_reason());
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ return ec.make_error("{} is not a directory", split_args[0]);
+ }
+
+ if (!ec.ec_dry_run) {
+ chdir(split_args[0].c_str());
+ }
+
+ return Ok(std::string());
+}
+
+static Result<std::string, lnav::console::user_message>
+com_sh(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
+{
+ if (args.empty()) {
+ args.emplace_back("filename");
+ return Ok(std::string());
+ }
+
+ if (lnav_data.ld_flags & LNF_SECURE_MODE) {
+ return ec.make_error("{} -- unavailable in secure mode", args[0]);
+ }
+
+ static size_t EXEC_COUNT = 0;
+
+ if (!ec.ec_dry_run) {
+ nonstd::optional<std::string> name_flag;
+
+ shlex lexer(cmdline);
+ auto cmd_start = args[0].size();
+ auto split_res = lexer.split(ec.create_resolver());
+ if (split_res.isOk()) {
+ auto flags = split_res.unwrap();
+ if (flags.size() >= 2) {
+ static const char* NAME_FLAG = "--name=";
+
+ if (startswith(flags[1].se_value, NAME_FLAG)) {
+ name_flag = flags[1].se_value.substr(strlen(NAME_FLAG));
+ cmd_start = flags[1].se_origin.sf_end;
+ }
+ }
+ }
+
+ auto carg = trim(cmdline.substr(cmd_start));
+
+ log_info("executing: %s", carg.c_str());
+
+ auto child_fds_res
+ = auto_pipe::for_child_fds(STDOUT_FILENO, STDERR_FILENO);
+ if (child_fds_res.isErr()) {
+ auto um = lnav::console::user_message::error(
+ "unable to create child pipes")
+ .with_reason(child_fds_res.unwrapErr());
+ ec.add_error_context(um);
+ return Err(um);
+ }
+ auto child_res = lnav::pid::from_fork();
+ if (child_res.isErr()) {
+ auto um
+ = lnav::console::user_message::error("unable to fork() child")
+ .with_reason(child_res.unwrapErr());
+ ec.add_error_context(um);
+ return Err(um);
+ }
+
+ auto child_fds = child_fds_res.unwrap();
+ auto child = child_res.unwrap();
+ for (auto& child_fd : child_fds) {
+ child_fd.after_fork(child.in());
+ }
+ if (child.in_child()) {
+ auto dev_null = open("/dev/null", O_RDONLY | O_CLOEXEC);
+
+ dup2(dev_null, STDIN_FILENO);
+ const char* exec_args[] = {
+ getenv_opt("SHELL").value_or("bash"),
+ "-c",
+ carg.c_str(),
+ nullptr,
+ };
+
+ for (const auto& pair : ec.ec_local_vars.top()) {
+ pair.second.match(
+ [&pair](const std::string& val) {
+ setenv(pair.first.c_str(), val.c_str(), 1);
+ },
+ [&pair](const string_fragment& sf) {
+ setenv(pair.first.c_str(), sf.to_string().c_str(), 1);
+ },
+ [](null_value_t) {},
+ [&pair](int64_t val) {
+ setenv(
+ pair.first.c_str(), fmt::to_string(val).c_str(), 1);
+ },
+ [&pair](double val) {
+ setenv(
+ pair.first.c_str(), fmt::to_string(val).c_str(), 1);
+ });
+ }
+
+ execvp(exec_args[0], (char**) exec_args);
+ _exit(EXIT_FAILURE);
+ }
+
+ std::string display_name;
+ auto open_prov = ec.get_provenance<exec_context::file_open>();
+ if (open_prov) {
+ if (name_flag) {
+ display_name = fmt::format(
+ FMT_STRING("{}/{}"), open_prov->fo_name, name_flag.value());
+ } else {
+ display_name = open_prov->fo_name;
+ }
+ } else if (name_flag) {
+ display_name = name_flag.value();
+ } else {
+ display_name
+ = fmt::format(FMT_STRING("[{}] {}"), EXEC_COUNT++, carg);
+ }
+
+ auto name_base = display_name;
+ size_t name_counter = 0;
+
+ while (true) {
+ auto fn_iter
+ = lnav_data.ld_active_files.fc_file_names.find(display_name);
+ if (fn_iter == lnav_data.ld_active_files.fc_file_names.end()) {
+ break;
+ }
+ name_counter += 1;
+ display_name
+ = fmt::format(FMT_STRING("{} [{}]"), name_base, name_counter);
+ }
+
+ auto create_piper_res
+ = lnav::piper::create_looper(display_name,
+ std::move(child_fds[0].read_end()),
+ std::move(child_fds[1].read_end()));
+
+ if (create_piper_res.isErr()) {
+ auto um
+ = lnav::console::user_message::error("unable to create piper")
+ .with_reason(create_piper_res.unwrapErr());
+ ec.add_error_context(um);
+ return Err(um);
+ }
+
+ lnav_data.ld_active_files.fc_file_names[display_name].with_piper(
+ create_piper_res.unwrap());
+ lnav_data.ld_child_pollers.emplace_back(child_poller{
+ display_name,
+ std::move(child),
+ [](auto& fc, auto& child) {},
+ });
+ lnav_data.ld_files_to_front.emplace_back(display_name,
+ file_location_t{});
+
+ if (lnav_data.ld_rl_view != nullptr) {
+ lnav_data.ld_rl_view->set_alt_value(
+ HELP_MSG_CTRL(C, "to send SIGINT to child process"));
+ }
+ return Ok(fmt::format(FMT_STRING("info: executing -- {}"), carg));
+ }
+
+ return Ok(std::string());
+}
+
+static Result<std::string, lnav::console::user_message>
com_shexec(exec_context& ec,
std::string cmdline,
std::vector<std::string>& args)
@@ -4159,9 +5172,11 @@ com_echo(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
auto ec_out = ec.get_output();
if (ec.ec_dry_run) {
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"The text to output:");
- lnav_data.ld_preview_source.replace_with(attr_line_t(retval));
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ lnav_data.ld_preview_source[0].replace_with(attr_line_t(retval));
retval = "";
} else if (ec_out) {
FILE* outfile = *ec_out;
@@ -4243,35 +5258,23 @@ com_eval(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
if (ec.ec_dry_run) {
attr_line_t al(expanded_cmd);
- lnav_data.ld_preview_status_source.get_description().set_value(
+ lnav_data.ld_preview_status_source[0].get_description().set_value(
"The command to be executed:");
- lnav_data.ld_preview_source.replace_with(al);
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ lnav_data.ld_preview_source[0].replace_with(al);
return Ok(std::string());
}
auto src_guard = ec.enter_source(EVAL_SRC, 1, expanded_cmd);
- std::string alt_msg;
- switch (expanded_cmd[0]) {
- case ':':
- return execute_command(ec, expanded_cmd.substr(1));
- case ';':
- return execute_sql(ec, expanded_cmd.substr(1), alt_msg);
- case '|':
- return execute_file(ec, expanded_cmd.substr(1));
- case '/': {
- auto search_cmd = expanded_cmd.substr(1);
- lnav_data.ld_view_stack.top() |
- [&search_cmd](auto tc) { tc->execute_search(search_cmd); };
- break;
- }
- default:
- return ec.make_error(
- "expecting argument to start with ':', ';', '/', "
- "or '|' to signify a command, SQL query, or script to "
- "execute");
+ auto content = string_fragment::from_str(expanded_cmd);
+ multiline_executor me(ec, ":eval");
+ for (auto line : content.split_lines()) {
+ TRY(me.push_back(line));
}
+ retval = TRY(me.final());
} else {
return ec.make_error("expecting a command or query to evaluate");
}
@@ -4292,14 +5295,16 @@ com_config(exec_context& ec,
static const auto INPUT_SRC = intern_string::lookup("input");
yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
- std::vector<lnav::console::user_message> errors;
+ std::vector<lnav::console::user_message> errors, errors_ignored;
std::string option = args[1];
lnav_config = rollback_lnav_config;
ypc.set_path(option)
.with_obj(lnav_config)
.with_error_reporter([&errors](const auto& ypc, auto msg) {
- errors.push_back(msg);
+ if (msg.um_level == lnav::console::user_message::level::error) {
+ errors.push_back(msg);
+ }
});
ypc.ypc_active_paths.insert(option);
ypc.update_callbacks();
@@ -4331,10 +5336,14 @@ com_config(exec_context& ec,
if (ec.ec_dry_run) {
attr_line_t al(old_value);
- lnav_data.ld_preview_source.replace_with(al)
+ lnav_data.ld_preview_view[0].set_sub_source(
+ &lnav_data.ld_preview_source[0]);
+ lnav_data.ld_preview_source[0]
+ .replace_with(al)
.set_text_format(detect_text_format(old_value))
.truncate_to(10);
- lnav_data.ld_preview_status_source.get_description()
+ lnav_data.ld_preview_status_source[0]
+ .get_description()
.set_value("Value of option: %s", option.c_str());
char help_text[1024];
@@ -4401,8 +5410,18 @@ com_config(exec_context& ec,
return ec.make_error("unhandled type");
}
+ while (!errors.empty()) {
+ if (errors.back().um_level
+ == lnav::console::user_message::level::error)
+ {
+ break;
+ } else {
+ errors.pop_back();
+ }
+ }
+
if (!errors.empty()) {
- return Err(errors[0]);
+ return Err(errors.back());
}
if (changed) {
@@ -4412,10 +5431,20 @@ com_config(exec_context& ec,
= ec.ec_source.back().s_location;
reload_config(errors);
+ while (!errors.empty()) {
+ if (errors.back().um_level
+ == lnav::console::user_message::level::error)
+ {
+ break;
+ } else {
+ errors.pop_back();
+ }
+ }
+
if (!errors.empty()) {
lnav_config = rollback_lnav_config;
- reload_config(errors);
- return Err(errors[0]);
+ reload_config(errors_ignored);
+ return Err(errors.back());
}
if (!ec.ec_dry_run) {
retval = "info: changed config option -- " + option;
@@ -4531,6 +5560,7 @@ com_spectrogram(exec_context& ec,
}
if (found) {
+ lnav_data.ld_views[LNV_SPECTRO].reload_data();
ss.text_selection_changed(lnav_data.ld_views[LNV_SPECTRO]);
ensure_view(&lnav_data.ld_views[LNV_SPECTRO]);
@@ -4559,6 +5589,12 @@ com_quit(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
}
static void
+breadcrumb_prompt(std::vector<std::string>& args)
+{
+ set_view_mode(ln_mode_t::BREADCRUMBS);
+}
+
+static void
command_prompt(std::vector<std::string>& args)
{
auto* tc = *lnav_data.ld_view_stack.top();
@@ -4658,6 +5694,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");
add_view_text_possibilities(lnav_data.ld_rl_view,
ln_mode_t::COMMAND,
"filter",
@@ -4672,6 +5711,7 @@ command_prompt(std::vector<std::string>& args)
add_tag_possibilities();
add_file_possibilities();
add_recent_netlocs_possibilities();
+ add_tz_possibilities(ln_mode_t::COMMAND);
auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
if (ta != nullptr) {
@@ -4687,6 +5727,8 @@ command_prompt(std::vector<std::string>& args)
lnav_data.ld_rl_view->focus(ln_mode_t::COMMAND,
cget(args, 2).value_or(":"),
cget(args, 3).value_or(""));
+
+ rl_set_help();
}
static void
@@ -4727,6 +5769,7 @@ search_prompt(std::vector<std::string>& args)
cget(args, 2).value_or("/"),
cget(args, 3).value_or(""));
lnav_data.ld_doc_status_source.set_title("Syntax Help");
+ lnav_data.ld_doc_status_source.set_description("");
rl_set_help();
lnav_data.ld_bottom_source.set_prompt(
"Search for: "
@@ -4760,7 +5803,7 @@ search_files_prompt(std::vector<std::string>& args)
lnav_data.ld_mode = ln_mode_t::SEARCH_FILES;
for (const auto& lf : lnav_data.ld_active_files.fc_files) {
- auto path = lnav::pcre2pp::quote(lf->get_unique_path());
+ auto path = lnav::pcre2pp::quote(lf->get_unique_path().string());
lnav_data.ld_rl_view->add_possibility(
ln_mode_t::SEARCH_FILES, "*", path);
}
@@ -4794,8 +5837,8 @@ search_spectro_details_prompt(std::vector<std::string>& args)
static void
sql_prompt(std::vector<std::string>& args)
{
- textview_curses* tc = *lnav_data.ld_view_stack.top();
- textview_curses& log_view = lnav_data.ld_views[LNV_LOG];
+ auto* tc = *lnav_data.ld_view_stack.top();
+ auto& log_view = lnav_data.ld_views[LNV_LOG];
lnav_data.ld_exec_context.ec_top_line = tc->get_selection();
@@ -4806,6 +5849,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");
rl_set_help();
lnav_data.ld_bottom_source.update_loading(0, 0);
lnav_data.ld_status[LNS_BOTTOM].do_update();
@@ -4816,6 +5862,8 @@ sql_prompt(std::vector<std::string>& args)
tc->reload_data();
lnav_data.ld_bottom_source.set_prompt(
"Enter an SQL query: (Press " ANSI_BOLD("CTRL+]") " to abort)");
+
+ add_sqlite_possibilities();
}
static void
@@ -4841,6 +5889,7 @@ com_prompt(exec_context& ec,
{
static std::map<std::string, std::function<void(std::vector<std::string>&)>>
PROMPT_TYPES = {
+ {"breadcrumb", breadcrumb_prompt},
{"command", command_prompt},
{"script", script_prompt},
{"search", search_prompt},
@@ -4853,25 +5902,40 @@ com_prompt(exec_context& ec,
if (args.empty()) {
} else if (!ec.ec_dry_run) {
- args.clear();
+ static const intern_string_t SRC = intern_string::lookup("flags");
auto lexer = shlex(cmdline);
- lexer.split(args, ec.create_resolver());
+ auto split_args_res = lexer.split(ec.create_resolver());
+ if (split_args_res.isErr()) {
+ auto split_err = split_args_res.unwrapErr();
+ auto um
+ = lnav::console::user_message::error("unable to parse prompt")
+ .with_reason(split_err.te_msg)
+ .with_snippet(lnav::console::snippet::from(
+ SRC, lexer.to_attr_line(split_err)));
+
+ return Err(um);
+ }
- auto alt_flag = std::find(args.begin(), args.end(), "--alt");
+ auto split_args = split_args_res.unwrap()
+ | lnav::itertools::map(
+ [](const auto& elem) { return elem.se_value; });
+
+ auto alt_flag
+ = std::find(split_args.begin(), split_args.end(), "--alt");
auto is_alt = false;
- if (alt_flag != args.end()) {
- args.erase(alt_flag);
+ if (alt_flag != split_args.end()) {
+ split_args.erase(alt_flag);
is_alt = true;
}
- auto prompter = PROMPT_TYPES.find(args[1]);
+ auto prompter = PROMPT_TYPES.find(split_args[1]);
if (prompter == PROMPT_TYPES.end()) {
- return ec.make_error("Unknown prompt type: {}", args[1]);
+ return ec.make_error("Unknown prompt type: {}", split_args[1]);
}
- prompter->second(args);
+ prompter->second(split_args);
lnav_data.ld_rl_view->set_alt_focus(is_alt);
}
return Ok(std::string());
@@ -4883,16 +5947,22 @@ 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(
- help_text("prompt", "The prompt to display").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")
+ .with_enum_values({
+ "breadcrumb",
+ "command",
+ "script",
+ "search",
+ "sql",
+ })
+ .optional())
.with_parameter(
help_text("initial-value",
"The initial value to fill in for the prompt")
@@ -4928,6 +5998,44 @@ readline_context::command_t STD_COMMANDS[] = {
.with_parameter(help_text("seconds", "The epoch timestamp to convert")
.with_format(help_parameter_format_t::HPF_INTEGER))
.with_example({"To convert the epoch time 1490191111", "1490191111"})},
+ {
+ "convert-time-to",
+ com_convert_time_to,
+ help_text(":convert-time-to")
+ .with_summary("Convert the focused timestamp to the "
+ "given timezone")
+ .with_parameter(help_text("zone", "The timezone name")),
+ },
+ {
+ "set-file-timezone",
+ 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 "
+ "the focused file or the given glob pattern.")
+ .with_parameter({"zone", "The timezone name"})
+ .with_parameter(help_text{"pattern",
+ "The glob pattern to match against "
+ "files that should use this timezone"}
+ .optional())
+ .with_tags({"file-options"}),
+ com_set_file_timezone_prompt,
+ },
+ {
+ "clear-file-timezone",
+ com_clear_file_timezone,
+ help_text(":clear-file-timezone")
+ .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_tags({"file-options"}),
+ com_clear_file_timezone_prompt,
+ },
{"current-time",
com_current_time,
@@ -4946,7 +6054,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"})},
@@ -4961,12 +6070,23 @@ readline_context::command_t STD_COMMANDS[] = {
{"To move 10 percent back in the view", "-10%"},
})
.with_tags({"navigation"})},
+
+ {
+ "annotate",
+ com_annotate,
+
+ help_text(":annotate")
+ .with_summary("Analyze the focused log message and "
+ "attach annotations")
+ .with_tags({"metadata"}),
+ },
+
{"mark",
com_mark,
help_text(":mark")
- .with_summary(
- "Toggle the bookmark state for the top line in the current view")
+ .with_summary("Toggle the bookmark state for the top line in the "
+ "current view")
.with_tags({"bookmarks"})},
{
"mark-expr",
@@ -4974,16 +6094,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,
},
@@ -4998,8 +6120,8 @@ readline_context::command_t STD_COMMANDS[] = {
com_goto_mark,
help_text(":next-mark")
- .with_summary(
- "Move to the next bookmark of the given type in the current view")
+ .with_summary("Move to the next bookmark of the given type in the "
+ "current view")
.with_parameter(help_text("type",
"The type of bookmark -- error, warning, "
"search, user, file, meta")
@@ -5010,7 +6132,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, "
@@ -5028,8 +6151,27 @@ 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"})},
+
+ {
+ "next-section",
+ com_next_section,
+
+ help_text(":next-section")
+ .with_summary("Move to the next section in the document")
+ .with_tags({"navigation"}),
+ },
+ {
+ "prev-section",
+ com_prev_section,
+
+ help_text(":prev-section")
+ .with_summary("Move to the previous section in the document")
+ .with_tags({"navigation"}),
+ },
+
{"help",
com_help,
@@ -5038,21 +6180,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,
@@ -5092,8 +6236,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",
@@ -5113,7 +6257,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"))
@@ -5125,37 +6270,45 @@ 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(
{"To clear the highlight with the pattern 'foobar'", "foobar"})},
- {"filter-in",
- com_filter,
+ {
+ "filter-in",
+ com_filter,
- help_text(":filter-in")
- .with_summary("Only show 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 do not have the "
- "string 'dhclient'",
- "dhclient"})},
- {"filter-out",
- com_filter,
-
- help_text(":filter-out")
- .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 "
- "'last message repeated'",
- "last message repeated"})},
+ help_text(":filter-in")
+ .with_summary("Only show 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 do not have the "
+ "string 'dhclient'",
+ "dhclient"}),
+ com_filter_prompt,
+ },
+ {
+ "filter-out",
+ com_filter,
+
+ help_text(":filter-out")
+ .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 "
+ "'last message repeated'",
+ "last message repeated"}),
+ com_filter_prompt,
+ },
{"delete-filter",
com_delete_filter,
@@ -5166,29 +6319,34 @@ readline_context::command_t STD_COMMANDS[] = {
help_text("pattern", "The regular expression to match"))
.with_opposites({"filter-in", "filter-out"})
.with_tags({"filtering"})
- .with_example(
- {"To delete the filter with the pattern 'last message repeated'",
- "last message repeated"})},
+ .with_example({"To delete the filter with the pattern 'last "
+ "message repeated'",
+ "last message repeated"})},
{
"filter-expr",
com_filter_expr,
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,
},
@@ -5203,26 +6361,27 @@ 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 /tmp/interesting-lines.txt",
- "/tmp/interesting-lines.txt"})},
+ .with_example({"To append marked lines to the file "
+ "/tmp/interesting-lines.txt",
+ "/tmp/interesting-lines.txt"})},
{"write-to",
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())
.with_parameter(help_text("path", "The path to the file to write"))
.with_tags({"io", "scripting"})
- .with_example(
- {"To write marked lines to the file /tmp/interesting-lines.txt",
- "/tmp/interesting-lines.txt"})},
+ .with_example({"To write marked lines to the file "
+ "/tmp/interesting-lines.txt",
+ "/tmp/interesting-lines.txt"})},
{"write-csv-to",
com_save_to,
@@ -5249,20 +6408,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())
@@ -5274,10 +6434,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())
@@ -5285,9 +6445,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,
@@ -5326,7 +6486,10 @@ readline_context::command_t STD_COMMANDS[] = {
com_pipe_to,
help_text(":pipe-line-to")
- .with_summary("Pipe the top line to the given shell command")
+ .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"})
@@ -5338,12 +6501,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",
@@ -5357,7 +6519,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",
@@ -5369,9 +6532,9 @@ readline_context::command_t STD_COMMANDS[] = {
"pattern", "The regular expression used in the filter command"))
.with_tags({"filtering"})
.with_opposites({"filter-out", "filter-in"})
- .with_example(
- {"To disable the filter with the pattern 'last message repeated'",
- "last message repeated"})},
+ .with_example({"To disable the filter with the pattern 'last "
+ "message repeated'",
+ "last message repeated"})},
{"enable-word-wrap",
com_enable_word_wrap,
@@ -5389,13 +6552,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,
@@ -5405,9 +6569,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,
@@ -5416,16 +6580,18 @@ readline_context::command_t STD_COMMANDS[] = {
.with_parameter(
help_text("table-name", "The name of the table to create"))
.with_parameter(
- help_text(
- "pattern",
- "The regular expression used to capture the table columns. "
- "If not given, the current search pattern is used.")
+ help_text("pattern",
+ "The regular expression used to capture the table "
+ "columns. "
+ "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,
@@ -5441,10 +6607,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"})
@@ -5457,8 +6623,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",
@@ -5466,9 +6633,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",
@@ -5481,18 +6648,24 @@ readline_context::command_t STD_COMMANDS[] = {
com_close,
help_text(":close")
- .with_summary("Close the top file in the view")
+ .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"}
+ .zero_or_more())
.with_opposites({"open"})},
{
"comment",
com_comment,
help_text(":comment")
- .with_summary(
- "Attach a comment to the top log line. The comment will be "
- "displayed right below the log message it is associated with. "
- "The comment can be formatted using markdown and you can add "
- "new-lines with '\\n'.")
+ .with_summary("Attach a comment to the top log line. The "
+ "comment will be "
+ "displayed right below the log message it is "
+ "associated with. "
+ "The comment can be formatted using markdown and "
+ "you can add "
+ "new-lines with '\\n'.")
.with_parameter(help_text("text", "The comment text"))
.with_example({"To add the comment 'This is where it all went "
"wrong' to the top line",
@@ -5527,7 +6700,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"})
@@ -5538,7 +6712,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"})
@@ -5563,23 +6738,24 @@ 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."))
- .with_example(
- {"To get a summary of the sc_bytes column in the access_log table",
- "sc_bytes"})},
+ .with_example({"To get a summary of the sc_bytes column in the "
+ "access_log table",
+ "sc_bytes"})},
{"switch-to-view",
com_switch_to_view,
@@ -5592,12 +6768,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",
@@ -5658,15 +6835,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!"})},
@@ -5691,6 +6870,31 @@ readline_context::command_t STD_COMMANDS[] = {
.with_tags({"scripting"})
.with_examples({{"To substitute the table name from a variable",
";SELECT * FROM ${table}"}})},
+
+ {
+ "sh",
+ com_sh,
+
+ help_text(":sh")
+ .with_summary("Execute the given command-line and display the "
+ "captured output")
+ .with_parameter(help_text(
+ "--name=<name>", "The name to give to the captured output"))
+ .with_parameter(
+ help_text("cmdline", "The command-line to execute."))
+ .with_tags({"scripting"}),
+ },
+
+ {
+ "cd",
+ com_cd,
+
+ help_text(":cd")
+ .with_summary("Change the current directory")
+ .with_parameter(help_text("dir", "The new current directory"))
+ .with_tags({"scripting"}),
+ },
+
{"config",
com_config,
@@ -5701,9 +6905,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"})},
@@ -5725,9 +6929,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,