From 185b980718b2d999fb91e53f10eb453b2b530883 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 29 Oct 2014 23:22:54 +0000 Subject: [PATCH] Add scoped aliases aliases are now stored in window, buffer, or globally. --- README.asciidoc | 21 ++++++++++++++ src/alias_registry.cc | 48 ++++++++++++++++++++++++++++++++ src/alias_registry.hh | 37 +++++++++++++++++++++++++ src/buffer.cc | 3 +- src/buffer.hh | 4 +++ src/command_manager.cc | 31 ++++++++------------- src/command_manager.hh | 8 +++--- src/commands.cc | 62 ++++++++++++++++++++++++++++++++---------- src/context.cc | 10 +++++++ src/context.hh | 2 ++ src/main.cc | 3 ++ src/normal.cc | 2 +- src/window.cc | 3 +- src/window.hh | 4 +++ 14 files changed, 197 insertions(+), 41 deletions(-) create mode 100644 src/alias_registry.cc create mode 100644 src/alias_registry.hh diff --git a/README.asciidoc b/README.asciidoc index 81086a57..cd9d5836 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -988,6 +988,27 @@ Some helper commands can be used to define composite commands: Note that these commands are available in interactive command mode, but are not that useful in this context. +Aliases +------- + +With +:alias+ commands can be given additional names. aliases are scoped, so +that an alias can refer to one command for a buffer, and to another for another +buffer. + +-------------------------------- +:alias +-------------------------------- + +with ++ being +global+, +buffer+ or +window+, will define ++ as +an alias for ++ + +------------------------------------- +:unalias [] +------------------------------------- + +Will remove the given alias in the given scope. If ++ is specified +the alias will only be removed if its current value is ++. + FIFO Buffer ----------- diff --git a/src/alias_registry.cc b/src/alias_registry.cc new file mode 100644 index 00000000..95c4784b --- /dev/null +++ b/src/alias_registry.cc @@ -0,0 +1,48 @@ +#include "alias_registry.hh" + +#include "command_manager.hh" + +namespace Kakoune +{ + +void AliasRegistry::add_alias(String alias, String command) +{ + kak_assert(not alias.empty()); + kak_assert(CommandManager::instance().command_defined(command)); + m_aliases[alias] = std::move(command); +} + +void AliasRegistry::remove_alias(const String& alias) +{ + auto it = m_aliases.find(alias); + if (it != m_aliases.end()) + m_aliases.erase(it); +} + +StringView AliasRegistry::operator[](const String& alias) const +{ + auto it = m_aliases.find(alias); + if (it != m_aliases.end()) + return it->second; + else if (m_parent) + return (*m_parent)[alias]; + else + return StringView{}; +} + +std::vector AliasRegistry::aliases_for(StringView command) const +{ + std::vector res; + if (m_parent) + res = m_parent->aliases_for(command); + + for (auto& alias : m_aliases) + { + if (alias.second == command) + res.push_back(alias.first); + } + + return res; +} + +} diff --git a/src/alias_registry.hh b/src/alias_registry.hh new file mode 100644 index 00000000..97f52ee3 --- /dev/null +++ b/src/alias_registry.hh @@ -0,0 +1,37 @@ +#ifndef alias_registry_hh_INCLUDED +#define alias_registry_hh_INCLUDED + +#include "utils.hh" +#include "safe_ptr.hh" +#include "string.hh" + +#include + +namespace Kakoune +{ + +class AliasRegistry : public SafeCountable +{ +public: + AliasRegistry(AliasRegistry& parent) : m_parent(&parent) {} + void add_alias(String alias, String command); + void remove_alias(const String& alias); + StringView operator[](const String& name) const; + + std::vector aliases_for(StringView command) const; + +private: + friend class GlobalAliases; + AliasRegistry() {} + + safe_ptr m_parent; + std::unordered_map m_aliases; +}; + +class GlobalAliases : public AliasRegistry, public Singleton +{}; + +} + +#endif // alias_registry_hh_INCLUDED + diff --git a/src/buffer.cc b/src/buffer.cc index 0b0dca1e..9bdc76df 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -23,7 +23,8 @@ Buffer::Buffer(String name, Flags flags, std::vector lines, m_fs_timestamp(fs_timestamp), m_hooks(GlobalHooks::instance()), m_options(GlobalOptions::instance()), - m_keymaps(GlobalKeymaps::instance()) + m_keymaps(GlobalKeymaps::instance()), + m_aliases(GlobalAliases::instance()) { BufferManager::instance().register_buffer(*this); m_options.register_watcher(*this); diff --git a/src/buffer.hh b/src/buffer.hh index 70bb59b7..63b2e7cd 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -1,6 +1,7 @@ #ifndef buffer_hh_INCLUDED #define buffer_hh_INCLUDED +#include "alias_registry.hh" #include "coord.hh" #include "flags.hh" #include "hook_manager.hh" @@ -155,6 +156,8 @@ public: const HookManager& hooks() const { return m_hooks; } KeymapManager& keymaps() { return m_keymaps; } const KeymapManager& keymaps() const { return m_keymaps; } + AliasRegistry& aliases() { return m_aliases; } + const AliasRegistry& aliases() const { return m_aliases; } ValueMap& values() const { return m_values; } @@ -217,6 +220,7 @@ private: OptionManager m_options; HookManager m_hooks; KeymapManager m_keymaps; + AliasRegistry m_aliases; // Values are just data holding by the buffer, so it is part of its // observable state diff --git a/src/command_manager.cc b/src/command_manager.cc index 71131048..81e56d16 100644 --- a/src/command_manager.cc +++ b/src/command_manager.cc @@ -1,5 +1,6 @@ #include "command_manager.hh" +#include "alias_registry.hh" #include "assert.hh" #include "context.hh" #include "register_manager.hh" @@ -13,7 +14,7 @@ namespace Kakoune bool CommandManager::command_defined(const String& command_name) const { - return find_command(command_name) != m_commands.end(); + return m_commands.find(command_name) != m_commands.end(); } void CommandManager::register_command(String command_name, @@ -30,13 +31,6 @@ void CommandManager::register_command(String command_name, std::move(completer) }; } -void CommandManager::register_alias(String alias, String command) -{ - kak_assert(not alias.empty()); - kak_assert(command_defined(command)); - m_aliases[alias] = std::move(command); -} - struct parse_error : runtime_error { parse_error(const String& error) @@ -347,10 +341,10 @@ struct command_not_found : runtime_error }; CommandManager::CommandMap::const_iterator -CommandManager::find_command(const String& name) const +CommandManager::find_command(const Context& context, const String& name) const { - auto it = m_aliases.find(name); - const String& cmd_name = it == m_aliases.end() ? name : it->second; + auto alias = context.aliases()[name]; + const String& cmd_name = alias.empty() ? name : alias.str(); return m_commands.find(cmd_name); } @@ -363,7 +357,7 @@ void CommandManager::execute_single_command(CommandParameters params, return; memoryview param_view(params.begin()+1, params.end()); - auto command_it = find_command(params[0]); + auto command_it = find_command(context, params[0]); if (command_it == m_commands.end()) throw command_not_found(params[0]); @@ -443,7 +437,7 @@ void CommandManager::execute(StringView command_line, execute_single_command(params, context, command_coord); } -CommandInfo CommandManager::command_info(StringView command_line) const +CommandInfo CommandManager::command_info(const Context& context, StringView command_line) const { TokenList tokens = parse(command_line); size_t cmd_idx = 0; @@ -458,7 +452,7 @@ CommandInfo CommandManager::command_info(StringView command_line) const tokens[cmd_idx].type() != Token::Type::Raw) return res; - auto cmd = find_command(tokens[cmd_idx].content()); + auto cmd = find_command(context, tokens[cmd_idx].content()); if (cmd == m_commands.end()) return res; @@ -467,11 +461,8 @@ CommandInfo CommandManager::command_info(StringView command_line) const res.second += cmd->second.docstring + "\n"; String aliases; - for (auto& alias : m_aliases) - { - if (alias.second == cmd->first) - aliases += " " + alias.first; - } + for (auto& alias : context.aliases().aliases_for(cmd->first)) + aliases += " " + alias; if (not aliases.empty()) res.second += "Aliases:" + aliases + "\n"; @@ -562,7 +553,7 @@ Completions CommandManager::complete(const Context& context, const String& command_name = tokens[cmd_idx].content(); - auto command_it = find_command(command_name); + auto command_it = find_command(context, command_name); if (command_it == m_commands.end() or not command_it->second.completer) return Completions(); diff --git a/src/command_manager.hh b/src/command_manager.hh index 283fec92..ca397a8f 100644 --- a/src/command_manager.hh +++ b/src/command_manager.hh @@ -65,7 +65,8 @@ public: Completions complete(const Context& context, CompletionFlags flags, StringView command_line, ByteCount cursor_pos); - CommandInfo command_info(StringView command_line) const; + CommandInfo command_info(const Context& context, + StringView command_line) const; bool command_defined(const String& command_name) const; @@ -74,7 +75,6 @@ public: ParameterDesc param_desc, CommandFlags flags = CommandFlags::None, CommandCompleter completer = CommandCompleter()); - void register_alias(String alias, String command); private: void execute_single_command(CommandParameters params, @@ -90,9 +90,9 @@ private: }; using CommandMap = std::unordered_map; CommandMap m_commands; - std::unordered_map m_aliases; - CommandMap::const_iterator find_command(const String& name) const; + CommandMap::const_iterator find_command(const Context& context, + const String& name) const; }; } diff --git a/src/commands.cc b/src/commands.cc index 5a5f2354..9bb6d8e1 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -82,6 +82,8 @@ const ParameterDesc single_optional_name_param{ SwitchMap{}, ParameterDesc::Flags::None, 0, 1 }; +static constexpr auto scopes = { "global", "buffer", "window" }; + struct CommandDesc { const char* name; @@ -511,8 +513,6 @@ HookManager& get_hook_manager(const String& scope, const Context& context) throw runtime_error("error: no such hook container " + scope); } -static constexpr auto scopes = { "global", "buffer", "window" }; - const CommandDesc add_hook_cmd = { "hook", nullptr, @@ -613,14 +613,6 @@ void define_command(const ParametersParser& parser, Context& context) if (parser.has_option("docstring")) docstring = parser.option_value("docstring"); - String alias; - if (parser.has_option("alias")) - { - alias = parser.option_value("alias"); - if (alias.empty()) - throw runtime_error("alias should not be an empty string"); - } - String commands = parser[1]; Command cmd; ParameterDesc desc; @@ -697,8 +689,6 @@ void define_command(const ParametersParser& parser, Context& context) auto& cm = CommandManager::instance(); cm.register_command(cmd_name, cmd, std::move(docstring), desc, flags, completer); - if (not alias.empty()) - cm.register_alias(std::move(alias), cmd_name); } const CommandDesc define_command_cmd = { @@ -709,7 +699,6 @@ const CommandDesc define_command_cmd = { SwitchMap{ { "shell-params", { false, "pass parameters to each shell escape as $0..$N" } }, { "allow-override", { false, "allow overriding an existing command" } }, { "hidden", { false, "do not display the command in completion candidates" } }, - { "alias", { true, "define an alias for this command" } }, { "docstring", { true, "define the documentation string for command" } }, { "file-completion", { false, "complete parameters using filename completion" } }, { "client-completion", { false, "complete parameters using client name completion" } }, @@ -723,6 +712,49 @@ const CommandDesc define_command_cmd = { define_command }; +AliasRegistry& get_aliases(const String& scope, const Context& context) +{ + if (prefix_match("global", scope)) + return GlobalAliases::instance(); + else if (prefix_match("buffer", scope)) + return context.buffer().aliases(); + else if (prefix_match("window", scope)) + return context.window().aliases(); + throw runtime_error("error: no such scope " + scope); +} + +const CommandDesc alias_cmd = { + "alias", + nullptr, + "alias : define a command alias in given scope", + ParameterDesc{SwitchMap{}, ParameterDesc::Flags::None, 3, 3}, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + AliasRegistry& aliases = get_aliases(parser[0], context); + aliases.add_alias(parser[1], parser[2]); + } +}; + +const CommandDesc unalias_cmd = { + "unalias", + nullptr, + "unalias []: remove a command alias in given scope\n" + "if is specified, the alias is removed only if its value is ", + ParameterDesc{SwitchMap{}, ParameterDesc::Flags::None, 2, 3}, + CommandFlags::None, + CommandCompleter{}, + [](const ParametersParser& parser, Context& context) + { + AliasRegistry& aliases = get_aliases(parser[0], context); + if (parser.positional_count() == 3 and + aliases[parser[1]] != parser[2]) + return; + aliases.remove_alias(parser[1]); + } +}; + const CommandDesc echo_cmd = { "echo", nullptr, @@ -1387,7 +1419,7 @@ static void register_command(CommandManager& cm, const CommandDesc& c) { cm.register_command(c.name, c.func, c.docstring, c.params, c.flags, c.completer); if (c.alias) - cm.register_alias(c.alias, c.name); + GlobalAliases::instance().add_alias(c.alias, c.name); } void register_commands() @@ -1414,6 +1446,8 @@ void register_commands() register_command(cm, add_hook_cmd); register_command(cm, rm_hook_cmd); register_command(cm, define_command_cmd); + register_command(cm, alias_cmd); + register_command(cm, unalias_cmd); register_command(cm, echo_cmd); register_command(cm, debug_cmd); register_command(cm, source_cmd); diff --git a/src/context.cc b/src/context.cc index 2ed0c51c..883df5ee 100644 --- a/src/context.cc +++ b/src/context.cc @@ -1,5 +1,6 @@ #include "context.hh" +#include "alias_registry.hh" #include "client.hh" #include "user_interface.hh" #include "register_manager.hh" @@ -80,6 +81,15 @@ KeymapManager& Context::keymaps() const return GlobalKeymaps::instance(); } +AliasRegistry& Context::aliases() const +{ + if (has_window()) + return window().aliases(); + if (has_buffer()) + return buffer().aliases(); + return GlobalAliases::instance(); +} + void Context::set_client(Client& client) { kak_assert(not has_client()); diff --git a/src/context.hh b/src/context.hh index f75e36c7..ad54773f 100644 --- a/src/context.hh +++ b/src/context.hh @@ -14,6 +14,7 @@ class InputHandler; class UserInterface; class DisplayLine; class KeymapManager; +class AliasRegistry; // A Context is used to access non singleton objects for various services // in commands. @@ -60,6 +61,7 @@ public: OptionManager& options() const; HookManager& hooks() const; KeymapManager& keymaps() const; + AliasRegistry& aliases() const; void print_status(DisplayLine status) const; diff --git a/src/main.cc b/src/main.cc index 0dba42f2..6e973411 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,4 +1,5 @@ #include "assert.hh" +#include "alias_registry.hh" #include "buffer.hh" #include "buffer_manager.hh" #include "buffer_utils.hh" @@ -317,6 +318,7 @@ int run_server(StringView session, StringView init_command, GlobalOptions global_options; GlobalHooks global_hooks; GlobalKeymaps global_keymaps; + GlobalAliases global_aliases; ShellManager shell_manager; CommandManager command_manager; BufferManager buffer_manager; @@ -399,6 +401,7 @@ int run_filter(StringView keystr, memoryview files) GlobalOptions global_options; GlobalHooks global_hooks; GlobalKeymaps global_keymaps; + GlobalAliases global_aliases; ShellManager shell_manager; BufferManager buffer_manager; RegisterManager register_manager; diff --git a/src/normal.cc b/src/normal.cc index a7c49b2f..89a92df3 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -361,7 +361,7 @@ void command(Context& context, int) context.ui().info_hide(); if (event == PromptEvent::Change and context.options()["autoinfo"].get() > 0) { - auto info = CommandManager::instance().command_info(cmdline); + auto info = CommandManager::instance().command_info(context, cmdline); Face col = get_face("Information"); CharCoord pos = context.window().dimensions(); pos.column -= 1; diff --git a/src/window.cc b/src/window.cc index a3bafef3..5adf685c 100644 --- a/src/window.cc +++ b/src/window.cc @@ -21,7 +21,8 @@ Window::Window(Buffer& buffer) : m_buffer(&buffer), m_hooks(buffer.hooks()), m_options(buffer.options()), - m_keymaps(buffer.keymaps()) + m_keymaps(buffer.keymaps()), + m_aliases(buffer.aliases()) { InputHandler hook_handler{{ *m_buffer, Selection{} }}; hook_handler.context().set_window(*this); diff --git a/src/window.hh b/src/window.hh index d1e821b6..8a51ad4a 100644 --- a/src/window.hh +++ b/src/window.hh @@ -1,6 +1,7 @@ #ifndef window_hh_INCLUDED #define window_hh_INCLUDED +#include "alias_registry.hh" #include "completion.hh" #include "display_buffer.hh" #include "highlighter_group.hh" @@ -44,6 +45,8 @@ public: const HookManager& hooks() const { return m_hooks; } KeymapManager& keymaps() { return m_keymaps; } const KeymapManager& keymaps() const { return m_keymaps; } + AliasRegistry& aliases() { return m_aliases; } + const AliasRegistry& aliases() const { return m_aliases; } Buffer& buffer() const { return *m_buffer; } @@ -67,6 +70,7 @@ private: HookManager m_hooks; OptionManager m_options; KeymapManager m_keymaps; + AliasRegistry m_aliases; HighlighterGroup m_highlighters; HighlighterGroup m_builtin_highlighters;