Add scoped aliases

aliases are now stored in window, buffer, or globally.
This commit is contained in:
Maxime Coste 2014-10-29 23:22:54 +00:00
parent 502de87697
commit 185b980718
14 changed files with 197 additions and 41 deletions

View File

@ -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 Note that these commands are available in interactive command mode, but are
not that useful in this context. 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 <scope> <alias> <command>
--------------------------------
with +<scope>+ being +global+, +buffer+ or +window+, will define +<alias>+ as
an alias for +<command>+
-------------------------------------
:unalias <scope> <alias> [<expected>]
-------------------------------------
Will remove the given alias in the given scope. If +<expected>+ is specified
the alias will only be removed if its current value is +<expected>+.
FIFO Buffer FIFO Buffer
----------- -----------

48
src/alias_registry.cc Normal file
View File

@ -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<StringView> AliasRegistry::aliases_for(StringView command) const
{
std::vector<StringView> 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;
}
}

37
src/alias_registry.hh Normal file
View File

@ -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 <unordered_map>
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<StringView> aliases_for(StringView command) const;
private:
friend class GlobalAliases;
AliasRegistry() {}
safe_ptr<AliasRegistry> m_parent;
std::unordered_map<String, String> m_aliases;
};
class GlobalAliases : public AliasRegistry, public Singleton<GlobalAliases>
{};
}
#endif // alias_registry_hh_INCLUDED

View File

@ -23,7 +23,8 @@ Buffer::Buffer(String name, Flags flags, std::vector<String> lines,
m_fs_timestamp(fs_timestamp), m_fs_timestamp(fs_timestamp),
m_hooks(GlobalHooks::instance()), m_hooks(GlobalHooks::instance()),
m_options(GlobalOptions::instance()), m_options(GlobalOptions::instance()),
m_keymaps(GlobalKeymaps::instance()) m_keymaps(GlobalKeymaps::instance()),
m_aliases(GlobalAliases::instance())
{ {
BufferManager::instance().register_buffer(*this); BufferManager::instance().register_buffer(*this);
m_options.register_watcher(*this); m_options.register_watcher(*this);

View File

@ -1,6 +1,7 @@
#ifndef buffer_hh_INCLUDED #ifndef buffer_hh_INCLUDED
#define buffer_hh_INCLUDED #define buffer_hh_INCLUDED
#include "alias_registry.hh"
#include "coord.hh" #include "coord.hh"
#include "flags.hh" #include "flags.hh"
#include "hook_manager.hh" #include "hook_manager.hh"
@ -155,6 +156,8 @@ public:
const HookManager& hooks() const { return m_hooks; } const HookManager& hooks() const { return m_hooks; }
KeymapManager& keymaps() { return m_keymaps; } KeymapManager& keymaps() { return m_keymaps; }
const KeymapManager& keymaps() const { 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; } ValueMap& values() const { return m_values; }
@ -217,6 +220,7 @@ private:
OptionManager m_options; OptionManager m_options;
HookManager m_hooks; HookManager m_hooks;
KeymapManager m_keymaps; KeymapManager m_keymaps;
AliasRegistry m_aliases;
// Values are just data holding by the buffer, so it is part of its // Values are just data holding by the buffer, so it is part of its
// observable state // observable state

View File

@ -1,5 +1,6 @@
#include "command_manager.hh" #include "command_manager.hh"
#include "alias_registry.hh"
#include "assert.hh" #include "assert.hh"
#include "context.hh" #include "context.hh"
#include "register_manager.hh" #include "register_manager.hh"
@ -13,7 +14,7 @@ namespace Kakoune
bool CommandManager::command_defined(const String& command_name) const 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, void CommandManager::register_command(String command_name,
@ -30,13 +31,6 @@ void CommandManager::register_command(String command_name,
std::move(completer) }; 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 struct parse_error : runtime_error
{ {
parse_error(const String& error) parse_error(const String& error)
@ -347,10 +341,10 @@ struct command_not_found : runtime_error
}; };
CommandManager::CommandMap::const_iterator 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); auto alias = context.aliases()[name];
const String& cmd_name = it == m_aliases.end() ? name : it->second; const String& cmd_name = alias.empty() ? name : alias.str();
return m_commands.find(cmd_name); return m_commands.find(cmd_name);
} }
@ -363,7 +357,7 @@ void CommandManager::execute_single_command(CommandParameters params,
return; return;
memoryview<String> param_view(params.begin()+1, params.end()); memoryview<String> 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()) if (command_it == m_commands.end())
throw command_not_found(params[0]); throw command_not_found(params[0]);
@ -443,7 +437,7 @@ void CommandManager::execute(StringView command_line,
execute_single_command(params, context, command_coord); 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<false>(command_line); TokenList tokens = parse<false>(command_line);
size_t cmd_idx = 0; size_t cmd_idx = 0;
@ -458,7 +452,7 @@ CommandInfo CommandManager::command_info(StringView command_line) const
tokens[cmd_idx].type() != Token::Type::Raw) tokens[cmd_idx].type() != Token::Type::Raw)
return res; return res;
auto cmd = find_command(tokens[cmd_idx].content()); auto cmd = find_command(context, tokens[cmd_idx].content());
if (cmd == m_commands.end()) if (cmd == m_commands.end())
return res; return res;
@ -467,11 +461,8 @@ CommandInfo CommandManager::command_info(StringView command_line) const
res.second += cmd->second.docstring + "\n"; res.second += cmd->second.docstring + "\n";
String aliases; String aliases;
for (auto& alias : m_aliases) for (auto& alias : context.aliases().aliases_for(cmd->first))
{ aliases += " " + alias;
if (alias.second == cmd->first)
aliases += " " + alias.first;
}
if (not aliases.empty()) if (not aliases.empty())
res.second += "Aliases:" + aliases + "\n"; res.second += "Aliases:" + aliases + "\n";
@ -562,7 +553,7 @@ Completions CommandManager::complete(const Context& context,
const String& command_name = tokens[cmd_idx].content(); 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 if (command_it == m_commands.end() or
not command_it->second.completer) not command_it->second.completer)
return Completions(); return Completions();

