diff --git a/NEWS.md b/NEWS.md index 0b712c24..3c73f015 100644 --- a/NEWS.md +++ b/NEWS.md @@ -90,6 +90,7 @@ Interface changes: can now hide/show fields by moving the cursor line to the given field and pressing the space bar or by clicking on the diamond with the mouse. +* The `sv` keymap binds `ยง` to focus the breadcrumb bar. Bug Fixes: * With the recent xz backdoor shenanigans, it seems like a good diff --git a/docs/schemas/config-v1.schema.json b/docs/schemas/config-v1.schema.json index 92b65460..9ca49b49 100644 --- a/docs/schemas/config-v1.schema.json +++ b/docs/schemas/config-v1.schema.json @@ -803,7 +803,7 @@ "title": "/ui/keymap-defs///command", "description": "The command to execute for the given key sequence. Use a script to execute more complicated operations.", "type": "string", - "pattern": "^[:|;].*", + "pattern": "^$|^[:|;].*", "examples": [ ":goto next hour" ] diff --git a/src/command_executor.cc b/src/command_executor.cc index b5bbbdbe..ca62818a 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -785,6 +785,17 @@ execute_from_file(exec_context& ec, Result execute_any(exec_context& ec, const std::string& cmdline_with_mode) { + if (cmdline_with_mode.empty()) { + auto um = lnav::console::user_message::error("empty command") + .with_help( + "a command should start with ':', ';', '/', '|' and " + "followed by the operation to perform"); + if (!ec.ec_source.empty()) { + um.with_snippet(ec.ec_source.back()); + } + return Err(um); + } + std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1); auto _cleanup = finally([&ec] { if (ec.is_read_write() && diff --git a/src/hotkeys.cc b/src/hotkeys.cc index ee876760..742998f4 100644 --- a/src/hotkeys.cc +++ b/src/hotkeys.cc @@ -166,8 +166,12 @@ handle_keyseq(const char* keyseq) vars["keyseq"] = keyseq; const auto& kc = iter->second; - log_debug("executing key sequence %s: %s", keyseq, kc.kc_cmd.c_str()); - auto result = execute_any(ec, kc.kc_cmd); + log_debug( + "executing key sequence %s: %s", keyseq, kc.kc_cmd.pp_value.c_str()); + auto sg = ec.enter_source(kc.kc_cmd.pp_location.sl_source, + kc.kc_cmd.pp_location.sl_line_number, + kc.kc_cmd.pp_value); + auto result = execute_any(ec, kc.kc_cmd.pp_value); if (result.isOk()) { lnav_data.ld_rl_view->set_value(result.unwrap()); } else { @@ -194,7 +198,7 @@ handle_keyseq(const char* keyseq) } bool -handle_paging_key(int ch) +handle_paging_key(int ch, const char* keyseq) { if (lnav_data.ld_view_stack.empty()) { return false; @@ -211,8 +215,7 @@ handle_paging_key(int ch) } } - auto keyseq = fmt::format(FMT_STRING("x{:02x}"), ch); - if (handle_keyseq(keyseq.c_str())) { + if (handle_keyseq(keyseq)) { return true; } diff --git a/src/hotkeys.hh b/src/hotkeys.hh index 3bbe060b..97b031cf 100644 --- a/src/hotkeys.hh +++ b/src/hotkeys.hh @@ -31,6 +31,6 @@ #define LNAV_HOTKEYS_H bool handle_keyseq(const char* keyseq); -bool handle_paging_key(int ch); +bool handle_paging_key(int ch, const char* keyseq); #endif // LNAV_HOTKEYS_H diff --git a/src/input_dispatcher.cc b/src/input_dispatcher.cc index 61f6e43b..7df19aa9 100644 --- a/src/input_dispatcher.cc +++ b/src/input_dispatcher.cc @@ -102,8 +102,12 @@ input_dispatcher::new_input(const struct timeval& current_time, int ch) for (int lpc = 0; this->id_escape_buffer[lpc]; lpc++) { + snprintf(keyseq.data(), + keyseq.size(), + "x%02x", + this->id_escape_buffer[lpc] & 0xff); handled = this->id_key_handler( - this->id_escape_buffer[lpc]); + this->id_escape_buffer[lpc], keyseq.data()); } this->id_escape_index = 0; break; @@ -121,6 +125,13 @@ input_dispatcher::new_input(const struct timeval& current_time, int ch) { this->id_escape_index = 0; } + } else if (ch > 0xff) { + if (KEY_F(0) <= ch && ch <= KEY_F(64)) { + snprintf(keyseq.data(), keyseq.size(), "f%d", ch - KEY_F0); + } else { + snprintf(keyseq.data(), keyseq.size(), "n%04o", ch); + } + handled = this->id_key_handler(ch, keyseq.data()); } else { auto seq_size = utf::utf8::char_size([ch]() { return std::make_pair(ch, 16); @@ -128,7 +139,7 @@ input_dispatcher::new_input(const struct timeval& current_time, int ch) if (seq_size == 1) { snprintf(keyseq.data(), keyseq.size(), "x%02x", ch & 0xff); - handled = this->id_key_handler(ch); + handled = this->id_key_handler(ch, keyseq.data()); } else { this->reset_escape_buffer(ch, current_time, seq_size); } @@ -150,7 +161,8 @@ input_dispatcher::poll(const struct timeval& current_time) timersub(¤t_time, &this->id_escape_start_time, &diff); if (escape_threshold < diff) { - this->id_key_handler(KEY_ESCAPE); + static const char ESC_KEYSEQ[] = "\x1b"; + this->id_key_handler(KEY_ESCAPE, ESC_KEYSEQ); this->id_escape_index = 0; } } diff --git a/src/input_dispatcher.hh b/src/input_dispatcher.hh index 3ba3e379..0247e861 100644 --- a/src/input_dispatcher.hh +++ b/src/input_dispatcher.hh @@ -53,7 +53,7 @@ public: }; std::function id_escape_matcher; - std::function id_key_handler; + std::function id_key_handler; std::function id_escape_handler; std::function id_mouse_handler; std::function id_unhandled_handler; diff --git a/src/keymaps/sv-keymap.json b/src/keymaps/sv-keymap.json index 867e6d46..b13e0730 100644 --- a/src/keymaps/sv-keymap.json +++ b/src/keymaps/sv-keymap.json @@ -22,7 +22,8 @@ "command": ";UPDATE lnav_views SET paused = 1 - paused" }, "x60": { - "id": "" + "id": "", + "command": ":prompt breadcrumb" }, "xc2xa7": { "id": "org.lnav.key.breadcrumb.focus", diff --git a/src/lnav.cc b/src/lnav.cc index 6253ec48..136f61f8 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -367,7 +367,7 @@ sigchld(int sig) } static void -handle_rl_key(int ch) +handle_rl_key(int ch, const char* keyseq) { switch (ch) { case KEY_F(2): @@ -379,7 +379,7 @@ handle_rl_key(int ch) case KEY_PPAGE: case KEY_NPAGE: case KEY_CTRL('p'): - handle_paging_key(ch); + handle_paging_key(ch, keyseq); break; case KEY_CTRL(']'): @@ -675,7 +675,7 @@ update_view_position(listview_curses* lv) } static bool -handle_config_ui_key(int ch) +handle_config_ui_key(int ch, const char* keyseq) { bool retval = false; @@ -730,14 +730,14 @@ handle_config_ui_key(int ch) lnav_data.ld_filter_view.reload_data(); lnav_data.ld_status[LNS_FILTER].set_needs_update(); } else { - return handle_paging_key(ch); + return handle_paging_key(ch, keyseq); } return true; } static bool -handle_key(int ch) +handle_key(int ch, const char* keyseq) { static auto* breadcrumb_view = injector::get(); @@ -749,7 +749,7 @@ handle_key(int ch) default: { switch (lnav_data.ld_mode) { case ln_mode_t::PAGING: - return handle_paging_key(ch); + return handle_paging_key(ch, keyseq); case ln_mode_t::BREADCRUMBS: if (ch == '`' || !breadcrumb_view->handle_key(ch)) { @@ -761,7 +761,7 @@ handle_key(int ch) case ln_mode_t::FILTER: case ln_mode_t::FILES: - return handle_config_ui_key(ch); + return handle_config_ui_key(ch, keyseq); case ln_mode_t::SPECTRO_DETAILS: { if (ch == '\t' || ch == 'q') { @@ -800,7 +800,7 @@ handle_key(int ch) case ln_mode_t::SQL: case ln_mode_t::EXEC: case ln_mode_t::USER: - handle_rl_key(ch); + handle_rl_key(ch, keyseq); break; case ln_mode_t::BUSY: diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 587bb7eb..9fd73814 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -5971,9 +5971,7 @@ readline_context::command_t STD_COMMANDS[] = { "The initial value to fill in for the prompt") .optional()) .with_example({ - "To open the command prompt with 'filter-in' already " - "filled " - "in", + "To open the command prompt with 'filter-in' already filled in", "command : 'filter-in '", }) .with_example({ diff --git a/src/lnav_config.cc b/src/lnav_config.cc index 66bc787f..e8f91d31 100644 --- a/src/lnav_config.cc +++ b/src/lnav_config.cc @@ -498,7 +498,7 @@ static const struct json_path_container key_command_handlers = { .with_description( "The command to execute for the given key sequence. Use a script " "to execute more complicated operations.") - .with_pattern("^[:|;].*") + .with_pattern("^$|^[:|;].*") .with_example(":goto next hour") .for_field(&key_command::kc_cmd), yajlpp::property_handler("alt-msg") @@ -520,6 +520,14 @@ static const struct json_path_container keymap_def_handlers = { [](const yajlpp_provider_context& ypc, key_map* km) { auto& retval = km->km_seq_to_cmd[ypc.get_substr("key_seq")]; + if (ypc.ypc_parse_context != nullptr) { + retval.kc_cmd.pp_path + = ypc.ypc_parse_context->get_full_path(); + retval.kc_cmd.pp_location.sl_source + = ypc.ypc_parse_context->ypc_source; + retval.kc_cmd.pp_location.sl_line_number + = ypc.ypc_parse_context->get_line_number(); + } return &retval; }) .with_path_provider( @@ -1570,8 +1578,12 @@ public: for (const auto& pair : lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap].km_seq_to_cmd) { - lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first] - = pair.second; + if (pair.second.kc_cmd.pp_value.empty()) { + lnav_config.lc_active_keymap.km_seq_to_cmd.erase(pair.first); + } else { + lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first] + = pair.second; + } } auto& ec = injector::get(); @@ -1580,31 +1592,52 @@ public: continue; } + auto keyseq_sf = string_fragment::from_str(pair.first); std::string keystr; - - auto sv = string_fragment::from_str(pair.first).to_string_view(); - while (!sv.empty()) { + if (keyseq_sf.startswith("f")) { + auto sv = keyseq_sf.to_string_view(); int32_t value; - auto scan_res = scn::scan(sv, "x{:2x}", value); + auto scan_res = scn::scan(sv, "f{}", value); if (!scan_res) { - throw "invalid hex input"; + log_error("invalid function key sequence: %s", keyseq_sf); + continue; } - auto ch = (char) (value & 0xff); - switch (ch) { - case '\t': - keystr.append("TAB"); - break; - case '\r': - keystr.append("ENTER"); - break; - default: - keystr.push_back(ch); + if (value < 0 || value > 64) { + log_error("invalid function key number: %s", keyseq_sf); + continue; + } + + keystr = toupper(pair.first); + } else { + auto sv + = string_fragment::from_str(pair.first).to_string_view(); + while (!sv.empty()) { + int32_t value; + auto scan_res = scn::scan(sv, "x{:2x}", value); + if (!scan_res) { + log_error("invalid key sequence: %s", + pair.first.c_str()); break; + } + auto ch = (char) (value & 0xff); + switch (ch) { + case '\t': + keystr.append("TAB"); + break; + case '\r': + keystr.append("ENTER"); + break; + default: + keystr.push_back(ch); + break; + } + sv = scan_res.range_as_string_view(); } - sv = scan_res.range_as_string_view(); } - ec.ec_global_vars[pair.second.kc_id] = keystr; + if (!keystr.empty()) { + ec.ec_global_vars[pair.second.kc_id] = keystr; + } } } }; @@ -1908,7 +1941,7 @@ save_config() void reload_config(std::vector& errors) { - lnav_config_listener* curr = lnav_config_listener::LISTENER_LIST; + auto* curr = lnav_config_listener::LISTENER_LIST; while (curr != nullptr) { auto reporter = [&errors](const void* cfg_value, diff --git a/src/lnav_config.hh b/src/lnav_config.hh index 0c38955d..24080066 100644 --- a/src/lnav_config.hh +++ b/src/lnav_config.hh @@ -81,7 +81,7 @@ void install_extra_formats(); struct key_command { std::string kc_id; - std::string kc_cmd; + positioned_property kc_cmd; std::string kc_alt_msg; }; diff --git a/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out b/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out index 652c97e5..1a62b0e3 100644 --- a/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out +++ b/test/expected/test_cli.sh_0b3639753916f71254e8c9cce4ebb8bfd9978d3e.out @@ -5118,7 +5118,7 @@ }, "x60": { "id": "", - "command": "", + "command": ":prompt breadcrumb", "alt-msg": "" }, "xc2xa4": { diff --git a/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out b/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out index d7da0d34..22a87a39 100644 --- a/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out +++ b/test/expected/test_cli.sh_cc06341dd560f927512e92c7c0985ed8b25827ae.out @@ -155,10 +155,11 @@ /ui/keymap-defs/sv/x26/command -> sv-keymap.json:16 /ui/keymap-defs/sv/x2b/command -> sv-keymap.json:22 /ui/keymap-defs/sv/x3d/command -> sv-keymap.json:19 +/ui/keymap-defs/sv/x60/command -> sv-keymap.json:26 /ui/keymap-defs/sv/x60/id -> sv-keymap.json:25 /ui/keymap-defs/sv/xc2xa4/command -> sv-keymap.json:10 -/ui/keymap-defs/sv/xc2xa7/command -> sv-keymap.json:29 -/ui/keymap-defs/sv/xc2xa7/id -> sv-keymap.json:28 +/ui/keymap-defs/sv/xc2xa7/command -> sv-keymap.json:30 +/ui/keymap-defs/sv/xc2xa7/id -> sv-keymap.json:29 /ui/keymap-defs/sv/xe2x82xac/command -> sv-keymap.json:13 /ui/keymap-defs/uk/x22/command -> uk-keymap.json:7 /ui/keymap-defs/uk/xc2xa3/command -> uk-keymap.json:10