#include "keymap.h" #include #include #include #include #include #include "config.h" #include "confighandlerexception.h" #include "configparser.h" #include "logger.h" #include "strprintf.h" #include "utils.h" #include "libnewsboat-ffi/src/keymap.rs.h" namespace newsboat { struct OpDesc { const Operation op; const std::string opstr; const std::string default_key; const std::string help_text; const unsigned short flags; }; /* * This is the list of operations, defining operation, operation name (for * keybindings), default key, description, and where it's valid */ static const std::vector opdescs = { { OP_OPEN, "open", "ENTER", translatable("Open feed/article"), KM_FEEDLIST | KM_FILEBROWSER | KM_ARTICLELIST | KM_TAGSELECT | KM_FILTERSELECT | KM_URLVIEW | KM_DIALOGS | KM_DIRBROWSER | KM_SEARCHRESULTSLIST }, { OP_SWITCH_FOCUS, "switch-focus", "TAB", translatable("Switch focus between widgets"), KM_FILEBROWSER | KM_DIRBROWSER }, {OP_QUIT, "quit", "q", translatable("Return to previous dialog/Quit"), KM_BOTH}, { OP_HARDQUIT, "hard-quit", "Q", translatable("Quit program, no confirmation"), KM_BOTH }, { OP_RELOAD, "reload", "r", translatable("Reload currently selected feed"), KM_FEEDLIST | KM_ARTICLELIST }, {OP_RELOADALL, "reload-all", "R", translatable("Reload all feeds"), KM_FEEDLIST}, { OP_MARKFEEDREAD, "mark-feed-read", "A", translatable("Mark feed read"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_MARKALLFEEDSREAD, "mark-all-feeds-read", "C", translatable("Mark all feeds read"), KM_FEEDLIST }, { OP_MARKALLABOVEASREAD, "mark-all-above-as-read", "", translatable("Mark all above as read"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, {OP_SAVE, "save", "s", translatable("Save article"), KM_ARTICLELIST | KM_ARTICLE | KM_SEARCHRESULTSLIST}, {OP_SAVEALL, "save-all", "", translatable("Save articles"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST}, { OP_NEXT, "next", "J", translatable("Go to next entry"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE | KM_DIALOGS | KM_DIRBROWSER | KM_FILEBROWSER | KM_FILTERSELECT | KM_TAGSELECT | KM_URLVIEW | KM_SEARCHRESULTSLIST | KM_PODBOAT }, { OP_PREV, "prev", "K", translatable("Go to previous entry"), KM_FEEDLIST | KM_ARTICLELIST | KM_ARTICLE | KM_DIALOGS | KM_DIRBROWSER | KM_FILEBROWSER | KM_FILTERSELECT | KM_TAGSELECT | KM_URLVIEW | KM_SEARCHRESULTSLIST | KM_PODBOAT }, { OP_NEXTUNREAD, "next-unread", "n", translatable("Go to next unread article"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, { OP_PREVUNREAD, "prev-unread", "p", translatable("Go to previous unread article"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, { OP_RANDOMUNREAD, "random-unread", "^K", translatable("Go to a random unread article"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, { OP_OPENBROWSER_AND_MARK, "open-in-browser-and-mark-read", "O", translatable("Open URL of article, or entry in URL view. Mark read"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE | KM_URLVIEW }, { OP_OPENALLUNREADINBROWSER, "open-all-unread-in-browser", "", translatable("Open all unread items of selected feed in browser"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_OPENALLUNREADINBROWSER_AND_MARK, "open-all-unread-in-browser-and-mark-read", "", translatable("Open all unread items of selected feed in browser and mark " "read"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_OPENINBROWSER, "open-in-browser", "o", translatable("Open URL of article, feed, or entry in URL view"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST| KM_ARTICLE | KM_URLVIEW }, { OP_OPENINBROWSER_NONINTERACTIVE, "open-in-browser-noninteractively", "", translatable("Open URL of article, feed, or entry in a browser, non-interactively"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE | KM_URLVIEW }, { OP_HELP, "help", "?", translatable("Open help dialog"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE | KM_PODBOAT | KM_URLVIEW }, { OP_TOGGLESOURCEVIEW, "toggle-source-view", "^U", translatable("Toggle source view"), KM_ARTICLE }, { OP_TOGGLEITEMREAD, "toggle-article-read", "N", translatable("Toggle read status for article"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, { OP_TOGGLESHOWREAD, "toggle-show-read-feeds", "l", translatable("Toggle show read feeds/articles"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_SHOWURLS, "show-urls", "u", translatable("Show URLs in current article"), KM_ARTICLE | KM_SEARCHRESULTSLIST | KM_ARTICLELIST }, {OP_CLEARTAG, "clear-tag", "^T", translatable("Clear current tag"), KM_FEEDLIST}, {OP_SETTAG, "set-tag", "t", translatable("Select tag"), KM_FEEDLIST}, {OP_SETTAG, "select-tag", "t", translatable("Select tag"), KM_FEEDLIST}, { OP_SEARCH, "open-search", "/", translatable("Open search dialog"), KM_FEEDLIST | KM_HELP | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, {OP_GOTO_URL, "goto-url", "#", translatable("Goto URL #"), KM_ARTICLE}, {OP_GOTO_TITLE, "goto-title", "", translatable("Goto item with title"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST}, {OP_ENQUEUE, "enqueue", "e", translatable("Add download to queue"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE}, { OP_RELOADURLS, "reload-urls", "^R", translatable("Reload the list of URLs from the configuration"), KM_FEEDLIST }, {OP_PB_DOWNLOAD, "pb-download", "d", translatable("Download file"), KM_PODBOAT}, {OP_PB_CANCEL, "pb-cancel", "c", translatable("Cancel download"), KM_PODBOAT}, { OP_PB_DELETE, "pb-delete", "D", translatable("Mark download as deleted"), KM_PODBOAT }, { OP_PB_PURGE, "pb-purge", "P", translatable("Purge finished and deleted downloads from queue"), KM_PODBOAT }, { OP_PB_TOGGLE_DLALL, "pb-toggle-download-all", "a", translatable("Toggle automatic download on/off"), KM_PODBOAT }, { OP_PB_PLAY, "pb-play", "p", translatable("Start player with currently selected download"), KM_PODBOAT }, { OP_PB_MARK_FINISHED, "pb-mark-as-finished", "m", translatable("Mark file as finished (not played)"), KM_PODBOAT }, { OP_PB_MOREDL, "pb-increase-max-dls", "+", translatable("Increase the number of concurrent downloads"), KM_PODBOAT }, { OP_PB_LESSDL, "pb-decreate-max-dls", "-", translatable("Decrease the number of concurrent downloads"), KM_PODBOAT }, {OP_REDRAW, "redraw", "^L", translatable("Redraw screen"), KM_SYSKEYS}, {OP_CMDLINE, "cmdline", ":", translatable("Open the commandline"), KM_NEWSBOAT}, { OP_SETFILTER, "set-filter", "F", translatable("Set a filter"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_SELECTFILTER, "select-filter", "f", translatable("Select a predefined filter"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_CLEARFILTER, "clear-filter", "^F", translatable("Clear currently set filter"), KM_FEEDLIST | KM_HELP | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_BOOKMARK, "bookmark", "^B", translatable("Bookmark current link/article"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE | KM_URLVIEW }, { OP_EDITFLAGS, "edit-flags", "^E", translatable("Edit flags"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, {OP_NEXTFEED, "next-feed", "j", translatable("Go to next feed"), KM_ARTICLELIST}, { OP_PREVFEED, "prev-feed", "k", translatable("Go to previous feed"), KM_ARTICLELIST }, { OP_NEXTUNREADFEED, "next-unread-feed", "^N", translatable("Go to next unread feed"), KM_ARTICLELIST }, { OP_PREVUNREADFEED, "prev-unread-feed", "^P", translatable("Go to previous unread feed"), KM_ARTICLELIST }, {OP_MACROPREFIX, "macro-prefix", ",", translatable("Call a macro"), KM_NEWSBOAT}, { OP_DELETE, "delete-article", "D", translatable("Delete article"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_ARTICLE }, { OP_DELETE_ALL, "delete-all-articles", "^D", translatable("Delete all articles"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_PURGE_DELETED, "purge-deleted", "$", translatable("Purge deleted articles"), KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_EDIT_URLS, "edit-urls", "E", translatable("Edit subscribed URLs"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_CLOSEDIALOG, "close-dialog", "^X", translatable("Close currently selected dialog"), KM_DIALOGS }, { OP_VIEWDIALOGS, "view-dialogs", "v", translatable("View list of open dialogs"), KM_NEWSBOAT }, { OP_NEXTDIALOG, "next-dialog", "^V", translatable("Go to next dialog"), KM_NEWSBOAT }, { OP_PREVDIALOG, "prev-dialog", "^G", translatable("Go to previous dialog"), KM_NEWSBOAT }, { OP_PIPE_TO, "pipe-to", "|", translatable("Pipe article to command"), KM_ARTICLE | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_SORT, "sort", "g", translatable("Sort current list"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_REVSORT, "rev-sort", "G", translatable("Sort current list (reverse)"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST }, { OP_PREVSEARCHRESULTS, "prevsearchresults", "z", translatable("Return to previous search results (if any)"), KM_SEARCHRESULTSLIST }, {OP_OPEN_URL_1, "one", "1", translatable("Open URL 1"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_2, "two", "2", translatable("Open URL 2"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_3, "three", "3", translatable("Open URL 3"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_4, "four", "4", translatable("Open URL 4"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_5, "five", "5", translatable("Open URL 5"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_6, "six", "6", translatable("Open URL 6"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_7, "seven", "7", translatable("Open URL 7"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_8, "eight", "8", translatable("Open URL 8"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_9, "nine", "9", translatable("Open URL 9"), KM_URLVIEW | KM_ARTICLE}, {OP_OPEN_URL_10, "zero", "0", translatable("Open URL 10"), KM_URLVIEW | KM_ARTICLE}, {OP_CMD_START_1, "cmd-one", "1", translatable("Start cmdline with 1"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_2, "cmd-two", "2", translatable("Start cmdline with 2"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_3, "cmd-three", "3", translatable("Start cmdline with 3"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_4, "cmd-four", "4", translatable("Start cmdline with 4"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_5, "cmd-five", "5", translatable("Start cmdline with 5"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_6, "cmd-six", "6", translatable("Start cmdline with 6"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_7, "cmd-seven", "7", translatable("Start cmdline with 7"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_8, "cmd-eight", "8", translatable("Start cmdline with 8"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_CMD_START_9, "cmd-nine", "9", translatable("Start cmdline with 9"), KM_FEEDLIST | KM_ARTICLELIST | KM_SEARCHRESULTSLIST | KM_TAGSELECT | KM_FILTERSELECT}, {OP_SK_UP, "up", "UP", translatable("Move to the previous entry"), KM_SYSKEYS}, {OP_SK_DOWN, "down", "DOWN", translatable("Move to the next entry"), KM_SYSKEYS}, { OP_SK_PGUP, "pageup", "PPAGE", translatable("Move to the previous page"), KM_SYSKEYS }, { OP_SK_PGDOWN, "pagedown", "NPAGE", translatable("Move to the next page"), KM_SYSKEYS }, { OP_SK_HOME, "home", "HOME", translatable("Move to the start of page/list"), KM_SYSKEYS }, { OP_SK_END, "end", "END", translatable("Move to the end of page/list"), KM_SYSKEYS }, { OP_INT_END_QUESTION, "XXXNOKEY-end-question", "end-question", "", KM_INTERNAL }, { OP_INT_CANCEL_QNA, "XXXNOKEY-cancel-qna", "cancel-qna", "", KM_INTERNAL }, { OP_INT_QNA_NEXTHIST, "XXXNOKEY-qna-next-history", "qna-next-history", "", KM_INTERNAL }, { OP_INT_QNA_PREVHIST, "XXXNOKEY-qna-prev-history", "qna-prev-history", "", KM_INTERNAL }, {OP_INT_SET, "set", "internal-set", "", KM_INTERNAL}, {OP_INT_GOTO_URL, "gotourl", "internal-goto-url", "", KM_INTERNAL}, }; static const std::map contexts = { {"feedlist", KM_FEEDLIST}, {"filebrowser", KM_FILEBROWSER}, {"help", KM_HELP}, {"articlelist", KM_ARTICLELIST}, {"article", KM_ARTICLE}, {"tagselection", KM_TAGSELECT}, {"filterselection", KM_FILTERSELECT}, {"urlview", KM_URLVIEW}, {"podboat", KM_PODBOAT}, {"dialogs", KM_DIALOGS}, {"dirbrowser", KM_DIRBROWSER}, {"searchresultslist", KM_SEARCHRESULTSLIST}, }; KeyMap::KeyMap(unsigned flags) { /* * At startup, initialize the keymap with the default settings from the * list above. */ LOG(Level::DEBUG, "KeyMap::KeyMap: flags = %x", flags); for (const auto& op_desc : opdescs) { if (!(op_desc.flags & (flags | KM_INTERNAL | KM_SYSKEYS))) { continue; } // Skip operations without a default key if (op_desc.default_key.empty()) { continue; } for (const auto& ctx : contexts) { const std::string& context = ctx.first; const std::uint32_t context_flag = ctx.second; if ((op_desc.flags & (context_flag | KM_INTERNAL | KM_SYSKEYS))) { keymap_[context][op_desc.default_key] = op_desc.op; } } } keymap_["help"]["b"] = OP_SK_PGUP; keymap_["help"]["SPACE"] = OP_SK_PGDOWN; keymap_["article"]["b"] = OP_SK_PGUP; keymap_["article"]["SPACE"] = OP_SK_PGDOWN; } std::vector KeyMap::get_keymap_descriptions(std::string context) { std::vector descs; for (const auto& opdesc : opdescs) { if (!(opdesc.flags & get_flag_from_context(context))) { // Ignore operation if it is not valid in this context continue; } bool bound_to_key = false; for (const auto& keymap : keymap_[context]) { const std::string& key = keymap.first; const Operation op = keymap.second; if (opdesc.op == op) { descs.push_back({key, opdesc.opstr, _(opdesc.help_text.c_str()), context, opdesc.flags}); bound_to_key = true; } } if (!bound_to_key) { LOG(Level::DEBUG, "KeyMap::get_keymap_descriptions: found unbound function: %s context = %s", opdesc.opstr, context); descs.push_back({"", opdesc.opstr, _(opdesc.help_text.c_str()), context, opdesc.flags}); } } return descs; } const std::map& KeyMap::get_macro_descriptions() { return macros_; } KeyMap::~KeyMap() {} void KeyMap::set_key(Operation op, const std::string& key, const std::string& context) { LOG(Level::DEBUG, "KeyMap::set_key(%d,%s) called", op, key); if (context == "all") { for (const auto& ctx : contexts) { keymap_[ctx.first][key] = op; } } else { keymap_[context][key] = op; } } void KeyMap::unset_key(const std::string& key, const std::string& context) { LOG(Level::DEBUG, "KeyMap::unset_key(%s) called", key); if (context == "all") { for (const auto& ctx : contexts) { keymap_[ctx.first][key] = OP_NIL; } } else { keymap_[context][key] = OP_NIL; } } void KeyMap::unset_all_keys(const std::string& context) { LOG(Level::DEBUG, "KeyMap::unset_all_keys(%s) called", context); auto internal_ops_only = get_internal_operations(); if (context == "all") { for (const auto& ctx : contexts) { keymap_[ctx.first] = internal_ops_only; } } else { keymap_[context] = std::move(internal_ops_only); } } Operation KeyMap::get_opcode(const std::string& opstr) { for (const auto& opdesc : opdescs) { if (opstr == opdesc.opstr) { return opdesc.op; } } return OP_NIL; } char KeyMap::get_key(const std::string& keycode) { if (keycode == "ENTER") { return '\n'; } else if (keycode == "ESC") { return 27; } else if (keycode.length() == 2 && keycode[0] == '^') { char chr = keycode[1]; return chr - '@'; } else if (keycode.length() == 1) { // TODO: implement more keys return keycode[0]; } return 0; } Operation KeyMap::get_operation(const std::string& keycode, const std::string& context) { std::string key; LOG(Level::DEBUG, "KeyMap::get_operation: keycode = %s context = %s", keycode, context); if (keycode.length() > 0) { key = keycode; } else { key = "NIL"; } return keymap_[context][key]; } void KeyMap::dump_config(std::vector& config_output) const { for (const auto& ctx : contexts) { const std::string& context = ctx.first; const auto& x = keymap_.at(context); for (const auto& keymap : x) { if (keymap.second < OP_INT_MIN) { std::string configline = "bind-key "; configline.append(utils::quote(keymap.first)); configline.append(" "); configline.append(getopname(keymap.second)); configline.append(" "); configline.append(context); config_output.push_back(configline); } } } for (const auto& macro : macros_) { std::string configline = "macro "; configline.append(macro.first); configline.append(" "); for (unsigned int i = 0; i < macro.second.cmds.size(); ++i) { const auto& cmd = macro.second.cmds[i]; configline.append(getopname(cmd.op)); for (const auto& arg : cmd.args) { configline.append(" "); configline.append(utils::quote(arg)); } if (i < (macro.second.cmds.size() - 1)) { configline.append(" ; "); } } if (macro.second.description.size() >= 1) { const auto escaped_string = utils::replace_all(macro.second.description, { {R"(\)", R"(\\)"}, {R"(")", R"(\")"}, }); configline.append(strprintf::fmt(R"( -- "%s")", escaped_string)); } config_output.push_back(configline); } } std::string KeyMap::getopname(Operation op) const { for (const auto& opdesc : opdescs) { if (opdesc.op == op) { return opdesc.opstr; } } return ""; } void KeyMap::handle_action(const std::string& action, const std::string& params) { /* * The keymap acts as ConfigActionHandler so that all the key-related * configuration is immediately handed to it. */ LOG(Level::DEBUG, "KeyMap::handle_action(%s, ...) called", action); if (action == "bind-key") { const auto tokens = utils::tokenize_quoted(params); if (tokens.size() < 2) { throw ConfigHandlerException(ActionHandlerStatus::TOO_FEW_PARAMS); } std::string context = "all"; if (tokens.size() >= 3) { context = tokens[2]; } if (!is_valid_context(context)) { throw ConfigHandlerException(strprintf::fmt( _("`%s' is not a valid context"), context)); } const Operation op = get_opcode(tokens[1]); if (op == OP_NIL) { throw ConfigHandlerException( strprintf::fmt(_("`%s' is not a valid " "key command"), tokens[1])); } set_key(op, tokens[0], context); } else if (action == "unbind-key") { const auto tokens = utils::tokenize_quoted(params); if (tokens.size() < 1) { throw ConfigHandlerException( ActionHandlerStatus::TOO_FEW_PARAMS); } std::string context = "all"; if (tokens.size() >= 2) { context = tokens[1]; } if (tokens[0] == "-a") { unset_all_keys(context); } else { unset_key(tokens[0], context); } } else if (action == "macro") { std::string remaining_params = params; const auto token = utils::extract_token_quoted(remaining_params); const auto parsed = parse_operation_sequence(remaining_params, action); const std::vector cmds = parsed.operations; const std::string description = parsed.description; if (!token.has_value() || cmds.empty()) { throw ConfigHandlerException(ActionHandlerStatus::TOO_FEW_PARAMS); } const std::string macrokey = token.value(); macros_[macrokey] = {cmds, description}; } else if (action == "run-on-startup") { startup_operations_sequence = parse_operation_sequence(params, action, false).operations; } else { throw ConfigHandlerException(ActionHandlerStatus::INVALID_PARAMS); } } ParsedOperations KeyMap::parse_operation_sequence(const std::string& line, const std::string& command_name, bool allow_description) { rust::String description; bool parsing_failed = false; const auto operations = keymap::bridged::tokenize_operation_sequence(line, description, allow_description, parsing_failed); if (parsing_failed) { throw ConfigHandlerException(strprintf::fmt(_("failed to parse operation sequence for %s"), command_name)); } std::vector cmds; for (const auto& operation : operations) { const auto& tokens = keymap::bridged::operation_tokens(operation); if (tokens.empty()) { continue; } const auto command_name = std::string(tokens[0]); const auto arguments = std::vector(std::next(std::begin(tokens)), std::end(tokens)); MacroCmd cmd; cmd.op = get_opcode(command_name); if (cmd.op == OP_NIL) { throw ConfigHandlerException(strprintf::fmt(_("`%s' is not a valid operation"), command_name)); } cmd.args = arguments; cmds.push_back(cmd); } return ParsedOperations{ .operations = cmds, .description = std::string(description) }; } std::vector KeyMap::get_startup_operation_sequence() { return startup_operations_sequence; } std::vector KeyMap::get_keys(Operation op, const std::string& context) { std::vector keys; for (const auto& keymap : keymap_[context]) { if (keymap.second == op) { keys.push_back(keymap.first); } } return keys; } std::vector KeyMap::get_macro(const std::string& key) { if (macros_.count(key) >= 1) { return macros_.at(key).cmds; } return {}; } bool KeyMap::is_valid_context(const std::string& context) { if (context == "all") { return true; } if (contexts.count(context) >= 1) { return true; } return false; } std::map KeyMap::get_internal_operations() const { std::map internal_ops; for (const auto& opdesc : opdescs) { if (opdesc.flags & KM_INTERNAL) { internal_ops[opdesc.default_key] = opdesc.op; } } return internal_ops; } unsigned short KeyMap::get_flag_from_context(const std::string& context) { if (contexts.count(context) >= 1) { return contexts.at(context) | KM_SYSKEYS; } return 0; // shouldn't happen } std::string KeyMap::prepare_keymap_hint(const std::vector& hints, const std::string& context) { std::string keymap_hint; for (const auto& hint : hints) { std::vector keys = get_keys(hint.op, context); if (keys.empty()) { keys = {""}; } for (auto& key : keys) { key = strprintf::fmt("%s", utils::quote_for_stfl(key)); } keymap_hint.append(utils::join(keys, ",")); keymap_hint.append(":"); keymap_hint.append(strprintf::fmt("%s", utils::quote_for_stfl(hint.text))); keymap_hint.append(" "); } return keymap_hint; } } // namespace newsboat