mirror of
https://github.com/harivansh-afk/forge.nvim.git
synced 2026-04-15 08:03:44 +00:00
feat(picker): add multi-backend picker abstraction
Problem: all pickers were tightly coupled to fzf-lua via ANSI strings and fzf-specific action tables, making it impossible to use telescope or snacks.nvim. Solution: introduce `forge.picker` dispatcher with `fzf`, `telescope`, and `snacks` backends. Format functions now return `forge.Segment[]` instead of ANSI strings. `pickers.lua` builds backend-agnostic `forge.PickerEntry[]` and delegates to `forge.picker.pick()`. Backend auto-detection tries fzf-lua, snacks, telescope in order. Commits, branches, and worktree pickers remain fzf-only with graceful fallback.
This commit is contained in:
parent
354c5000c0
commit
fa7cab89af
6 changed files with 826 additions and 599 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@class forge.Config
|
---@class forge.Config
|
||||||
|
---@field picker 'fzf-lua'|'telescope'|'snacks'|'auto'
|
||||||
---@field ci forge.CIConfig
|
---@field ci forge.CIConfig
|
||||||
---@field sources table<string, forge.SourceConfig>
|
---@field sources table<string, forge.SourceConfig>
|
||||||
---@field keys forge.KeysConfig|false
|
---@field keys forge.KeysConfig|false
|
||||||
|
|
@ -83,6 +84,7 @@ local M = {}
|
||||||
|
|
||||||
---@type forge.Config
|
---@type forge.Config
|
||||||
local DEFAULTS = {
|
local DEFAULTS = {
|
||||||
|
picker = 'auto',
|
||||||
ci = { lines = 10000 },
|
ci = { lines = 10000 },
|
||||||
sources = {},
|
sources = {},
|
||||||
keys = {
|
keys = {
|
||||||
|
|
@ -538,15 +540,25 @@ local function extract_author(entry, field)
|
||||||
return tostring(v or '')
|
return tostring(v or '')
|
||||||
end
|
end
|
||||||
|
|
||||||
local function hl(group, text)
|
---@param secs integer
|
||||||
local utils = require('fzf-lua.utils')
|
---@return string
|
||||||
return utils.ansi_from_hl(group, text)
|
local function format_duration(secs)
|
||||||
|
if secs < 0 then
|
||||||
|
secs = 0
|
||||||
|
end
|
||||||
|
if secs >= 3600 then
|
||||||
|
return ('%dh%dm'):format(math.floor(secs / 3600), math.floor(secs % 3600 / 60))
|
||||||
|
end
|
||||||
|
if secs >= 60 then
|
||||||
|
return ('%dm%ds'):format(math.floor(secs / 60), secs % 60)
|
||||||
|
end
|
||||||
|
return ('%ds'):format(secs)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry table
|
---@param entry table
|
||||||
---@param fields table
|
---@param fields table
|
||||||
---@param show_state boolean
|
---@param show_state boolean
|
||||||
---@return string
|
---@return forge.Segment[]
|
||||||
function M.format_pr(entry, fields, show_state)
|
function M.format_pr(entry, fields, show_state)
|
||||||
local display = M.config().display
|
local display = M.config().display
|
||||||
local icons = display.icons
|
local icons = display.icons
|
||||||
|
|
@ -555,7 +567,7 @@ function M.format_pr(entry, fields, show_state)
|
||||||
local title = entry[fields.title] or ''
|
local title = entry[fields.title] or ''
|
||||||
local author = extract_author(entry, fields.author)
|
local author = extract_author(entry, fields.author)
|
||||||
local age = relative_time(entry[fields.created_at])
|
local age = relative_time(entry[fields.created_at])
|
||||||
local prefix = ''
|
local segments = {}
|
||||||
if show_state then
|
if show_state then
|
||||||
local state = (entry[fields.state] or ''):lower()
|
local state = (entry[fields.state] or ''):lower()
|
||||||
local icon, group
|
local icon, group
|
||||||
|
|
@ -566,20 +578,22 @@ function M.format_pr(entry, fields, show_state)
|
||||||
else
|
else
|
||||||
icon, group = icons.closed, 'ForgeClosed'
|
icon, group = icons.closed, 'ForgeClosed'
|
||||||
end
|
end
|
||||||
prefix = hl(group, icon) .. ' '
|
table.insert(segments, { icon, group })
|
||||||
|
table.insert(segments, { ' ' })
|
||||||
end
|
end
|
||||||
return prefix
|
table.insert(segments, { ('#%-5s'):format(num), 'ForgeNumber' })
|
||||||
.. hl('ForgeNumber', ('#%-5s'):format(num))
|
table.insert(segments, { ' ' .. pad_or_truncate(title, widths.title) .. ' ' })
|
||||||
.. ' '
|
table.insert(segments, {
|
||||||
.. pad_or_truncate(title, widths.title)
|
pad_or_truncate(author, widths.author) .. (' %3s'):format(age),
|
||||||
.. ' '
|
'ForgeDim',
|
||||||
.. hl('ForgeDim', pad_or_truncate(author, widths.author) .. (' %3s'):format(age))
|
})
|
||||||
|
return segments
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry table
|
---@param entry table
|
||||||
---@param fields table
|
---@param fields table
|
||||||
---@param show_state boolean
|
---@param show_state boolean
|
||||||
---@return string
|
---@return forge.Segment[]
|
||||||
function M.format_issue(entry, fields, show_state)
|
function M.format_issue(entry, fields, show_state)
|
||||||
local display = M.config().display
|
local display = M.config().display
|
||||||
local icons = display.icons
|
local icons = display.icons
|
||||||
|
|
@ -588,7 +602,7 @@ function M.format_issue(entry, fields, show_state)
|
||||||
local title = entry[fields.title] or ''
|
local title = entry[fields.title] or ''
|
||||||
local author = extract_author(entry, fields.author)
|
local author = extract_author(entry, fields.author)
|
||||||
local age = relative_time(entry[fields.created_at])
|
local age = relative_time(entry[fields.created_at])
|
||||||
local prefix = ''
|
local segments = {}
|
||||||
if show_state then
|
if show_state then
|
||||||
local state = (entry[fields.state] or ''):lower()
|
local state = (entry[fields.state] or ''):lower()
|
||||||
local icon, group
|
local icon, group
|
||||||
|
|
@ -597,18 +611,20 @@ function M.format_issue(entry, fields, show_state)
|
||||||
else
|
else
|
||||||
icon, group = icons.closed, 'ForgeClosed'
|
icon, group = icons.closed, 'ForgeClosed'
|
||||||
end
|
end
|
||||||
prefix = hl(group, icon) .. ' '
|
table.insert(segments, { icon, group })
|
||||||
|
table.insert(segments, { ' ' })
|
||||||
end
|
end
|
||||||
return prefix
|
table.insert(segments, { ('#%-5s'):format(num), 'ForgeNumber' })
|
||||||
.. hl('ForgeNumber', ('#%-5s'):format(num))
|
table.insert(segments, { ' ' .. pad_or_truncate(title, widths.title) .. ' ' })
|
||||||
.. ' '
|
table.insert(segments, {
|
||||||
.. pad_or_truncate(title, widths.title)
|
pad_or_truncate(author, widths.author) .. (' %3s'):format(age),
|
||||||
.. ' '
|
'ForgeDim',
|
||||||
.. hl('ForgeDim', pad_or_truncate(author, widths.author) .. (' %3s'):format(age))
|
})
|
||||||
|
return segments
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param check table
|
---@param check table
|
||||||
---@return string
|
---@return forge.Segment[]
|
||||||
function M.format_check(check)
|
function M.format_check(check)
|
||||||
local display = M.config().display
|
local display = M.config().display
|
||||||
local icons = display.icons
|
local icons = display.icons
|
||||||
|
|
@ -632,23 +648,18 @@ function M.format_check(check)
|
||||||
local ok_s, ts = pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.startedAt)
|
local ok_s, ts = pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.startedAt)
|
||||||
local ok_e, te = pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.completedAt)
|
local ok_e, te = pcall(vim.fn.strptime, '%Y-%m-%dT%H:%M:%SZ', check.completedAt)
|
||||||
if ok_s and ok_e and ts > 0 and te > 0 then
|
if ok_s and ok_e and ts > 0 and te > 0 then
|
||||||
local secs = te - ts
|
elapsed = format_duration(te - ts)
|
||||||
if secs >= 60 then
|
|
||||||
elapsed = ('%dm%ds'):format(math.floor(secs / 60), secs % 60)
|
|
||||||
else
|
|
||||||
elapsed = ('%ds'):format(secs)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return hl(group, icon)
|
return {
|
||||||
.. ' '
|
{ icon, group },
|
||||||
.. pad_or_truncate(name, widths.name)
|
{ ' ' .. pad_or_truncate(name, widths.name) .. ' ' },
|
||||||
.. ' '
|
{ elapsed, 'ForgeDim' },
|
||||||
.. hl('ForgeDim', elapsed)
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param run forge.CIRun
|
---@param run forge.CIRun
|
||||||
---@return string
|
---@return forge.Segment[]
|
||||||
function M.format_run(run)
|
function M.format_run(run)
|
||||||
local display = M.config().display
|
local display = M.config().display
|
||||||
local icons = display.icons
|
local icons = display.icons
|
||||||
|
|
@ -670,19 +681,18 @@ function M.format_run(run)
|
||||||
local age = relative_time(run.created_at)
|
local age = relative_time(run.created_at)
|
||||||
if run.branch ~= '' then
|
if run.branch ~= '' then
|
||||||
local name_w = widths.name - widths.branch + 10
|
local name_w = widths.name - widths.branch + 10
|
||||||
return hl(group, icon)
|
return {
|
||||||
.. ' '
|
{ icon, group },
|
||||||
.. pad_or_truncate(run.name, name_w)
|
{ ' ' .. pad_or_truncate(run.name, name_w) .. ' ' },
|
||||||
.. ' '
|
{ pad_or_truncate(run.branch, widths.branch), 'ForgeBranch' },
|
||||||
.. hl('ForgeBranch', pad_or_truncate(run.branch, widths.branch))
|
{ ' ' .. ('%-6s'):format(event) .. ' ' .. age, 'ForgeDim' },
|
||||||
.. ' '
|
}
|
||||||
.. hl('ForgeDim', ('%-6s'):format(event) .. ' ' .. age)
|
|
||||||
end
|
end
|
||||||
return hl(group, icon)
|
return {
|
||||||
.. ' '
|
{ icon, group },
|
||||||
.. pad_or_truncate(run.name, widths.name)
|
{ ' ' .. pad_or_truncate(run.name, widths.name) .. ' ' },
|
||||||
.. ' '
|
{ ('%-6s'):format(event) .. ' ' .. age, 'ForgeDim' },
|
||||||
.. hl('ForgeDim', ('%-6s'):format(event) .. ' ' .. age)
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param checks table[]
|
---@param checks table[]
|
||||||
|
|
@ -715,6 +725,9 @@ function M.config()
|
||||||
cfg.keys = false
|
cfg.keys = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
vim.validate('forge.picker', cfg.picker, function(v)
|
||||||
|
return v == 'auto' or v == 'fzf-lua' or v == 'telescope' or v == 'snacks'
|
||||||
|
end, "'auto', 'fzf-lua', 'telescope', or 'snacks'")
|
||||||
vim.validate('forge.sources', cfg.sources, 'table')
|
vim.validate('forge.sources', cfg.sources, 'table')
|
||||||
vim.validate('forge.keys', cfg.keys, function(v)
|
vim.validate('forge.keys', cfg.keys, function(v)
|
||||||
return v == false or type(v) == 'table'
|
return v == false or type(v) == 'table'
|
||||||
|
|
|
||||||
76
lua/forge/picker/fzf.lua
Normal file
76
lua/forge/picker/fzf.lua
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local fzf_args = (vim.env.FZF_DEFAULT_OPTS or '')
|
||||||
|
:gsub('%-%-bind=[^%s]+', '')
|
||||||
|
:gsub('%-%-color=[^%s]+', '')
|
||||||
|
|
||||||
|
---@param key string
|
||||||
|
---@return string
|
||||||
|
local function to_fzf_key(key)
|
||||||
|
if key == '<cr>' then
|
||||||
|
return 'default'
|
||||||
|
end
|
||||||
|
local result = key:gsub('<c%-(%a)>', function(ch)
|
||||||
|
return 'ctrl-' .. ch:lower()
|
||||||
|
end)
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param segments forge.Segment[]
|
||||||
|
---@return string
|
||||||
|
local function render(segments)
|
||||||
|
local utils = require('fzf-lua.utils')
|
||||||
|
local parts = {}
|
||||||
|
for _, seg in ipairs(segments) do
|
||||||
|
if seg[2] then
|
||||||
|
table.insert(parts, utils.ansi_from_hl(seg[2], seg[1]))
|
||||||
|
else
|
||||||
|
table.insert(parts, seg[1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param opts forge.PickerOpts
|
||||||
|
function M.pick(opts)
|
||||||
|
local cfg = require('forge').config()
|
||||||
|
local keys = cfg.keys
|
||||||
|
if keys == false then
|
||||||
|
keys = {}
|
||||||
|
end
|
||||||
|
local bindings = keys[opts.picker_name] or {}
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
for i, entry in ipairs(opts.entries) do
|
||||||
|
lines[i] = ('%d\t%s'):format(i, render(entry.display))
|
||||||
|
end
|
||||||
|
|
||||||
|
local fzf_actions = {}
|
||||||
|
for _, def in ipairs(opts.actions) do
|
||||||
|
local key = def.name == 'default' and '<cr>' or bindings[def.name]
|
||||||
|
if key then
|
||||||
|
fzf_actions[to_fzf_key(key)] = function(selected)
|
||||||
|
if not selected[1] then
|
||||||
|
def.fn(nil)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local idx = tonumber(selected[1]:match('^(%d+)'))
|
||||||
|
def.fn(idx and opts.entries[idx] or nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require('fzf-lua').fzf_exec(lines, {
|
||||||
|
fzf_args = fzf_args,
|
||||||
|
prompt = opts.prompt or '',
|
||||||
|
fzf_opts = {
|
||||||
|
['--ansi'] = '',
|
||||||
|
['--no-multi'] = '',
|
||||||
|
['--with-nth'] = '2..',
|
||||||
|
['--delimiter'] = '\t',
|
||||||
|
},
|
||||||
|
actions = fzf_actions,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
80
lua/forge/picker/init.lua
Normal file
80
lua/forge/picker/init.lua
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@alias forge.Segment {[1]: string, [2]: string?}
|
||||||
|
|
||||||
|
---@class forge.PickerEntry
|
||||||
|
---@field display forge.Segment[]
|
||||||
|
---@field value any
|
||||||
|
---@field ordinal string?
|
||||||
|
|
||||||
|
---@class forge.PickerActionDef
|
||||||
|
---@field name string
|
||||||
|
---@field fn fun(entry: forge.PickerEntry?)
|
||||||
|
|
||||||
|
---@class forge.PickerOpts
|
||||||
|
---@field prompt string?
|
||||||
|
---@field entries forge.PickerEntry[]
|
||||||
|
---@field actions forge.PickerActionDef[]
|
||||||
|
---@field picker_name string
|
||||||
|
|
||||||
|
---@type table<string, string>
|
||||||
|
local backends = {
|
||||||
|
['fzf-lua'] = 'forge.picker.fzf',
|
||||||
|
telescope = 'forge.picker.telescope',
|
||||||
|
snacks = 'forge.picker.snacks',
|
||||||
|
}
|
||||||
|
|
||||||
|
---@return string
|
||||||
|
local function detect()
|
||||||
|
local cfg = require('forge').config()
|
||||||
|
local name = cfg.picker or 'auto'
|
||||||
|
if name ~= 'auto' then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
if pcall(require, 'fzf-lua') then
|
||||||
|
return 'fzf-lua'
|
||||||
|
end
|
||||||
|
if pcall(require, 'snacks') then
|
||||||
|
return 'snacks'
|
||||||
|
end
|
||||||
|
if pcall(require, 'telescope') then
|
||||||
|
return 'telescope'
|
||||||
|
end
|
||||||
|
return 'fzf-lua'
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param entry forge.PickerEntry
|
||||||
|
---@return string
|
||||||
|
function M.ordinal(entry)
|
||||||
|
if entry.ordinal then
|
||||||
|
return entry.ordinal
|
||||||
|
end
|
||||||
|
local parts = {}
|
||||||
|
for _, seg in ipairs(entry.display) do
|
||||||
|
table.insert(parts, seg[1])
|
||||||
|
end
|
||||||
|
return table.concat(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return string
|
||||||
|
function M.backend()
|
||||||
|
return detect()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param opts forge.PickerOpts
|
||||||
|
function M.pick(opts)
|
||||||
|
local name = detect()
|
||||||
|
local mod_path = backends[name]
|
||||||
|
if not mod_path then
|
||||||
|
vim.notify('[forge]: unknown picker backend: ' .. name, vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local ok, backend = pcall(require, mod_path)
|
||||||
|
if not ok then
|
||||||
|
vim.notify('[forge]: picker backend ' .. name .. ' not available', vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
backend.pick(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
64
lua/forge/picker/snacks.lua
Normal file
64
lua/forge/picker/snacks.lua
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param opts forge.PickerOpts
|
||||||
|
function M.pick(opts)
|
||||||
|
local Snacks = require('snacks')
|
||||||
|
local picker_mod = require('forge.picker')
|
||||||
|
|
||||||
|
local cfg = require('forge').config()
|
||||||
|
local keys = cfg.keys
|
||||||
|
if keys == false then
|
||||||
|
keys = {}
|
||||||
|
end
|
||||||
|
local bindings = keys[opts.picker_name] or {}
|
||||||
|
|
||||||
|
local items = {}
|
||||||
|
for i, entry in ipairs(opts.entries) do
|
||||||
|
items[i] = {
|
||||||
|
idx = i,
|
||||||
|
text = picker_mod.ordinal(entry),
|
||||||
|
value = entry,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local snacks_actions = {}
|
||||||
|
local input_keys = {}
|
||||||
|
local list_keys = {}
|
||||||
|
for _, def in ipairs(opts.actions) do
|
||||||
|
local key = def.name == 'default' and '<cr>' or bindings[def.name]
|
||||||
|
if key then
|
||||||
|
local action_name = 'forge_' .. def.name
|
||||||
|
snacks_actions[action_name] = function(picker)
|
||||||
|
local item = picker:current()
|
||||||
|
picker:close()
|
||||||
|
def.fn(item and item.value or nil)
|
||||||
|
end
|
||||||
|
if key == '<cr>' then
|
||||||
|
snacks_actions['confirm'] = snacks_actions[action_name]
|
||||||
|
else
|
||||||
|
-- selene: allow(mixed_table)
|
||||||
|
input_keys[key] = { action_name, mode = { 'i', 'n' } }
|
||||||
|
list_keys[key] = action_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Snacks.picker({
|
||||||
|
items = items,
|
||||||
|
prompt = opts.prompt,
|
||||||
|
format = function(item)
|
||||||
|
local ret = {}
|
||||||
|
for _, seg in ipairs(item.value.display) do
|
||||||
|
table.insert(ret, { seg[1], seg[2] or 'Normal' })
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end,
|
||||||
|
actions = snacks_actions,
|
||||||
|
win = {
|
||||||
|
input = { keys = input_keys },
|
||||||
|
list = { keys = list_keys },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
69
lua/forge/picker/telescope.lua
Normal file
69
lua/forge/picker/telescope.lua
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param opts forge.PickerOpts
|
||||||
|
function M.pick(opts)
|
||||||
|
local pickers = require('telescope.pickers')
|
||||||
|
local finders = require('telescope.finders')
|
||||||
|
local conf = require('telescope.config').values
|
||||||
|
local actions = require('telescope.actions')
|
||||||
|
local action_state = require('telescope.actions.state')
|
||||||
|
local picker_mod = require('forge.picker')
|
||||||
|
|
||||||
|
local cfg = require('forge').config()
|
||||||
|
local keys = cfg.keys
|
||||||
|
if keys == false then
|
||||||
|
keys = {}
|
||||||
|
end
|
||||||
|
local bindings = keys[opts.picker_name] or {}
|
||||||
|
|
||||||
|
local finder = finders.new_table({
|
||||||
|
results = opts.entries,
|
||||||
|
entry_maker = function(entry)
|
||||||
|
return {
|
||||||
|
value = entry,
|
||||||
|
ordinal = picker_mod.ordinal(entry),
|
||||||
|
display = function(tbl)
|
||||||
|
local text = ''
|
||||||
|
local hl_list = {}
|
||||||
|
for _, seg in ipairs(tbl.value.display) do
|
||||||
|
local start = #text
|
||||||
|
text = text .. seg[1]
|
||||||
|
if seg[2] then
|
||||||
|
table.insert(hl_list, { { start, #text }, seg[2] })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return text, hl_list
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
pickers
|
||||||
|
.new({}, {
|
||||||
|
prompt_title = (opts.prompt or ''):gsub('[>%s]+$', ''),
|
||||||
|
finder = finder,
|
||||||
|
sorter = conf.generic_sorter({}),
|
||||||
|
attach_mappings = function(prompt_bufnr, map)
|
||||||
|
for _, def in ipairs(opts.actions) do
|
||||||
|
local key = def.name == 'default' and '<cr>' or bindings[def.name]
|
||||||
|
if key then
|
||||||
|
local function action_fn()
|
||||||
|
local entry = action_state.get_selected_entry()
|
||||||
|
actions.close(prompt_bufnr)
|
||||||
|
def.fn(entry and entry.value or nil)
|
||||||
|
end
|
||||||
|
if key == '<cr>' then
|
||||||
|
actions.select_default:replace(action_fn)
|
||||||
|
else
|
||||||
|
map('i', key, action_fn)
|
||||||
|
map('n', key, action_fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
:find()
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue