summaryrefslogtreecommitdiffstats
path: root/src/readline_callbacks.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/readline_callbacks.cc901
1 files changed, 901 insertions, 0 deletions
diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc
new file mode 100644
index 0000000..fa070b9
--- /dev/null
+++ b/src/readline_callbacks.cc
@@ -0,0 +1,901 @@
+/**
+ * Copyright (c) 2015, Timothy Stack
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of Timothy Stack nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "base/humanize.network.hh"
+#include "base/injector.hh"
+#include "command_executor.hh"
+#include "config.h"
+#include "field_overlay_source.hh"
+#include "help_text_formatter.hh"
+#include "lnav.hh"
+#include "lnav_config.hh"
+#include "lnav_util.hh"
+#include "log_format_loader.hh"
+#include "plain_text_source.hh"
+#include "readline_curses.hh"
+#include "readline_highlighters.hh"
+#include "service_tags.hh"
+#include "sql_help.hh"
+#include "sqlite-extension-func.hh"
+#include "tailer/tailer.looper.hh"
+#include "view_helpers.examples.hh"
+#include "vtab_module.hh"
+#include "yajlpp/yajlpp.hh"
+
+using namespace std::chrono_literals;
+
+#define ABORT_MSG "(Press " ANSI_BOLD("CTRL+]") " to abort)"
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+#define ANSI_RE(msg) ANSI_CSI "1;3" STR(COLOR_CYAN) "m" msg ANSI_NORM
+#define ANSI_CLS(msg) ANSI_CSI "1;3" STR(COLOR_MAGENTA) "m" msg ANSI_NORM
+#define ANSI_KW(msg) ANSI_CSI "3" STR(COLOR_BLUE) "m" msg ANSI_NORM
+#define ANSI_REV(msg) ANSI_CSI "7m" msg ANSI_NORM
+#define ANSI_STR(msg) ANSI_CSI "32m" msg ANSI_NORM
+
+const char *RE_HELP =
+ " " ANSI_RE(".") " Any character "
+ " " "a" ANSI_RE("|") "b a or b "
+ " " ANSI_RE("(?-i)") " Case-sensitive search\n"
+
+ " " ANSI_CLS("\\w") " Word character "
+ " " "a" ANSI_RE("?") " 0 or 1 a's "
+ " " ANSI_RE("$") " End of string\n"
+
+ " " ANSI_CLS("\\d") " Digit "
+ " " "a" ANSI_RE("*") " 0 or more a's "
+ " " ANSI_RE("(") "..." ANSI_RE(")") " Capture\n"
+
+ " " ANSI_CLS("\\s") " White space "
+ " " "a" ANSI_RE("+") " 1 or more a's "
+ " " ANSI_RE("^") " Start of string\n"
+
+ " " ANSI_RE("\\") " Escape character "
+ " " ANSI_RE("[^") "ab" ANSI_RE("]") " " ANSI_BOLD("Not") " a or b "
+ " " ANSI_RE("[") "ab" ANSI_RE("-") "d" ANSI_RE("]") " Any of a, b, c, or d"
+;
+
+const char *RE_EXAMPLE =
+ ANSI_UNDERLINE("Examples") "\n"
+ " abc" ANSI_RE("*") " matches "
+ ANSI_STR("'ab'") ", " ANSI_STR("'abc'") ", " ANSI_STR("'abccc'") "\n"
+
+ " key=" ANSI_RE("(\\w+)")
+ " matches key=" ANSI_REV("123") ", key=" ANSI_REV("abc") " and captures 123 and abc\n"
+
+ " " ANSI_RE("\\") "[abc" ANSI_RE("\\") "] matches " ANSI_STR("'[abc]'") "\n"
+
+ " " ANSI_RE("(?-i)") "ABC matches " ANSI_STR("'ABC'") " and " ANSI_UNDERLINE("not") " " ANSI_STR("'abc'")
+;
+
+const char *SQL_HELP =
+ " " ANSI_KW("SELECT") " Select rows from a table "
+ " " ANSI_KW("DELETE") " Delete rows from a table\n"
+ " " ANSI_KW("INSERT") " Insert rows into a table "
+ " " ANSI_KW("UPDATE") " Update rows in a table\n"
+ " " ANSI_KW("CREATE") " Create a table/index "
+ " " ANSI_KW("DROP") " Drop a table/index\n"
+ " " ANSI_KW("ATTACH") " Attach a SQLite database file "
+ " " ANSI_KW("DETACH") " Detach a SQLite database"
+;
+
+const char *SQL_EXAMPLE =
+ ANSI_UNDERLINE("Examples") "\n"
+ " SELECT * FROM %s WHERE log_level >= 'warning' LIMIT 10\n"
+ " UPDATE %s SET log_mark = 1 WHERE log_line = log_top_line()\n"
+ " SELECT * FROM logline LIMIT 10"
+;
+
+static const char* LNAV_CMD_PROMPT = "Enter an lnav command: " ABORT_MSG;
+
+void
+rl_set_help()
+{
+ switch (lnav_data.ld_mode) {
+ case ln_mode_t::SEARCH: {
+ lnav_data.ld_doc_source.replace_with(RE_HELP);
+ lnav_data.ld_example_source.replace_with(RE_EXAMPLE);
+ break;
+ }
+ case ln_mode_t::SQL: {
+ textview_curses& log_view = lnav_data.ld_views[LNV_LOG];
+ auto* lss = (logfile_sub_source*) log_view.get_sub_source();
+ attr_line_t example_al;
+
+ if (log_view.get_inner_height() > 0) {
+ auto cl = lss->at(log_view.get_top());
+ auto lf = lss->find(cl);
+ const auto* format_name = lf->get_format()->get_name().get();
+
+ example_al.with_ansi_string(
+ SQL_EXAMPLE, format_name, format_name);
+ readline_sqlite_highlighter(example_al, 0);
+ }
+
+ lnav_data.ld_doc_source.replace_with(SQL_HELP);
+ lnav_data.ld_example_source.replace_with(example_al);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static bool
+rl_sql_help(readline_curses* rc)
+{
+ attr_line_t al(rc->get_line_buffer());
+ const string_attrs_t& sa = al.get_attrs();
+ size_t x = rc->get_x();
+ bool has_doc = false;
+
+ if (x > 0) {
+ x -= 1;
+ }
+
+ annotate_sql_statement(al);
+
+ auto avail_help = find_sql_help_for_line(al, x);
+
+ if (!avail_help.empty()) {
+ size_t help_count = avail_help.size();
+ textview_curses& dtc = lnav_data.ld_doc_view;
+ textview_curses& etc = lnav_data.ld_example_view;
+ unsigned long doc_width, ex_width;
+ vis_line_t doc_height, ex_height;
+ attr_line_t doc_al, ex_al;
+
+ dtc.get_dimensions(doc_height, doc_width);
+ etc.get_dimensions(ex_height, ex_width);
+
+ for (const auto& ht : avail_help) {
+ format_help_text_for_term(*ht,
+ std::min(70UL, doc_width),
+ doc_al,
+ help_count > 1
+ ? help_text_content::synopsis
+ : help_text_content::full);
+ if (help_count == 1) {
+ format_example_text_for_term(
+ *ht, eval_example, std::min(70UL, ex_width), ex_al);
+ } else {
+ doc_al.append("\n");
+ }
+ }
+
+ if (!doc_al.empty()) {
+ lnav_data.ld_doc_source.replace_with(doc_al);
+ dtc.reload_data();
+
+ lnav_data.ld_example_source.replace_with(ex_al);
+ etc.reload_data();
+
+ has_doc = true;
+ }
+ }
+
+ auto ident_iter = find_string_attr_containing(
+ sa, &SQL_IDENTIFIER_ATTR, al.nearest_text(x));
+ if (ident_iter != sa.end()) {
+ auto ident = al.get_substring(ident_iter->sa_range);
+ auto intern_ident = intern_string::lookup(ident);
+ auto vtab = lnav_data.ld_vtab_manager->lookup_impl(intern_ident);
+ auto vtab_module_iter = vtab_module_ddls.find(intern_ident);
+ std::string ddl;
+
+ if (vtab != nullptr) {
+ ddl = trim(vtab->get_table_statement());
+ } else if (vtab_module_iter != vtab_module_ddls.end()) {
+ ddl = vtab_module_iter->second;
+ } else {
+ auto table_ddl_iter = lnav_data.ld_table_ddl.find(ident);
+
+ if (table_ddl_iter != lnav_data.ld_table_ddl.end()) {
+ ddl = table_ddl_iter->second;
+ }
+ }
+
+ if (!ddl.empty()) {
+ lnav_data.ld_preview_source.replace_with(ddl)
+ .set_text_format(text_format_t::TF_SQL)
+ .truncate_to(30);
+ lnav_data.ld_preview_status_source.get_description().set_value(
+ "Definition for table -- %s", ident.c_str());
+ }
+ }
+
+ return has_doc;
+}
+
+void
+rl_change(readline_curses* rc)
+{
+ static const std::set<std::string> COMMANDS_WITH_SQL = {
+ "filter-expr",
+ "mark-expr",
+ };
+
+ static const std::set<std::string> COMMANDS_FOR_FIELDS = {
+ "hide-fields",
+ "show-fields",
+ };
+
+ textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
+
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
+ lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
+ lnav_data.ld_user_message_source.clear();
+ lnav_data.ld_preview_source.clear();
+ lnav_data.ld_preview_status_source.get_description()
+ .set_cylon(false)
+ .clear();
+
+ switch (lnav_data.ld_mode) {
+ case ln_mode_t::COMMAND: {
+ static std::string last_command;
+ static int generation = 0;
+
+ const auto line = rc->get_line_buffer();
+ std::vector<std::string> args;
+ auto iter = lnav_commands.end();
+
+ split_ws(line, args);
+
+ if (args.empty()) {
+ generation = 0;
+ } else if (args[0] != last_command) {
+ last_command = args[0];
+ generation = 0;
+ } else {
+ generation += 1;
+ }
+
+ auto* os = tc->get_overlay_source();
+ if (!args.empty() && os != nullptr) {
+ auto* fos = dynamic_cast<field_overlay_source*>(os);
+
+ if (fos != nullptr) {
+ if (generation == 0) {
+ auto& top_ctx = fos->fos_contexts.top();
+
+ if (COMMANDS_WITH_SQL.count(args[0]) > 0) {
+ top_ctx.c_prefix = ":";
+ top_ctx.c_show = true;
+ top_ctx.c_show_discovered = false;
+ } else if (COMMANDS_FOR_FIELDS.count(args[0]) > 0) {
+ top_ctx.c_prefix = "";
+ top_ctx.c_show = true;
+ top_ctx.c_show_discovered = false;
+ } else {
+ top_ctx.c_prefix = "";
+ top_ctx.c_show = false;
+ }
+ }
+ }
+ }
+
+ if (!args.empty()) {
+ iter = lnav_commands.find(args[0]);
+ }
+ if (iter == lnav_commands.end()) {
+ lnav_data.ld_doc_source.clear();
+ lnav_data.ld_example_source.clear();
+ lnav_data.ld_preview_source.clear();
+ lnav_data.ld_preview_status_source.get_description()
+ .set_cylon(false)
+ .clear();
+ lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
+ lnav_data.ld_bottom_source.grep_error("");
+ } else if (args[0] == "config" && args.size() > 1) {
+ static const auto INPUT_SRC = intern_string::lookup("input");
+ yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
+
+ ypc.set_path(args[1]).with_obj(lnav_config);
+ ypc.update_callbacks();
+
+ if (ypc.ypc_current_handler != nullptr) {
+ const json_path_handler_base* jph = ypc.ypc_current_handler;
+ char help_text[1024];
+
+ snprintf(help_text,
+ sizeof(help_text),
+ ANSI_BOLD("%s %s") " -- %s " ABORT_MSG,
+ jph->jph_property.c_str(),
+ jph->jph_synopsis,
+ jph->jph_description);
+ lnav_data.ld_bottom_source.set_prompt(help_text);
+ lnav_data.ld_bottom_source.grep_error("");
+ } else {
+ lnav_data.ld_bottom_source.grep_error(
+ "Unknown configuration option: " + args[1]);
+ }
+ } else if ((args[0] != "filter-expr" && args[0] != "mark-expr")
+ || !rl_sql_help(rc))
+ {
+ readline_context::command_t& cmd = *iter->second;
+ const help_text& ht = cmd.c_help;
+
+ if (ht.ht_name) {
+ textview_curses& dtc = lnav_data.ld_doc_view;
+ textview_curses& etc = lnav_data.ld_example_view;
+ unsigned long width;
+ vis_line_t height;
+ attr_line_t al;
+
+ dtc.get_dimensions(height, width);
+ format_help_text_for_term(ht, std::min(70UL, width), al);
+ lnav_data.ld_doc_source.replace_with(al);
+ dtc.set_needs_update();
+
+ al.clear();
+ etc.get_dimensions(height, width);
+ format_example_text_for_term(ht, eval_example, width, al);
+ lnav_data.ld_example_source.replace_with(al);
+ etc.set_needs_update();
+ }
+
+ if (cmd.c_prompt != nullptr && generation == 0
+ && trim(line) == args[0])
+ {
+ const auto new_prompt
+ = cmd.c_prompt(lnav_data.ld_exec_context, line);
+
+ if (!new_prompt.empty()) {
+ rc->rewrite_line(line.length(), new_prompt);
+ }
+ }
+
+ lnav_data.ld_bottom_source.grep_error("");
+ lnav_data.ld_status[LNS_BOTTOM].window_change();
+ }
+ break;
+ }
+ case ln_mode_t::EXEC: {
+ const auto line = rc->get_line_buffer();
+ size_t name_end = line.find(' ');
+ const auto script_name = line.substr(0, name_end);
+ auto& scripts = injector::get<available_scripts&>();
+ auto iter = scripts.as_scripts.find(script_name);
+
+ if (iter == scripts.as_scripts.end()
+ || iter->second[0].sm_description.empty())
+ {
+ lnav_data.ld_bottom_source.set_prompt(
+ "Enter a script to execute: " ABORT_MSG);
+ } else {
+ auto& meta = iter->second[0];
+ char help_text[1024];
+
+ snprintf(help_text,
+ sizeof(help_text),
+ ANSI_BOLD("%s") " -- %s " ABORT_MSG,
+ meta.sm_synopsis.c_str(),
+ meta.sm_description.c_str());
+ lnav_data.ld_bottom_source.set_prompt(help_text);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
+{
+ textview_curses* tc = get_textview_for_mode(mode);
+ std::string term_val;
+ std::string name;
+
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
+ lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
+ tc->reload_data();
+ lnav_data.ld_user_message_source.clear();
+
+ switch (mode) {
+ case ln_mode_t::SEARCH:
+ case ln_mode_t::SEARCH_FILTERS:
+ case ln_mode_t::SEARCH_FILES:
+ case ln_mode_t::SEARCH_SPECTRO_DETAILS:
+ name = "$search";
+ break;
+
+ case ln_mode_t::CAPTURE:
+ require(0);
+ name = "$capture";
+ break;
+
+ case ln_mode_t::COMMAND: {
+ lnav_data.ld_exec_context.ec_dry_run = true;
+
+ lnav_data.ld_preview_generation += 1;
+ lnav_data.ld_preview_status_source.get_description()
+ .set_cylon(false)
+ .clear();
+ lnav_data.ld_preview_source.clear();
+ auto result = execute_command(lnav_data.ld_exec_context,
+ rc->get_value().get_string());
+
+ if (result.isOk()) {
+ auto msg = result.unwrap();
+
+ if (msg.empty()) {
+ lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
+ lnav_data.ld_bottom_source.grep_error("");
+ } else {
+ lnav_data.ld_bottom_source.set_prompt(msg);
+ lnav_data.ld_bottom_source.grep_error("");
+ }
+ } else {
+ lnav_data.ld_bottom_source.set_prompt("");
+ lnav_data.ld_bottom_source.grep_error(
+ result.unwrapErr().um_message.get_string());
+ }
+
+ lnav_data.ld_preview_view.reload_data();
+
+ lnav_data.ld_exec_context.ec_dry_run = false;
+ return;
+ }
+
+ case ln_mode_t::SQL: {
+ term_val = trim(rc->get_value().get_string() + ";");
+
+ if (!term_val.empty() && term_val[0] == '.') {
+ lnav_data.ld_bottom_source.grep_error("");
+ } else if (!sqlite3_complete(term_val.c_str())) {
+ lnav_data.ld_bottom_source.grep_error(
+ "sql error: incomplete statement");
+ } else {
+ auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
+ int retcode;
+
+ retcode
+ = sqlite3_prepare_v2(lnav_data.ld_db,
+ rc->get_value().get_string().c_str(),
+ -1,
+ stmt.out(),
+ nullptr);
+ if (retcode != SQLITE_OK) {
+ const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
+
+ lnav_data.ld_bottom_source.grep_error(
+ fmt::format(FMT_STRING("sql error: {}"), errmsg));
+ } else {
+ lnav_data.ld_bottom_source.grep_error("");
+ }
+ }
+
+ if (!rl_sql_help(rc)) {
+ rl_set_help();
+ lnav_data.ld_preview_source.clear();
+ }
+ return;
+ }
+
+ case ln_mode_t::BREADCRUMBS:
+ case ln_mode_t::PAGING:
+ case ln_mode_t::FILTER:
+ case ln_mode_t::FILES:
+ case ln_mode_t::EXEC:
+ case ln_mode_t::USER:
+ case ln_mode_t::SPECTRO_DETAILS:
+ case ln_mode_t::BUSY:
+ return;
+ }
+
+ if (!complete) {
+ tc->set_top(lnav_data.ld_search_start_line);
+ }
+ tc->execute_search(rc->get_value().get_string());
+}
+
+void
+rl_search(readline_curses* rc)
+{
+ auto* tc = get_textview_for_mode(lnav_data.ld_mode);
+
+ rl_search_internal(rc, lnav_data.ld_mode);
+ tc->set_follow_search_for(0, {});
+}
+
+void
+lnav_rl_abort(readline_curses* rc)
+{
+ textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
+
+ lnav_data.ld_bottom_source.set_prompt("");
+ lnav_data.ld_example_source.clear();
+ lnav_data.ld_doc_source.clear();
+ lnav_data.ld_preview_status_source.get_description()
+ .set_cylon(false)
+ .clear();
+ lnav_data.ld_preview_source.clear();
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
+ lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
+
+ std::vector<lnav::console::user_message> errors;
+ lnav_config = rollback_lnav_config;
+ reload_config(errors);
+
+ lnav_data.ld_bottom_source.grep_error("");
+ switch (lnav_data.ld_mode) {
+ case ln_mode_t::SEARCH:
+ tc->set_top(lnav_data.ld_search_start_line);
+ tc->revert_search();
+ break;
+ case ln_mode_t::SQL:
+ tc->reload_data();
+ break;
+ default:
+ break;
+ }
+ lnav_data.ld_rl_view->set_value("");
+ lnav_data.ld_mode = ln_mode_t::PAGING;
+}
+
+static void
+rl_callback_int(readline_curses* rc, bool is_alt)
+{
+ textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
+ exec_context& ec = lnav_data.ld_exec_context;
+ std::string alt_msg;
+
+ lnav_data.ld_bottom_source.set_prompt("");
+ lnav_data.ld_doc_source.clear();
+ lnav_data.ld_example_source.clear();
+ lnav_data.ld_preview_status_source.get_description()
+ .set_cylon(false)
+ .clear();
+ lnav_data.ld_preview_source.clear();
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
+ tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
+ lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
+
+ auto new_mode = ln_mode_t::PAGING;
+
+ switch (lnav_data.ld_mode) {
+ case ln_mode_t::SEARCH_FILTERS:
+ new_mode = ln_mode_t::FILTER;
+ break;
+ case ln_mode_t::SEARCH_FILES:
+ new_mode = ln_mode_t::FILES;
+ break;
+ case ln_mode_t::SEARCH_SPECTRO_DETAILS:
+ new_mode = ln_mode_t::SPECTRO_DETAILS;
+ break;
+ default:
+ break;
+ }
+
+ auto old_mode = std::exchange(lnav_data.ld_mode, new_mode);
+ switch (old_mode) {
+ case ln_mode_t::BREADCRUMBS:
+ case ln_mode_t::PAGING:
+ case ln_mode_t::FILTER:
+ case ln_mode_t::FILES:
+ case ln_mode_t::SPECTRO_DETAILS:
+ case ln_mode_t::BUSY:
+ require(0);
+ break;
+
+ case ln_mode_t::COMMAND: {
+ rc->set_alt_value("");
+ ec.ec_source.back().s_content
+ = fmt::format(FMT_STRING(":{}"), rc->get_value().get_string());
+ readline_lnav_highlighter(ec.ec_source.back().s_content, -1);
+ ec.ec_source.back().s_content.with_attr_for_all(
+ VC_ROLE.value(role_t::VCR_QUOTED_CODE));
+ auto exec_res = execute_command(ec, rc->get_value().get_string());
+ if (exec_res.isOk()) {
+ rc->set_value(exec_res.unwrap());
+ } else {
+ auto um = exec_res.unwrapErr();
+
+ lnav_data.ld_user_message_source.replace_with(
+ um.to_attr_line().rtrim());
+ lnav_data.ld_user_message_view.reload_data();
+ lnav_data.ld_user_message_expiration
+ = std::chrono::steady_clock::now() + 20s;
+ rc->set_value("");
+ }
+ ec.ec_source.back().s_content.clear();
+ break;
+ }
+
+ case ln_mode_t::USER:
+ rc->set_alt_value("");
+ ec.ec_local_vars.top()["value"] = rc->get_value().get_string();
+ rc->set_value("");
+ break;
+
+ case ln_mode_t::SEARCH:
+ case ln_mode_t::SEARCH_FILTERS:
+ case ln_mode_t::SEARCH_FILES:
+ case ln_mode_t::SEARCH_SPECTRO_DETAILS:
+ case ln_mode_t::CAPTURE:
+ rl_search_internal(rc, old_mode, true);
+ if (!rc->get_value().empty()) {
+ auto& bm = tc->get_bookmarks();
+ const auto& bv = bm[&textview_curses::BM_SEARCH];
+ auto vl = is_alt ? bv.prev(tc->get_top())
+ : bv.next(tc->get_top());
+
+ if (vl) {
+ tc->set_top(vl.value());
+ } else {
+ tc->set_follow_search_for(2000, [tc, is_alt, &bm]() {
+ if (bm[&textview_curses::BM_SEARCH].empty()) {
+ return false;
+ }
+
+ if (is_alt && tc->is_searching()) {
+ return false;
+ }
+
+ nonstd::optional<vis_line_t> first_hit;
+
+ if (is_alt) {
+ first_hit = bm[&textview_curses::BM_SEARCH].prev(
+ vis_line_t(tc->get_top()));
+ } else {
+ first_hit = bm[&textview_curses::BM_SEARCH].next(
+ vis_line_t(tc->get_top() - 1));
+ }
+ if (first_hit) {
+ auto first_hit_vl = first_hit.value();
+ if (first_hit_vl > 0_vl) {
+ --first_hit_vl;
+ }
+ tc->set_top(first_hit_vl);
+ }
+
+ return true;
+ });
+ }
+ rc->set_attr_value(
+ attr_line_t("search: ").append(rc->get_value()));
+ rc->set_alt_value(HELP_MSG_2(
+ n, N, "to move forward/backward through search results"));
+ }
+ break;
+
+ case ln_mode_t::SQL: {
+ ec.ec_source.back().s_content
+ = fmt::format(FMT_STRING(";{}"), rc->get_value().get_string());
+ readline_lnav_highlighter(ec.ec_source.back().s_content, -1);
+ ec.ec_source.back().s_content.with_attr_for_all(
+ VC_ROLE.value(role_t::VCR_QUOTED_CODE));
+ auto result
+ = execute_sql(ec, rc->get_value().get_string(), alt_msg);
+ auto& dls = lnav_data.ld_db_row_source;
+ attr_line_t prompt;
+
+ if (result.isOk()) {
+ auto msg = result.unwrap();
+
+ if (!msg.empty()) {
+ prompt = lnav::console::user_message::ok(
+ attr_line_t("SQL Result: ")
+ .append(attr_line_t::from_ansi_str(
+ msg.c_str())))
+ .to_attr_line();
+ if (dls.dls_rows.size() > 1) {
+ ensure_view(&lnav_data.ld_views[LNV_DB]);
+ }
+ }
+ } else {
+ auto um = result.unwrapErr();
+ lnav_data.ld_user_message_source.replace_with(
+ um.to_attr_line().rtrim());
+ lnav_data.ld_user_message_view.reload_data();
+ lnav_data.ld_user_message_expiration
+ = std::chrono::steady_clock::now() + 20s;
+ }
+ ec.ec_source.back().s_content.clear();
+
+ rc->set_attr_value(prompt);
+ rc->set_alt_value(alt_msg);
+ break;
+ }
+
+ case ln_mode_t::EXEC: {
+ auto_mem<FILE> tmpout(fclose);
+
+ tmpout = std::tmpfile();
+
+ if (!tmpout) {
+ rc->set_value(fmt::format(
+ FMT_STRING("Unable to open temporary output file: {}"),
+ strerror(errno)));
+ } else {
+ auto fd_copy = auto_fd::dup_of(fileno(tmpout));
+ char desc[256], timestamp[32];
+ time_t current_time = time(nullptr);
+ const auto path_and_args = rc->get_value();
+
+ {
+ exec_context::output_guard og(
+ ec, "tmp", std::make_pair(tmpout.release(), fclose));
+
+ auto exec_res
+ = execute_file(ec, path_and_args.get_string());
+ if (exec_res.isOk()) {
+ rc->set_value(exec_res.unwrap());
+ } else {
+ auto um = exec_res.unwrapErr();
+
+ lnav_data.ld_user_message_source.replace_with(
+ um.to_attr_line().rtrim());
+ lnav_data.ld_user_message_view.reload_data();
+ lnav_data.ld_user_message_expiration
+ = std::chrono::steady_clock::now() + 20s;
+ rc->set_value("");
+ }
+ }
+
+ struct stat st;
+
+ if (fstat(fd_copy, &st) != -1 && st.st_size > 0) {
+ strftime(timestamp,
+ sizeof(timestamp),
+ "%a %b %d %H:%M:%S %Z",
+ localtime(&current_time));
+ snprintf(desc,
+ sizeof(desc),
+ "Output of %s (%s)",
+ path_and_args.get_string().c_str(),
+ timestamp);
+ lnav_data.ld_active_files.fc_file_names[desc]
+ .with_fd(std::move(fd_copy))
+ .with_include_in_session(false)
+ .with_detect_format(false);
+ lnav_data.ld_files_to_front.emplace_back(desc, 0_vl);
+
+ if (lnav_data.ld_rl_view != nullptr) {
+ lnav_data.ld_rl_view->set_alt_value(
+ HELP_MSG_1(X, "to close the file"));
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+void
+rl_callback(readline_curses* rc)
+{
+ rl_callback_int(rc, false);
+}
+
+void
+rl_alt_callback(readline_curses* rc)
+{
+ rl_callback_int(rc, true);
+}
+
+void
+rl_display_matches(readline_curses* rc)
+{
+ const auto& matches = rc->get_matches();
+ auto& tc = lnav_data.ld_match_view;
+ unsigned long width;
+ __attribute((unused)) unsigned long height;
+ int max_len, cols;
+
+ getmaxyx(lnav_data.ld_window, height, width);
+
+ max_len = rc->get_max_match_length() + 2;
+ cols = std::max(1UL, width / max_len);
+
+ if (matches.empty()) {
+ lnav_data.ld_match_source.clear();
+ } else {
+ const auto current_match = rc->get_match_string();
+ int curr_col = 0;
+ attr_line_t al;
+ bool add_nl = false;
+
+ for (const auto& match : matches) {
+ if (add_nl) {
+ al.append(1, '\n');
+ add_nl = false;
+ }
+ if (match == current_match) {
+ al.append(match, VC_STYLE.value(text_attrs{A_REVERSE}));
+ } else {
+ al.append(match);
+ }
+ curr_col += 1;
+ if (curr_col < cols) {
+ int padding = max_len - match.size();
+
+ al.append(padding, ' ');
+ } else {
+ curr_col = 0;
+ add_nl = true;
+ }
+ }
+ lnav_data.ld_match_source.replace_with(al);
+ }
+
+ tc.reload_data();
+}
+
+void
+rl_display_next(readline_curses* rc)
+{
+ textview_curses& tc = lnav_data.ld_match_view;
+
+ if (tc.get_top() >= (tc.get_top_for_last_row() - 1)) {
+ tc.set_top(0_vl);
+ } else {
+ tc.shift_top(tc.get_height());
+ }
+}
+
+void
+rl_completion_request(readline_curses* rc)
+{
+ isc::to<tailer::looper&, services::remote_tailer_t>().send(
+ [rc](auto& tlooper) {
+ auto rp_opt = humanize::network::path::from_str(
+ rc->get_remote_complete_path());
+ if (rp_opt) {
+ tlooper.complete_path(*rp_opt);
+ }
+ });
+}
+
+void
+rl_focus(readline_curses* rc)
+{
+ auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
+ .get_overlay_source();
+
+ fos->fos_contexts.emplace("", false, true);
+
+ get_textview_for_mode(lnav_data.ld_mode)->save_current_search();
+}
+
+void
+rl_blur(readline_curses* rc)
+{
+ auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
+ .get_overlay_source();
+
+ fos->fos_contexts.pop();
+ lnav_data.ld_preview_generation += 1;
+}