feat: keymap actions can be parameterized

This commit is contained in:
Steven Arcangeli 2024-06-10 16:44:59 -05:00
parent 18272aba9d
commit 96368e13e9
6 changed files with 321 additions and 66 deletions

View file

@ -184,16 +184,16 @@ require("oil").setup({
keymaps = {
["g?"] = "actions.show_help",
["<CR>"] = "actions.select",
["<C-s>"] = "actions.select_vsplit",
["<C-h>"] = "actions.select_split",
["<C-t>"] = "actions.select_tab",
["<C-s>"] = { "actions.select_split", opts = { vertical = true } },
["<C-h>"] = { "actions.select_split", opts = { horizontal = true } },
["<C-t>"] = { "actions.select_split", opts = { tab = true } },
["<C-p>"] = "actions.preview",
["<C-c>"] = "actions.close",
["<C-l>"] = "actions.refresh",
["-"] = "actions.parent",
["_"] = "actions.open_cwd",
["`"] = "actions.cd",
["~"] = "actions.tcd",
["~"] = { "actions.cd", opts = { scope = "tab" } },
["gs"] = "actions.change_sort",
["gx"] = "actions.open_external",
["g."] = "actions.toggle_hidden",

View file

@ -75,16 +75,16 @@ CONFIG *oil-confi
keymaps = {
["g?"] = "actions.show_help",
["<CR>"] = "actions.select",
["<C-s>"] = "actions.select_vsplit",
["<C-h>"] = "actions.select_split",
["<C-t>"] = "actions.select_tab",
["<C-s>"] = { "actions.select_split", opts = { vertical = true } },
["<C-h>"] = { "actions.select_split", opts = { horizontal = true } },
["<C-t>"] = { "actions.select_split", opts = { tab = true } },
["<C-p>"] = "actions.preview",
["<C-c>"] = "actions.close",
["<C-l>"] = "actions.refresh",
["-"] = "actions.parent",
["_"] = "actions.open_cwd",
["`"] = "actions.cd",
["~"] = "actions.tcd",
["~"] = { "actions.cd", opts = { scope = "tab" } },
["gs"] = "actions.change_sort",
["gx"] = "actions.open_external",
["g."] = "actions.toggle_hidden",
@ -435,38 +435,71 @@ birthtime *column-birthtim
--------------------------------------------------------------------------------
ACTIONS *oil-actions*
These are actions that can be used in the `keymaps` section of config options.
You can also call them directly with
The `keymaps` option in `oil.setup` allow you to create mappings using all the same parameters as |vim.keymap.set|.
>lua
keymaps = {
-- Mappings can be a string
["~"] = "<cmd>edit $HOME<CR>",
-- Mappings can be a function
["gd"] = function()
require("oil").set_columns({ "icon", "permissions", "size", "mtime" })
end,
-- You can pass additional opts to vim.keymap.set by using
-- a table with the mapping as the first element.
["<leader>ff"] = {
function()
require("telescope.builtin").find_files({
cwd = require("oil").get_current_dir()
})
end,
mode = "n",
nowait = true,
desc = "Find files in the current directory"
},
-- Mappings that are a string starting with "actions." will be
-- one of the built-in actions, documented below.
["`"] = "actions.tcd",
-- Some actions have parameters. These are passed in via the `opts` key.
["<leader>:"] = {
"actions.open_cmdline",
opts = {
shorten_path = true,
modify = ":h",
},
desc = "Open the command line with the current directory as an argument",
},
}
Below are the actions that can be used in the `keymaps` section of config
options. You can refer to them as strings (e.g. "actions.<action_name>") or you
can use the functions directly with
`require("oil.actions").action_name.callback()`
add_to_loclist *actions.add_to_loclist*
Adds files in the current oil directory to the location list, keeping the
previous entries.
add_to_qflist *actions.add_to_qflist*
Adds files in the current oil directory to the quickfix list, keeping the
previous entries.
cd *actions.cd*
:cd to the current oil directory
Parameters:
{scope} `nil|"tab"|"win"` Scope of the directory change (e.g. use |:tcd|
or |:lcd|)
{silent} `boolean` Do not show a message when changing directories
change_sort *actions.change_sort*
Change the sort order
Parameters:
{sort} `oil.SortSpec[]` List of columns plus direction (see
|oil.set_sort|) instead of interactive selection
close *actions.close*
Close oil and restore original buffer
copy_entry_filename *actions.copy_entry_filename*
Yank the filename of the entry under the cursor to a register
copy_entry_path *actions.copy_entry_path*
Yank the filepath of the entry under the cursor to a register
open_cmdline *actions.open_cmdline*
Open vim cmdline with current entry as an argument
open_cmdline_dir *actions.open_cmdline_dir*
Open vim cmdline with current directory as an argument
Parameters:
{modify} `string` Modify the path with |fnamemodify()| using this as
the mods argument
{shorten_path} `boolean` Use relative paths when possible
open_cwd *actions.open_cwd*
Open oil in Neovim's current working directory
@ -493,38 +526,47 @@ preview_scroll_up *actions.preview_scroll_u
refresh *actions.refresh*
Refresh current directory list
Parameters:
{force} `boolean` When true, do not prompt user if they will be discarding
changes
select *actions.select*
Open the entry under the cursor
select_split *actions.select_split*
Open the entry under the cursor in a horizontal split
select_tab *actions.select_tab*
Open the entry under the cursor in a new tab
select_vsplit *actions.select_vsplit*
Open the entry under the cursor in a vertical split
send_to_loclist *actions.send_to_loclist*
Sends files in the current oil directory to the location list, replacing the
previous entries.
Parameters:
{close} `boolean` Close the original oil buffer once selection is
made
{horizontal} `boolean` Open the buffer in a horizontal split
{split} `"aboveleft"|"belowright"|"topleft"|"botright"` Split
modifier
{tab} `boolean` Open the buffer in a new tab
{vertical} `boolean` Open the buffer in a vertical split
send_to_qflist *actions.send_to_qflist*
Sends files in the current oil directory to the quickfix list, replacing the
previous entries.
Parameters:
{action} `"r"|"a"` Replace or add to current quickfix list (see
|setqflist-action|)
{target} `"qflist"|"loclist"` The target list to send files to
show_help *actions.show_help*
Show default keymaps
tcd *actions.tcd*
:tcd to the current oil directory
toggle_hidden *actions.toggle_hidden*
Toggle hidden files and directories
toggle_trash *actions.toggle_trash*
Jump to and from the trash for the current directory
yank_entry *actions.yank_entry*
Yank the filepath of the entry under the cursor to a register
Parameters:
{modify} `string` Modify the path with |fnamemodify()| using this as the
mods argument
--------------------------------------------------------------------------------
HIGHLIGHTS *oil-highlights*

View file

@ -4,20 +4,48 @@ local util = require("oil.util")
local M = {}
M.show_help = {
desc = "Show default keymaps",
callback = function()
local config = require("oil.config")
require("oil.keymap_util").show_help(config.keymaps)
end,
desc = "Show default keymaps",
}
M.select = {
desc = "Open the entry under the cursor",
callback = oil.select,
callback = function(opts)
opts = opts or {}
local callback = opts.callback
opts.callback = nil
oil.select(opts, callback)
end,
parameters = {
vertical = {
type = "boolean",
desc = "Open the buffer in a vertical split",
},
horizontal = {
type = "boolean",
desc = "Open the buffer in a horizontal split",
},
split = {
type = '"aboveleft"|"belowright"|"topleft"|"botright"',
desc = "Split modifier",
},
tab = {
type = "boolean",
desc = "Open the buffer in a new tab",
},
close = {
type = "boolean",
desc = "Close the original oil buffer once selection is made",
},
},
}
M.select_vsplit = {
desc = "Open the entry under the cursor in a vertical split",
deprecated = true,
callback = function()
oil.select({ vertical = true })
end,
@ -25,6 +53,7 @@ M.select_vsplit = {
M.select_split = {
desc = "Open the entry under the cursor in a horizontal split",
deprecated = true,
callback = function()
oil.select({ horizontal = true })
end,
@ -32,6 +61,7 @@ M.select_split = {
M.select_tab = {
desc = "Open the entry under the cursor in a new tab",
deprecated = true,
callback = function()
oil.select({ tab = true })
end,
@ -98,11 +128,14 @@ M.close = {
}
---@param cmd string
local function cd(cmd)
---@param silent? boolean
local function cd(cmd, silent)
local dir = oil.get_current_dir()
if dir then
vim.cmd({ cmd = cmd, args = { dir } })
vim.notify(string.format("CWD: %s", dir), vim.log.levels.INFO)
if not silent then
vim.notify(string.format("CWD: %s", dir), vim.log.levels.INFO)
end
else
vim.notify("Cannot :cd; not in a directory", vim.log.levels.WARN)
end
@ -110,13 +143,31 @@ end
M.cd = {
desc = ":cd to the current oil directory",
callback = function()
cd("cd")
callback = function(opts)
opts = opts or {}
local cmd = "cd"
if opts.scope == "tab" then
cmd = "tcd"
elseif opts.scope == "win" then
cmd = "lcd"
end
cd(cmd, opts.silent)
end,
parameters = {
scope = {
type = 'nil|"tab"|"win"',
desc = "Scope of the directory change (e.g. use |:tcd| or |:lcd|)",
},
silent = {
type = "boolean",
desc = "Do not show a message when changing directories",
},
},
}
M.tcd = {
desc = ":tcd to the current oil directory",
deprecated = true,
callback = function()
cd("tcd")
end,
@ -200,8 +251,12 @@ M.open_external = {
return
end
local path = dir .. entry.name
-- TODO use vim.ui.open once this is resolved
-- https://github.com/neovim/neovim/issues/24567
if vim.ui.open then
vim.ui.open(path)
return
end
local cmd, err = get_open_cmd(path)
if not cmd then
vim.notify(string.format("Could not open %s: %s", path, err), vim.log.levels.ERROR)
@ -214,8 +269,9 @@ M.open_external = {
M.refresh = {
desc = "Refresh current directory list",
callback = function()
if vim.bo.modified then
callback = function(opts)
opts = opts or {}
if vim.bo.modified and not opts.force then
local ok, choice = pcall(vim.fn.confirm, "Discard changes?", "No\nYes")
if not ok or choice ~= 2 then
return
@ -226,6 +282,12 @@ M.refresh = {
-- :h CTRL-L-default
vim.cmd.nohlsearch()
end,
parameters = {
force = {
desc = "When true, do not prompt user if they will be discarding changes",
type = "boolean",
},
},
}
local function open_cmdline_with_path(path)
@ -236,7 +298,10 @@ end
M.open_cmdline = {
desc = "Open vim cmdline with current entry as an argument",
callback = function()
callback = function(opts)
opts = vim.tbl_deep_extend("keep", opts or {}, {
shorten_path = true,
})
local config = require("oil.config")
local fs = require("oil.fs")
local entry = oil.get_cursor_entry()
@ -252,13 +317,53 @@ M.open_cmdline = {
if not adapter or not path or adapter.name ~= "files" then
return
end
local fullpath = fs.shorten_path(fs.posix_to_os_path(path) .. entry.name)
local fullpath = fs.posix_to_os_path(path) .. entry.name
if opts.modify then
fullpath = vim.fn.fnamemodify(fullpath, opts.modify)
end
if opts.shorten_path then
fullpath = fs.shorten_path(fullpath)
end
open_cmdline_with_path(fullpath)
end,
parameters = {
modify = {
desc = "Modify the path with |fnamemodify()| using this as the mods argument",
type = "string",
},
shorten_path = {
desc = "Use relative paths when possible",
type = "boolean",
},
},
}
M.yank_entry = {
desc = "Yank the filepath of the entry under the cursor to a register",
callback = function(opts)
opts = opts or {}
local entry = oil.get_cursor_entry()
local dir = oil.get_current_dir()
if not entry or not dir then
return
end
local path = dir .. entry.name
if opts.modify then
path = vim.fn.fnamemodify(path, opts.modify)
end
vim.fn.setreg(vim.v.register, path)
end,
parameters = {
modify = {
desc = "Modify the path with |fnamemodify()| using this as the mods argument",
type = "string",
},
},
}
M.copy_entry_path = {
desc = "Yank the filepath of the entry under the cursor to a register",
deprecated = true,
callback = function()
local entry = oil.get_cursor_entry()
local dir = oil.get_current_dir()
@ -271,6 +376,7 @@ M.copy_entry_path = {
M.copy_entry_filename = {
desc = "Yank the filename of the entry under the cursor to a register",
deprecated = true,
callback = function()
local entry = oil.get_cursor_entry()
if not entry then
@ -282,6 +388,7 @@ M.copy_entry_filename = {
M.open_cmdline_dir = {
desc = "Open vim cmdline with current directory as an argument",
deprecated = true,
callback = function()
local fs = require("oil.fs")
local dir = oil.get_current_dir()
@ -293,7 +400,14 @@ M.open_cmdline_dir = {
M.change_sort = {
desc = "Change the sort order",
callback = function()
callback = function(opts)
opts = opts or {}
if opts.sort then
oil.set_sort(opts.sort)
return
end
local sort_cols = { "name", "size", "atime", "mtime", "ctime", "birthtime" }
vim.ui.select(sort_cols, { prompt = "Sort by", kind = "oil_sort_col" }, function(col)
if not col then
@ -315,6 +429,12 @@ M.change_sort = {
)
end)
end,
parameters = {
sort = {
type = "oil.SortSpec[]",
desc = "List of columns plus direction (see |oil.set_sort|) instead of interactive selection",
},
},
}
M.toggle_trash = {
@ -348,16 +468,31 @@ M.toggle_trash = {
M.send_to_qflist = {
desc = "Sends files in the current oil directory to the quickfix list, replacing the previous entries.",
callback = function()
util.send_to_quickfix({
callback = function(opts)
opts = vim.tbl_deep_extend("keep", opts or {}, {
target = "qflist",
mode = "r",
action = "r",
})
util.send_to_quickfix({
target = opts.target,
action = opts.action,
})
end,
parameters = {
target = {
type = '"qflist"|"loclist"',
desc = "The target list to send files to",
},
action = {
type = '"r"|"a"',
desc = "Replace or add to current quickfix list (see |setqflist-action|)",
},
},
}
M.add_to_qflist = {
desc = "Adds files in the current oil directory to the quickfix list, keeping the previous entries.",
deprecated = true,
callback = function()
util.send_to_quickfix({
target = "qflist",
@ -368,6 +503,7 @@ M.add_to_qflist = {
M.send_to_loclist = {
desc = "Sends files in the current oil directory to the location list, replacing the previous entries.",
deprecated = true,
callback = function()
util.send_to_quickfix({
target = "loclist",
@ -378,6 +514,7 @@ M.send_to_loclist = {
M.add_to_loclist = {
desc = "Adds files in the current oil directory to the location list, keeping the previous entries.",
deprecated = true,
callback = function()
util.send_to_quickfix({
target = "loclist",
@ -395,6 +532,8 @@ M._get_actions = function()
table.insert(ret, {
name = name,
desc = action.desc,
deprecated = action.deprecated,
parameters = action.parameters,
})
end
end

View file

@ -58,16 +58,16 @@ local default_config = {
keymaps = {
["g?"] = "actions.show_help",
["<CR>"] = "actions.select",
["<C-s>"] = "actions.select_vsplit",
["<C-h>"] = "actions.select_split",
["<C-t>"] = "actions.select_tab",
["<C-s>"] = { "actions.select_split", opts = { vertical = true } },
["<C-h>"] = { "actions.select_split", opts = { horizontal = true } },
["<C-t>"] = { "actions.select_split", opts = { tab = true } },
["<C-p>"] = "actions.preview",
["<C-c>"] = "actions.close",
["<C-l>"] = "actions.refresh",
["-"] = "actions.parent",
["_"] = "actions.open_cwd",
["`"] = "actions.cd",
["~"] = "actions.tcd",
["~"] = { "actions.cd", opts = { scope = "tab" } },
["gs"] = "actions.change_sort",
["gx"] = "actions.open_external",
["g."] = "actions.toggle_hidden",

View file

@ -10,10 +10,14 @@ local M = {}
---@return string|nil mode
local function resolve(rhs)
if type(rhs) == "string" and vim.startswith(rhs, "actions.") then
return resolve(actions[vim.split(rhs, ".", { plain = true })[2]])
local action_name = vim.split(rhs, ".", { plain = true })[2]
local action = actions[action_name]
assert(action, "Unknown action name: " .. action_name)
return resolve(action)
elseif type(rhs) == "table" then
local opts = vim.deepcopy(rhs)
local callback = opts.callback
-- We support passing in a `callback` key, or using the 1 index as the rhs of the keymap
local callback = resolve(opts.callback or opts[1])
local mode = opts.mode
if type(rhs.callback) == "string" then
local action_opts, action_mode
@ -21,8 +25,24 @@ local function resolve(rhs)
opts = vim.tbl_extend("keep", opts, action_opts)
mode = mode or action_mode
end
-- remove all the keys that we can't pass as options to `vim.keymap.set`
opts.callback = nil
opts.mode = nil
opts[1] = nil
opts.deprecated = nil
opts.parameters = nil
if opts.opts and type(callback) == "function" then
local callback_args = opts.opts
opts.opts = nil
local orig_callback = callback
callback = function()
---@diagnostic disable-next-line: redundant-parameter
orig_callback(callback_args)
end
end
return callback, opts, mode
else
return rhs, {}

View file

@ -2,7 +2,7 @@ import os
import os.path
import re
from dataclasses import dataclass, field
from typing import List
from typing import Any, Dict, List
from nvim_doc_tools import (
LuaParam,
@ -13,7 +13,6 @@ from nvim_doc_tools import (
indent,
leftright,
parse_directory,
parse_functions,
read_nvim_json,
read_section,
render_md_api2,
@ -228,21 +227,76 @@ def get_highlights_vimdoc() -> "VimdocSection":
return section
def load_params(params: Dict[str, Any]) -> List[LuaParam]:
ret = []
for name, data in sorted(params.items()):
ret.append(LuaParam(name, data["type"], data["desc"]))
return ret
def get_actions_vimdoc() -> "VimdocSection":
section = VimdocSection("Actions", "oil-actions", ["\n"])
section.body.append(
"""The `keymaps` option in `oil.setup` allow you to create mappings using all the same parameters as |vim.keymap.set|.
>lua
keymaps = {
-- Mappings can be a string
["~"] = "<cmd>edit $HOME<CR>",
-- Mappings can be a function
["gd"] = function()
require("oil").set_columns({ "icon", "permissions", "size", "mtime" })
end,
-- You can pass additional opts to vim.keymap.set by using
-- a table with the mapping as the first element.
["<leader>ff"] = {
function()
require("telescope.builtin").find_files({
cwd = require("oil").get_current_dir()
})
end,
mode = "n",
nowait = true,
desc = "Find files in the current directory"
},
-- Mappings that are a string starting with "actions." will be
-- one of the built-in actions, documented below.
["`"] = "actions.tcd",
-- Some actions have parameters. These are passed in via the `opts` key.
["<leader>:"] = {
"actions.open_cmdline",
opts = {
shorten_path = true,
modify = ":h",
},
desc = "Open the command line with the current directory as an argument",
},
}
"""
)
section.body.append("\n")
section.body.extend(
wrap(
"""These are actions that can be used in the `keymaps` section of config options. You can also call them directly with `require("oil.actions").action_name.callback()`"""
"""Below are the actions that can be used in the `keymaps` section of config options. You can refer to them as strings (e.g. "actions.<action_name>") or you can use the functions directly with `require("oil.actions").action_name.callback()`"""
)
)
section.body.append("\n")
actions = read_nvim_json('require("oil.actions")._get_actions()')
actions.sort(key=lambda a: a["name"])
for action in actions:
if action.get("deprecated"):
continue
name = action["name"]
desc = action["desc"]
section.body.append(leftright(name, f"*actions.{name}*"))
section.body.extend(wrap(desc, 4))
params = action.get("parameters")
if params:
section.body.append("\n")
section.body.append(" Parameters:\n")
section.body.extend(
format_vimdoc_params(load_params(params), LuaTypes(), 6)
)
section.body.append("\n")
return section