/** * 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 COMMANDS_WITH_SQL = { "filter-expr", "mark-expr", }; static const std::set 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 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(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(); 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 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 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 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 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(¤t_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().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; }