View File

@ -65,7 +65,8 @@ public:
Completions complete(const Context& context, CompletionFlags flags, Completions complete(const Context& context, CompletionFlags flags,
StringView command_line, ByteCount cursor_pos); 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; bool command_defined(const String& command_name) const;
@ -74,7 +75,6 @@ public:
ParameterDesc param_desc, ParameterDesc param_desc,
CommandFlags flags = CommandFlags::None, CommandFlags flags = CommandFlags::None,
CommandCompleter completer = CommandCompleter()); CommandCompleter completer = CommandCompleter());
void register_alias(String alias, String command);
private: private:
void execute_single_command(CommandParameters params, void execute_single_command(CommandParameters params,
@ -90,9 +90,9 @@ private:
}; };
using CommandMap = std::unordered_map<String, CommandDescriptor>; using CommandMap = std::unordered_map<String, CommandDescriptor>;
CommandMap m_commands; CommandMap m_commands;
std::unordered_map<String, String> m_aliases;
CommandMap::const_iterator find_command(const String& name) const; CommandMap::const_iterator find_command(const Context& context,
const String& name) const;
}; };
} }

View File

@ -82,6 +82,8 @@ const ParameterDesc single_optional_name_param{
SwitchMap{}, ParameterDesc::Flags::None, 0, 1 SwitchMap{}, ParameterDesc::Flags::None, 0, 1
}; };
static constexpr auto scopes = { "global", "buffer", "window" };
struct CommandDesc struct CommandDesc
{ {
const char* name; 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); throw runtime_error("error: no such hook container " + scope);
} }
static constexpr auto scopes = { "global", "buffer", "window" };
const CommandDesc add_hook_cmd = { const CommandDesc add_hook_cmd = {
"hook", "hook",
nullptr, nullptr,
@ -613,14 +613,6 @@ void define_command(const ParametersParser& parser, Context& context)
if (parser.has_option("docstring")) if (parser.has_option("docstring"))
docstring = parser.option_value("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]; String commands = parser[1];
Command cmd; Command cmd;
ParameterDesc desc; ParameterDesc desc;
@ -697,8 +689,6 @@ void define_command(const ParametersParser& parser, Context& context)
auto& cm = CommandManager::instance(); auto& cm = CommandManager::instance();
cm.register_command(cmd_name, cmd, std::move(docstring), desc, flags, completer); 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 = { 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" } }, SwitchMap{ { "shell-params", { false, "pass parameters to each shell escape as $0..$N" } },
{ "allow-override", { false, "allow overriding an existing command" } }, { "allow-override", { false, "allow overriding an existing command" } },
{ "hidden", { false, "do not display the command in completion candidates" } }, { "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" } }, { "docstring", { true, "define the documentation string for command" } },
{ "file-completion", { false, "complete parameters using filename completion" } }, { "file-completion", { false, "complete parameters using filename completion" } },
{ "client-completion", { false, "complete parameters using client name completion" } }, { "client-completion", { false, "complete parameters using client name completion" } },
@ -723,6 +712,49 @@ const CommandDesc define_command_cmd = {
define_command 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 <scope> <alias> <command>: 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 <scope> <alias> [<expected>]: remove a command alias in given scope\n"
"if <expected> is specified, the alias is removed only if its value is <expected>",
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 = { const CommandDesc echo_cmd = {
"echo", "echo",
nullptr, 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); cm.register_command(c.name, c.func, c.docstring, c.params, c.flags, c.completer);
if (c.alias) if (c.alias)
cm.register_alias(c.alias, c.name); GlobalAliases::instance().add_alias(c.alias, c.name);
} }
void register_commands() void register_commands()
@ -1414,6 +1446,8 @@ void register_commands()
register_command(cm, add_hook_cmd); register_command(cm, add_hook_cmd);
register_command(cm, rm_hook_cmd); register_command(cm, rm_hook_cmd);
register_command(cm, define_command_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, echo_cmd);
register_command(cm, debug_cmd); register_command(cm, debug_cmd);
register_command(cm, source_cmd); register_command(cm, source_cmd);

View File

@ -1,5 +1,6 @@
#include "context.hh" #include "context.hh"
#include "alias_registry.hh"
#include "client.hh" #include "client.hh"
#include "user_interface.hh" #include "user_interface.hh"
#include "register_manager.hh" #include "register_manager.hh"
@ -80,6 +81,15 @@ KeymapManager& Context::keymaps() const
return GlobalKeymaps::instance(); 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) void Context::set_client(Client& client)
{ {
kak_assert(not has_client()); kak_assert(not has_client());

View File

@ -14,6 +14,7 @@ class InputHandler;
class UserInterface; class UserInterface;
class DisplayLine; class DisplayLine;
class KeymapManager; class KeymapManager;
class AliasRegistry;
// A Context is used to access non singleton objects for various services // A Context is used to access non singleton objects for various services
// in commands. // in commands.
@ -60,6 +61,7 @@ public:
OptionManager& options() const; OptionManager& options() const;
HookManager& hooks() const; HookManager& hooks() const;
KeymapManager& keymaps() const; KeymapManager& keymaps() const;
AliasRegistry& aliases() const;
void print_status(DisplayLine status) const; void print_status(DisplayLine status) const;

View File

@ -1,4 +1,5 @@
#include "assert.hh" #include "assert.hh"
#include "alias_registry.hh"
#include "buffer.hh" #include "buffer.hh"
#include "buffer_manager.hh" #include "buffer_manager.hh"
#include "buffer_utils.hh" #include "buffer_utils.hh"
@ -317,6 +318,7 @@ int run_server(StringView session, StringView init_command,
GlobalOptions global_options; GlobalOptions global_options;
GlobalHooks global_hooks; GlobalHooks global_hooks;
GlobalKeymaps global_keymaps; GlobalKeymaps global_keymaps;
GlobalAliases global_aliases;
ShellManager shell_manager; ShellManager shell_manager;
CommandManager command_manager; CommandManager command_manager;
BufferManager buffer_manager; BufferManager buffer_manager;
@ -399,6 +401,7 @@ int run_filter(StringView keystr, memoryview<StringView> files)
GlobalOptions global_options; GlobalOptions global_options;
GlobalHooks global_hooks; GlobalHooks global_hooks;
GlobalKeymaps global_keymaps; GlobalKeymaps global_keymaps;
GlobalAliases global_aliases;
ShellManager shell_manager; ShellManager shell_manager;
BufferManager buffer_manager; BufferManager buffer_manager;
RegisterManager register_manager; RegisterManager register_manager;

View File

@ -361,7 +361,7 @@ void command(Context& context, int)
context.ui().info_hide(); context.ui().info_hide();
if (event == PromptEvent::Change and context.options()["autoinfo"].get<int>() > 0) if (event == PromptEvent::Change and context.options()["autoinfo"].get<int>() > 0)
{ {
auto info = CommandManager::instance().command_info(cmdline); auto info = CommandManager::instance().command_info(context, cmdline);
Face col = get_face("Information"); Face col = get_face("Information");
CharCoord pos = context.window().dimensions(); CharCoord pos = context.window().dimensions();
pos.column -= 1; pos.column -= 1;

View File

@ -21,7 +21,8 @@ Window::Window(Buffer& buffer)
: m_buffer(&buffer), : m_buffer(&buffer),
m_hooks(buffer.hooks()), m_hooks(buffer.hooks()),
m_options(buffer.options()), m_options(buffer.options()),
m_keymaps(buffer.keymaps()) m_keymaps(buffer.keymaps()),
m_aliases(buffer.aliases())
{ {
InputHandler hook_handler{{ *m_buffer, Selection{} }}; InputHandler hook_handler{{ *m_buffer, Selection{} }};
hook_handler.context().set_window(*this); hook_handler.context().set_window(*this);

View File

@ -1,6 +1,7 @@
#ifndef window_hh_INCLUDED #ifndef window_hh_INCLUDED
#define window_hh_INCLUDED #define window_hh_INCLUDED
#include "alias_registry.hh"
#include "completion.hh" #include "completion.hh"
#include "display_buffer.hh" #include "display_buffer.hh"
#include "highlighter_group.hh" #include "highlighter_group.hh"
@ -44,6 +45,8 @@ public:
const HookManager& hooks() const { return m_hooks; } const HookManager& hooks() const { return m_hooks; }
KeymapManager& keymaps() { return m_keymaps; } KeymapManager& keymaps() { return m_keymaps; }
const KeymapManager& keymaps() const { 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; } Buffer& buffer() const { return *m_buffer; }
@ -67,6 +70,7 @@ private:
HookManager m_hooks; HookManager m_hooks;
OptionManager m_options; OptionManager m_options;
KeymapManager m_keymaps; KeymapManager m_keymaps;
AliasRegistry m_aliases;
HighlighterGroup m_highlighters; HighlighterGroup m_highlighters;
HighlighterGroup m_builtin_highlighters; HighlighterGroup m_builtin_highlighters;