Repository navigation
Expand file tree
/
Copy pathmode.lua
More file actions
294 lines (276 loc) · 9.36 KB
/
Copy pathmode.lua
File metadata and controls
294 lines (276 loc) · 9.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
--- The layermode table's functionality can be accessed through the global
--- |Layers.mode| after |Layers.setup| has been called, i.e. by creating a new
--- overlay instance using `Layers.mode.new()`.
---
--- It allows users to define custom modes which overlay the actual Neovim
--- modes. This extends the mechanism of |Layers.map| (which is still used
--- internally) with some additional features:
--- - It allows adding keymaps to the mode without them becoming active
--- immediately. This is done by either using |layermode:add()| and going one
--- by one as the |layermap:set()| would do, or by providing all of them at
--- once using |layermode:keymaps()|.
--- - It supports activating the keymaps using |layermode:activate()|,
--- |layermode:toggle()|, or |layermode:oneshot()|. The last of which will
--- quit the mode after executing any single overlaid keymap.
--- - It supports deactivating the keymaps using |layermode:deactivate()|, or
--- |layermode:toggle()|.
--- - It supports the creation of hooks into the (de-)activation of the mode using
--- |layermode:hook()|
--- - It supports showing a helper display for the keymaps in the mode with the
--- |layermode:show_help()|, |layermode:dismiss_help()|,
--- |layermode:toggle_help()|, and |layermode:auto_show_help()| methods.
---
---@toc_entry Using Layered Modes
---@class layers.mode
---@field _win integer
---@field _maps table<layers.mapmode, layers.keymaps>
---@field _mode_name string?
---@field _hooks layers.hook[]
---@field _active layers.map
---@field opts help_opts
---@field window window_opts
local layermode = {}
layermode.__index = layermode
--- Setup instances with default configs. The provided options will apply to all
--- future instances created with |layermode.new()| (i.e. also through the
--- recommended accessor `Layers.mode.new()`)
---
--- This is usually called by the plugin-wide |Layers.setup()| using the correct
--- sub table of the global |Layers.config| and does not need to be accessed
--- manually.
---
---@param opts? mode_config
---@return layers.mode class
function layermode.setup(opts)
opts = opts or {}
layermode.opts = opts.help
layermode.window = opts.window
return layermode
end
--- Create a new layered mode. The returned instance embeds the currently active
--- global settings and works otherwise fully independently from other
--- instances. Parallel use of multiple instances will come with the possibility
--- of one mode storing an overlaid keymap as an original to be restored, and is
--- thus not recommended.
---
---@toc_entry Layers.mode.new()
---@param name string? mode name, will override the configured help window title
---@return layers.mode instance
function layermode.new(name)
local self = setmetatable({}, layermode)
self._mode_name = name
self._maps = {}
self._hooks = {}
setmetatable(self._maps, {
__index = function(store, mode)
if not rawget(store, mode) then
rawset(store, mode, {})
end
return rawget(store, mode)
end,
})
return self
end
--- Add multiple keymaps to this mode. The `opts` param must be a table with
--- entries per mode, using it's short-name as a key (see the first param to
--- |nvim_set_keymap|). The value of those entries should be a list of keymap
--- tables containing three values.
---@usage >lua
--- mymode:keymaps({
--- n = {
--- {
--- "r",
--- function() vim.cmd("smile") end,
--- { desc = "not replace" },
--- },
--- ...
--- },
--- i = { ... },
--- })
---<
---
--- This method does not replace or remove already present keymaps, it rather
--- just appends them to the internal store, with the later ones taking
--- precedence in case of conflicting `lhs` params.
---
---@toc_entry mymode:keymaps(...)
---@param opts table<layers.mapmode, layers.keymaps>
function layermode:keymaps(opts)
-- avoid direct assign to preserve the metatable of _maps
for mode, maps in pairs(opts) do
for _, map in ipairs(maps) do
table.insert(self._maps[mode], map)
end
end
end
--- Add a single keymap to this mode. This call follows the same rules as
--- |vim.keymap.set| does. It will append to any existing keymaps already
--- stored, with the later ones taking precedence in case of conflicting `lhs`
--- params.
---
---@toc_entry mymode:add(...)
---@param mode layers.mapmodes
---@param lhs layers.lhs
---@param rhs layers.rhs
---@param opts vim.keymap.set.Opts
function layermode:add(mode, lhs, rhs, opts)
---@cast mode string[]
mode = type(mode) == "string" and { mode } or mode
for _, m in ipairs(mode) do
table.insert(self._maps[m], { lhs, rhs, opts })
end
end
--- Activate this layered_mode, bringing the keymaps of this mode into effect by
--- creating a new layered_map and running all hooks.
---
---@toc_entry mymode:activate()
function layermode:activate()
self._active = require("layers.map").new()
for mode, modemaps in pairs(self._maps) do
for _, map in ipairs(modemaps) do
self._active:set(mode, map[1], map[2], map[3])
end
end
for _, hook in ipairs(self._hooks) do
hook(true)
end
end
--- Activate this mode similar to activate, but exit the mode after a single
--- keymap has been used once. This uses a |layermode:hook()| internally.
---
---@toc_entry mymode:oneshot(...)
function layermode:oneshot()
self._active = require("layers.map").new()
for mode, modemaps in pairs(self._maps) do
for _, map in ipairs(modemaps) do
self._active:set(mode, map[1], function()
if type(map[2]) == "function" then
map[2]()
elseif type(map[2]) == "string" then
vim.cmd.normal(map[2])
end
self:deactivate()
end, map[3])
end
end
for _, hook in ipairs(self._hooks) do
hook(true)
end
end
--- Deactivate this layered_mode, clearing the underlying mode and running all
--- hooks.
---
---@toc_entry mymode:deactivate()
function layermode:deactivate()
self._active:clear()
self._active = nil
for _, hook in ipairs(self._hooks) do
hook(false)
end
end
--- Toggle this mode using the activate or deactivate methods, depending on the
--- current status.
---
---@toc_entry mymode:toggle()
function layermode:toggle()
if self._active ~= nil then
self:deactivate()
else
self:activate()
end
end
--- Return the current active status of this mode.
---
---@toc_entry mymode:active()
---@return boolean
function layermode:active()
return self._active ~= nil
end
--- Hook the provided function into the flow, right after the activation or
--- deactivation respectively. Expects a function taking a single boolean (the
--- active status when the hook is run).
---
--- For an example, see |layermode:auto_show_help()|.
---
---@toc_entry mymode:add_hook(...)
---@param hook layers.hook
function layermode:add_hook(hook)
table.insert(self._hooks, hook)
end
--- Automatically show the help window for this mode, depending on the active
--- status. See |layermode:show_help()|. In contrast to the regular `show_help`
--- call, this method takes no params. It is however trivial to recreate the
--- hook used under the hood with some non-global options:
---@toc_entry mymode:auto_show_help()
---@eval return require("mini.doc").afterlines_to_code(require("mini.doc").current.eval_section)
function layermode:auto_show_help()
self:add_hook(function(active)
if active then
self:show_help()
else
self:dismiss_help()
end
end)
end
--- Show a help window for this mode. If the params are omitted, the window will
--- use the global settings from |Layers.setup()|.
---
---@toc_entry mymode:show_help(...)
---@param opts help_opts?
---@param win_config vim.api.keyset.win_config?
---@param win_opts vim.api.keyset.option?
function layermode:show_help(opts, win_config, win_opts)
opts = opts or {}
win_config = win_config or {}
win_opts = win_opts or {}
-- create buffer with opts from our _maps
opts = vim.tbl_deep_extend("force", self.opts, opts)
local buf = vim.api.nvim_create_buf(false, true)
local text = {}
for mode, modemaps in pairs(self._maps) do
if #self._maps > 1 or opts.force_mode_headers then
table.insert(text, mode .. ":")
end
for _, map in ipairs(modemaps) do
table.insert(text, " " .. map[1] .. ": " .. (map[3].desc or opts.missing_desc_string))
end
end
vim.api.nvim_buf_set_lines(buf, 0, -1, true, text)
-- open window with config
win_config = vim.tbl_deep_extend("force", self.window.config, win_config)
win_config = vim.tbl_deep_extend("keep", win_config, {
height = #text,
col = vim.go.columns,
row = vim.go.lines - 1,
})
-- add some text to the title to distinguish between modes
if self._mode_name ~= nil then
win_config.title = self._mode_name
end
self._win = vim.api.nvim_open_win(buf, false, win_config)
-- set additional window options
win_opts = vim.tbl_deep_extend("force", self.window.opts, win_opts)
for option, value in pairs(win_opts) do
vim.api.nvim_set_option_value(option, value, { win = self._win })
end
end
--- Dismiss the help window for this mode if it is shown.
---
---@toc_entry mymode:dismiss_help()
function layermode:dismiss_help()
if self._win ~= nil then
vim.api.nvim_win_close(self._win, true)
self._win = nil
end
end
--- Toggle the help window for this mode.
---
---@toc_entry mymode:toggle_help()
function layermode:toggle_help()
if self._win == nil then
self:show_help()
else
self:dismiss_help()
end
end
return layermode