Skip to content

Commit 36aec13

Browse files
authored
feat: keymap(<plugin>).set lazy-loading API (#133)
* tests: case for rhs defined in keys spec triggered * feat: `keymap(<plugin>).set` lazy-loading API
1 parent 525d029 commit 36aec13

File tree

6 files changed

+269
-0
lines changed

6 files changed

+269
-0
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,33 @@ require("lz.n").load(plugins)
165165

166166
[^3]: This is equivalent to `lazy.nvim`'s `VeryLazy` event.
167167

168+
### `keymap(<plugin>).set`
169+
170+
To provide a familiar UX that is as close as possible to the built-in Neovim experience,
171+
`lz.n` has a helper function that lets you lazy-load plugins with keymap
172+
triggers using the same signature as [`:h vim.keymap.set()`](https://neovim.io/doc/user/lua.html#vim.keymap.set()).
173+
174+
Examples:
175+
176+
```lua
177+
-- You can pass in a plugin spec or a plugin's name.
178+
local keymap = require("lz.n").keymap({
179+
"telescope.nvim",
180+
cmd = "Telescope",
181+
before = function()
182+
-- ...
183+
end,
184+
})
185+
-- Now you can create keymaps that will load the plugin using
186+
-- the same UX as vim.keymap.set().
187+
keymap.set("n", "<leader>tp", function()
188+
require("telescope.builtin").find_files()
189+
end)
190+
keymap.set("n", "<leader>tg", function()
191+
require("telescope.builtin").live_grep()
192+
end)
193+
```
194+
168195
### Plugin dependencies
169196

170197
This library does not provide a `lz.n.PluginSpec` field like `lazy.nvim`'s `dependencies`.

doc/lz.n.txt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,49 @@ M.register_handler({handler}) *M.register_handler*
8383
(boolean) success
8484

8585

86+
M.keymap({plugin}) *M.keymap*
87+
Creates an equivalent to |vim.keymap| that will load a plugin when a keymap
88+
created with `keymap.set` is triggered.
89+
This may be useful if you have lots of keymaps defined using `vim.keymap.set`.
90+
91+
Examples:
92+
93+
Load a plugin by name.
94+
95+
>lua
96+
local lz = require("lz.n")
97+
local keymap = lz.keymap("foo")
98+
keymap.set("n", "<leader>f", function() end, {}) -- Will load foo when invoked.
99+
<
100+
101+
Load a plugin with a |lz.n.PluginSpec|.
102+
103+
>lua
104+
local lz = require("lz.n")
105+
local keymap = lz.keymap({
106+
"bar",
107+
before = function()
108+
-- ...
109+
end,
110+
})
111+
keymap.set("n", "<leader>b", function() end, {}) -- Will load bar when invoked.
112+
<
113+
114+
115+
Parameters: ~
116+
{plugin} (string|lz.n.PluginSpec) The plugin to load (name or spec).
117+
118+
Returns: ~
119+
(lz.n.keymap)
120+
121+
122+
lz.n.keymap *lz.n.keymap*
123+
124+
Fields: ~
125+
{set} (fun(mode:string|string[],lhs:string,rhs:string|function,opts:vim.keymap.set.Opts))
126+
The same signature as |vim.keymap.set()|
127+
128+
86129
==============================================================================
87130
lz.n type definitions *lz.n.types*
88131

lua/lz/n/handler/keys.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ local function parse_keys_spec(value, mode)
2323
return ret
2424
end
2525

26+
---@param keys lz.n.Keys
27+
local function is_nop(keys)
28+
return type(keys.rhs) == "string" and (keys.rhs == "" or keys.rhs:lower() == "<nop>")
29+
end
30+
2631
---@param value string|lz.n.KeysSpec
2732
---@return lz.n.Keys[]
2833
local function parse(value)
@@ -42,6 +47,7 @@ end
4247
local state = require("lz.n.handler.state").new()
4348

4449
---@type lz.n.KeysHandler
50+
---@diagnostic disable-next-line: missing-fields
4551
local M = {
4652
spec_field = "keys",
4753
---@param keys_spec? string|string[]|lz.n.KeysSpec[]
@@ -117,6 +123,9 @@ local function add_keys(keys)
117123

118124
---@param buf? number
119125
local function add(buf)
126+
if is_nop(keys) then
127+
return set(keys, buf)
128+
end
120129
vim.keymap.set(keys.mode, lhs, function()
121130
-- always delete the mapping immediately to prevent recursive mappings
122131
del(keys)

lua/lz/n/init.lua

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,89 @@ M.register_handler = function(handler)
9999
return require("lz.n.handler").register_handler(handler)
100100
end
101101

102+
---Creates an equivalent to |vim.keymap| that will load a plugin when a keymap
103+
---created with `keymap.set` is triggered.
104+
---This may be useful if you have lots of keymaps defined using `vim.keymap.set`.
105+
---
106+
---Examples:
107+
---
108+
---Load a plugin by name.
109+
---
110+
--->lua
111+
---local lz = require("lz.n")
112+
---local keymap = lz.keymap("foo")
113+
---keymap.set("n", "<leader>f", function() end, {}) -- Will load foo when invoked.
114+
---<
115+
---
116+
---Load a plugin with a |lz.n.PluginSpec|.
117+
---
118+
--->lua
119+
---local lz = require("lz.n")
120+
---local keymap = lz.keymap({
121+
--- "bar",
122+
--- before = function()
123+
--- -- ...
124+
--- end,
125+
---})
126+
---keymap.set("n", "<leader>b", function() end, {}) -- Will load bar when invoked.
127+
---<
128+
---
129+
---@param plugin string | lz.n.PluginSpec The plugin to load (name or spec).
130+
---@return lz.n.keymap
131+
M.keymap = function(plugin)
132+
local keymap = {
133+
---@param mode string|string[] Mode "short-name" (see |nvim_set_keymap()|), or a list thereof.
134+
---@param lhs string Left-hand side |{lhs}| of the mapping.
135+
---@param rhs string|function Right-hand side |{rhs}| of the mapping, can be a Lua function.
136+
---@param opts? vim.keymap.set.Opts
137+
set = function(mode, lhs, rhs, opts)
138+
opts = opts or {}
139+
local spec = vim.tbl_deep_extend("force", opts, {
140+
[1] = lhs,
141+
[2] = rhs,
142+
mode = mode,
143+
})
144+
---@cast spec lz.n.KeysSpec
145+
local h = require("lz.n.handler.keys")
146+
---@type lz.n.Plugin
147+
local plugin_
148+
if type(plugin) == "string" then
149+
local name = plugin
150+
---@diagnostic disable-next-line: cast-local-type
151+
plugin_ = M.lookup(name)
152+
if plugin_ then
153+
plugin_ = vim.deepcopy(plugin_)
154+
else
155+
plugin_ = {
156+
name = name,
157+
}
158+
end
159+
else
160+
local name = plugin[1]
161+
---@diagnostic disable-next-line: cast-local-type
162+
plugin_ = M.lookup(name)
163+
if plugin_ then
164+
plugin_ = vim.tbl_deep_extend("force", plugin_, plugin)
165+
else
166+
---@diagnostic disable-next-line: cast-local-type
167+
plugin_ = vim.deepcopy(plugin)
168+
---@diagnostic disable-next-line: inject-field
169+
plugin_.name = name
170+
plugin_[1] = nil
171+
---@cast plugin_ lz.n.Plugin
172+
end
173+
end
174+
plugin_.keys = nil
175+
h.parse(plugin_, { spec })
176+
h.add(plugin_)
177+
end,
178+
}
179+
return keymap
180+
end
181+
182+
---@class lz.n.keymap
183+
---
184+
---The same signature as |vim.keymap.set()|
185+
---@field set fun(mode: string|string[], lhs: string, rhs: string|function, opts: vim.keymap.set.Opts)
186+
102187
return M

spec/keymap_set_spec.lua

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
local loaded_plugin
2+
3+
---@diagnostic disable: invisible
4+
vim.g.lz_n = {
5+
load = function(plugin)
6+
loaded_plugin = plugin
7+
end,
8+
}
9+
local lz_n = require("lz.n")
10+
11+
---@param lhs string
12+
local function feedkeys(lhs)
13+
local feed = vim.api.nvim_replace_termcodes("<Ignore>" .. lhs, true, true, true)
14+
vim.api.nvim_feedkeys(feed, "ix", false)
15+
vim.api.nvim_feedkeys(feed, "x", false)
16+
end
17+
18+
describe("keymap('<plugin>').set", function()
19+
it("extends pending plugins", function()
20+
local foo_loaded = false
21+
lz_n.load({
22+
"foo",
23+
cmd = "Foo",
24+
before = function()
25+
foo_loaded = true
26+
end,
27+
})
28+
assert.False(foo_loaded)
29+
local lhs = "<leader>f"
30+
local invoked = false
31+
lz_n.keymap("foo").set("n", lhs, function()
32+
invoked = true
33+
end, {})
34+
feedkeys(lhs)
35+
assert.True(foo_loaded)
36+
assert.True(invoked)
37+
end)
38+
it("works without prior 'load'", function()
39+
loaded_plugin = nil
40+
local lhs = "<leader>b"
41+
lz_n.keymap("bar").set("n", lhs, function() end, {})
42+
feedkeys(lhs)
43+
assert.are_equal("bar", loaded_plugin)
44+
end)
45+
it("loads plugin spec", function()
46+
local loaded = false
47+
local lhs = "<leader>c"
48+
lz_n.keymap({
49+
"cat",
50+
load = function()
51+
loaded = true
52+
end,
53+
}).set("n", lhs, function() end, {})
54+
feedkeys(lhs)
55+
assert.True(loaded)
56+
end)
57+
it("can set multiple keymaps", function()
58+
loaded_plugin = nil
59+
local a_invoked = false
60+
local b_invoked = false
61+
local lhs_a = "<leader>ta"
62+
local lhs_b = "<leader>tb"
63+
local load_count = 0
64+
local keymap = lz_n.keymap({
65+
"bat",
66+
load = function()
67+
loaded_plugin = "bat"
68+
load_count = load_count + 1
69+
end,
70+
})
71+
keymap.set("n", lhs_a, function()
72+
a_invoked = true
73+
end, {})
74+
keymap.set("n", lhs_b, function()
75+
b_invoked = true
76+
end, {})
77+
feedkeys(lhs_a)
78+
feedkeys(lhs_b)
79+
assert.True(a_invoked)
80+
assert.True(b_invoked)
81+
assert.are_equal(1, load_count)
82+
assert.are_equal("bat", loaded_plugin)
83+
end)
84+
end)

spec/keys_spec.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,25 @@ describe("handlers.keys", function()
8383
assert.True(triggered)
8484
loader._load = orig_load
8585
end)
86+
it("Locally created keymaps are triggered", function()
87+
local triggered = false
88+
local lhs = "<leader>xz"
89+
---@type lz.n.KeysSpec
90+
local keys_spec = {
91+
lhs,
92+
function()
93+
triggered = true
94+
end,
95+
}
96+
---@type lz.n.Plugin
97+
local plugin = {
98+
name = "xz",
99+
}
100+
keys.parse(plugin, { keys_spec })
101+
keys.add(plugin)
102+
local feed = vim.api.nvim_replace_termcodes("<Ignore>" .. lhs, true, true, true)
103+
vim.api.nvim_feedkeys(feed, "ix", false)
104+
vim.api.nvim_feedkeys(feed, "x", false)
105+
assert.True(triggered)
106+
end)
86107
end)

0 commit comments

Comments
 (0)