Skip to content

Commit

Permalink
Lua plugin registering
Browse files Browse the repository at this point in the history
Used to proved `map`.

[ci skip]
  • Loading branch information
KillTheMule committed Jan 17, 2019
1 parent ea7504c commit 87452ad
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 0 deletions.
73 changes: 73 additions & 0 deletions runtime/doc/if_lua.txt
Expand Up @@ -148,6 +148,66 @@ lua/charblob.lua: >
encode = charblob_encode,
}
------------------------------------------------------------------------------
The plugin table *lua-plugin_table*

To create a new plugin, call |vim.helpers.new_plugin|, passing the plugin
name. It returns a table representing the plugin, containing some information
on the plugin and some methods on the plugin table itself.

Information in the plugin table *lua-plugin_table_information*

Key:~
`ns` The namespace for the addon. It is uniquely identified by the name
given when calling |vim.helpers.new_plugin|. Don't change it, and don't
make assumptions about its type.
`name` The name of the plugin, as passed to |vim.helpers.new_plugin|.

Methods in the plugin table *lua-plugin_table_functions*

plugin:map({keys}, {fn}) *lua-plugin:map*
Provides an interface to |map-cmd|. Note the mode-agnostic nature. If
you want to provide functionality only for certain modes, check
for the mode in the function body. Otherwise, use
|lua-plugin:map_table|.

Parameters:~
{keys} String. The keys to be mapped.
{fn} Function. The lua function to be executed by the keys.

plugin:map({args}) *lua-plugin:map_table*
Provides an interface to |map|.

Parameters:~
{args} Table. Mandatory keys are `keys` and `fn` as in
|lua-plugin:map|. Optionally, set any of `buffer`, `nowait`,
`silent` or `unique` to `true` to use it as a special
argument as in |:map-arguments|. You can set `recursive` to
`true` to get a recursive mapping. Set `mode` to the mode
you want the mapping to work in (use `n` for normal, `v` for
visual, `s` for select, `o` for operator-pending, `i for
insert, `c` for command-line, `l` for a language mapping, `t`
for terminal, or the combinations `nvos`, `vs`, `ic`; trying
a descriptive name for single modes might also work). Set
`is_cmd` to `false` explicitely to disable creating a command
mapping.

plugin:unmap({keys}, {mode}) *lua-plugin:unmap*
Provides an interface to |:unmap|. Only keys having been mapped from
this plugin via |lua-plugin:map| can be unmapped.

Parameters:~
{keys} The keys to be unmapped.
{mode} String, optional. The mode to unmap the keys in. Possible
values as described in |lua-plugin:map_table|.

Return:~
A table containing tables with 2 entries. The first entry of each
such subtable is a char representing the mode, the second is the
function that was mapped for this mode. Returns `nil` if unmapping
did not work (that is, they keys haven't been mapped from this
plugin before).

==============================================================================
Commands *lua-commands*

Expand Down Expand Up @@ -293,7 +353,20 @@ vim.types *lua-vim.types*
values corresponding to `vim.types.float`, `vim.types.array` and
`vim.types.dictionary` will not change or that `vim.types` table will
only contain values for these three types.
------------------------------------------------------------------------------
vim.helpers.* helper functions

This modules provides lua-only helper functions to assist in writing lua
plugins.

vim.helpers.new_plugin({string}) *vim.helpers.new_plugin*
Declare a new plugin namespace.

Parameters:~
{string} The name of the plugin

Return:~
A table representing the plugin. See |lua-plugin_table|.
==============================================================================
The luaeval function *lua-luaeval* *lua-eval*
*luaeval()*
Expand Down
13 changes: 13 additions & 0 deletions runtime/lua/helpers/init.lua
@@ -0,0 +1,13 @@
local nvim_eval = vim.api.nvim_eval

local new_plugin = require('helpers.plugin').new_plugin

if not vim.helpers then vim.helpers = {} end

vim.helpers.new_plugin = new_plugin

local module ={
new_plugin = new_plugin,
}

return module
20 changes: 20 additions & 0 deletions runtime/lua/helpers/plugin/init.lua
@@ -0,0 +1,20 @@
-- luacheck: globals unpack vim.api
local nvim = vim.api
local map = require('helpers.plugin.map').map
local unmap = require('helpers.plugin.map').unmap
local functions = require('helpers.plugin.map').functions

local function new_plugin(name)
-- TODO(KillTheMule): Check assumptions about subsequent calls of this
local ns = nvim.nvim_create_namespace(name)

-- Should be redundant after the comment above has ben ascertained
assert(functions[ns] == nil, "Namspace "..tostring(ns).." already exists")

functions[ns] = {}
return { ns = ns, name = name, map = map, unmap = unmap }
end

return {
new_plugin = new_plugin,
}
216 changes: 216 additions & 0 deletions runtime/lua/helpers/plugin/map.lua
@@ -0,0 +1,216 @@
-- luacheck: globals unpack vim.api
local nvim = vim.api
local command = nvim.nvim_command

local functions = {}

local function register_fn(ns, keys, shortmode, fn)
if not functions[ns] then
functions[ns] = {}
end

for sshortmode in shortmode:gmatch"." do
if not functions[ns][sshortmode] then
functions[ns][sshortmode] = {}
else
assert(functions[ns][sshortmode][keys] == nil,
"Keys already mapped from namespace "..tostring(ns))
end
functions[ns][sshortmode][keys] = fn
end
end

local function map_to_ns(keys, ns, opts, mapcmd, is_cmd, shortmode)
local keys_rhs = keys:gsub("<", "<lt>")
local sshortmode = shortmode:sub(1,1)
local rhs = "lua require('helpers.plugin.map').functions["..
tostring(ns).."]['"..sshortmode.."']['"..keys_rhs.."']()"
rhs = (is_cmd and " <Cmd>"..rhs) or rhs
opts = (opts and opts.." ") or ""
mapcmd = (mapcmd and mapcmd.." ") or ""

command(mapcmd..opts..keys..rhs.."<CR>")
end

local shortmode_cmds = {
[true] = {
n = "nmap",
vs = "vmap",
s = "smap",
v = "xmap",
o = "omap",
ic = "map!",
i = "imap",
l = "lmap",
c = "cmap",
t = "tmap",
},
[false] = {
n = "nnoremap",
vs = "vnoremap",
s = "snoremap",
v = "xnoremap",
o = "onoremap",
ic = "noremap!",
i = "inoremap",
l = "lnoremap",
c = "cnoremap",
t = "tnoremap",
}
}

local function try_get_shortmode(mode)
local shortmode

mode = mode and mode:lower()

if mode == "normal" then
shortmode = "n"
elseif mode:find("vis") then
shortmode = "v"
elseif mode:find("ins") then
shortmode = "i"
elseif mode:find("com") then
shortmode = "c"
elseif mode:find("sel") then
shortmode = "s"
elseif mode:find("op") then
shortmode = "o"
elseif mode:find("lang") then
shortmode = "l"
elseif mode:find("term") then
shortmode = "t"
else
shortmode = mode
end

return shortmode
end

local function get_mapcmd(mode, recursive)
local mapcmd, shortmode

if mode == nil and recursive then
mapcmd, shortmode = "map", "nvos"
elseif mode == nil and not recursive then
mapcmd, shortmode = "noremap", "nvos"
else
shortmode = try_get_shortmode(mode)
mapcmd = shortmode_cmds[recursive][shortmode]
end
assert(mapcmd ~= nil,
"Can't find mapping command for mode '"..tostring(mode).."'")

return mapcmd, shortmode
end

local allowed_map_opts = { buffer = true, nowait = true, silent = true,
unique = true }
local function destructure_map_args(table)
for k, _ in pairs(table) do
assert(allowed_map_opts[k] or k == "fn" or k == "keys" or k == "mode"
or k == "is_cmd" or k == "recursive",
"Key "..tostring(k).." not allowed in function map!")
end

assert(type(table.fn) == "function", "'fn' mandatory funtion argument to map")
assert(type(table.keys) == "string", "'keys' mandatory string argument to map")
if table.is_cmd ~= nil then
assert(type(table.is_cmd) == "boolean",
"'is_cmd' optional boolean argument to map")
end
if table.mode ~= nil then
assert(type(table.mode) == "string",
"'mode' optional string argument to map")
end
if table.recursive ~= nil then
assert(type(table.recursive) == "boolean",
"'recursive' optional boolean argument to map")
end

local opts = ""
for opt, _ in pairs(allowed_map_opts) do
if table[opt] then
opts = opts.."<"..opt..">"
end
end

local is_cmd
-- Assume if is_cmd isn't explicitely false, then a cmd mapping was requested
if table.is_cmd == nil or table.is_cmd == true then
is_cmd = true
else
is_cmd = false
end

local recursive
if table.recursive == nil or table.recursive == false then
recursive = false
else
recursive = true
end

local mapcmd, shortmode = get_mapcmd(table.mode, recursive)

return table.keys, table.fn, opts, mapcmd, is_cmd, shortmode
end

local function map(self, arg1, arg2)
local firststring = (type(arg1) == "string")
local secondfun = (type(arg2) == "function")
local two_args = firststring and secondfun
local one_arg = (type(arg1) == "table")
assert(two_args or one_arg, "Must pass (string, function) or (table) as args")

-- we need that table[ns] is the same as luaeval "table["..tostring(ns).."]"
-- Not sure how to ascertain that, so let's just throw out an error if the
-- returned type of nvim_create_namespace changes
assert(type(self.ns) == "number", "Namespace must be a number")

local ns = self.ns
local name = self.name
local keys, fn, opts, mapcmd, is_cmd

if one_arg then
keys, fn, opts, mapcmd, is_cmd, shortmode = destructure_map_args(arg1)
else
keys, fn, opts, mapcmd, is_cmd, shortmode = arg1, arg2, nil, "map", true, "nvos"
end

register_fn(ns, keys, shortmode, fn)

map_to_ns(keys, ns, opts, mapcmd, is_cmd, shortmode)
end

local function unmap(self, keys, mode)
assert(type(keys) == "string", "'keys' mandatory string argument to unmap")

if mode ~= nil then
assert(type(mode) == "string", "'mode' string argument to unmap")
end

local mapcmd, shortmode = get_mapcmd(mode, true)
local unmapcmd = mapcmd:gsub("map", "unmap").." "
local mapped_fns = {}

for sshortmode in shortmode:gmatch"." do
local f = functions[self.ns][sshortmode][keys]
functions[self.ns][sshortmode][keys] = nil
if f then
table.insert(mapped_fns, { sshortmode, f })
end
end

if #mapped_fns == 0 then
return nil
else
command(unmapcmd..keys)
return mapped_fns
end
end

return {
unmap = unmap,
map = map,
functions = functions,
}
47 changes: 47 additions & 0 deletions runtime/lua/testplugin.lua
@@ -0,0 +1,47 @@
local nvim = vim.api
local helpers = require('helpers')

local plugin = helpers.new_plugin("testplugin")

local function stuff()
local curbuf = nvim.nvim_get_current_buf()
nvim.nvim_buf_set_lines(curbuf, 0, 0, true, {"Testplugin"})
end

local function stuff2()
local curbuf = nvim.nvim_get_current_buf()
nvim.nvim_buf_set_lines(curbuf, 0, 0, true, {"Testplugin2"})
end

local function init()
plugin:map("<F2>", stuff)
plugin:map{ buffer = true, keys = "<", fn = stuff2 }
end

local function maperr1()
local err, errmsg = pcall(plugin.map, plugin,
{ buffer = true, keys = "<", fn = stuff2, x = 1 })
return err, errmsg
end

local function maperr2()
local err, errmsg = pcall(plugin.map, plugin,
{ buffer = true, keys = a, fn = stuff2 })
return err, errmsg
end

local function clear_f2()
plugin:unmap("<F2>")
end

local function clear_f3()
return plugin:unmap("<F3>")
end

return {
init = init,
maperr1 = maperr1,
maperr2 = maperr2,
clear_f2 = clear_f2,
clear_f3 = clear_f3,
}

0 comments on commit 87452ad

Please sign in to comment.