8000 mini.nvim/lua/mini/clue.lua at main · cathyprime/mini.nvim · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
1998 lines (1785 loc) · 77.3 KB

clue.lua

File metadata and controls

1998 lines (1785 loc) · 77.3 KB
--- *mini.clue* Show next key clues
--- *MiniClue*
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Features:
--- - Implement custom key query process to reach target key combination:
--- - Starts after customizable opt-in triggers (mode + keys).
---
--- - Each key press narrows down set of possible targets.
--- Pressing `<BS>` removes previous user entry.
--- Pressing `<Esc>` or `<C-c>` leads to an early stop.
--- Doesn't depend on 'timeoutlen' and has basic support for 'langmap'.
---
--- - Ends when there is at most one target left or user pressed `<CR>`.
--- Results into emulating pressing all query keys plus possible postkeys.
---
--- - Show window (after configurable delay) with clues. It lists available
--- next keys along with their descriptions (auto generated from descriptions
--- present keymaps and user-supplied clues; preferring the former).
---
--- - Configurable "postkeys" for key combinations - keys which will be emulated
--- after combination is reached during key query process.
---
--- - Provide customizable sets of clues for common built-in keys/concepts:
--- - `g` key.
--- - `z` key.
--- - Window commands.
--- - Built-in completion.
--- - Marks.
--- - Registers.
---
--- - Lua functions to disable/enable triggers globally or per buffer.
---
--- For more details see:
--- - |MiniClue-key-query-process|.
--- - |MiniClue-examples|.
--- - |MiniClue.config|.
--- - |MiniClue.gen_clues|.
---
--- Notes:
--- - Works on all supported versions but using Neovim>=0.9 is recommended.
---
--- - There is no functionality to create mappings while defining clues.
--- This is done to clearly separate these two different actions.
--- The best suggested practice is to manually create mappings with
--- descriptions (`desc` field in options), as they will be automatically
--- used inside clue window.
---
--- - Triggers are implemented as special buffer-local mappings. This leads to
--- several caveats:
--- - They will override same regular buffer-local mappings and have
--- precedence over global one.
---
--- Example: having set `<C-w>` as Normal mode trigger means that
--- there should not be another `<C-w>` mapping.
---
--- - They need to be the latest created buffer-local mappings or they will
--- not function properly. Most common indicator of this is that some
--- mapping starts to work only after clue window is shown.
---
--- Example: `g` is set as Normal mode trigger, but `gcc` from |mini.comment|
--- doesn't work right away. This is probably because there are some
--- other buffer-local mappings starting with `g` which were created after
--- mapping for `g` trigger. Most common places for this are in LSP server's
--- `on_attach` or during tree-sitter start in buffer.
---
--- To check if trigger is the most recent buffer-local mapping, execute
--- `:<mode-char>map <trigger-keys>` (like `:nmap g` for previous example).
--- Mapping for trigger should be the first listed.
---
--- This module makes the best effort to work out of the box and cover
--- most common cases, but it is not foolproof. The solution here is to
--- ensure that triggers are created after making all buffer-local mappings:
--- run either |MiniClue.setup()| or |MiniClue.ensure_buf_triggers()|.
---
--- - Descriptions from existing mappings take precedence over user-supplied
--- clues. This is to ensure that information shown in clue window is as
--- relevant as possible. To add/customize description of an already existing
--- mapping, use |MiniClue.set_mapping_desc()|.
---
--- - Due to technical difficulties, there is no foolproof support for
--- Operator-pending mode triggers (like `a`/`i` from |mini.ai|):
--- - Doesn't work as part of a command in "temporary Normal mode" (like
--- after |i_CTRL-O|) due to implementation difficulties.
--- - Can have unexpected behavior with custom operators.
---
--- - Has (mostly solved) issues with macros:
--- - All triggers are disabled during macro recording due to technical
--- reasons.
--- - The `@` and `Q` keys are specially mapped inside |MiniClue.setup()|
--- to temporarily disable triggers.
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.clue').setup({})` (replace
--- `{}` with your `config` table). It will create global Lua table `MiniClue`
--- which you can use for scripting or manually (with `:lua MiniClue.*`).
---
--- Config table **needs to have triggers configured**, none is set up by default.
---
--- See |MiniClue.config| for available config settings.
---
--- You can override runtime config settings (like clues or window options)
--- locally to a buffer inside `vim.b.miniclue_config` which should have same
--- structure as `MiniClue.config`. See |mini.nvim-buffer-local-config| for
--- more details.
---
--- # Comparisons ~
---
--- - 'folke/which-key.nvim':
--- - Both have the same main goal: show available next keys along with
--- their customizable descriptions.
--- - Has different UI and content layout.
--- - Allows creating mappings inside its configuration, while this module
--- doesn't have this by design (to clearly separate two different tasks).
--- - Doesn't allow creating submodes, while this module does (via `postkeys`).
---
--- - 'anuvyklack/hydra.nvim':
--- - Both allow creating submodes: state which starts at certain key
--- combination; treats some keys differently; ends after `<Esc>`.
--- - Doesn't show information about available next keys (outside of
--- submodes), while that is this module's main goal.
---
--- # Highlight groups ~
---
--- * `MiniClueBorder` - window border.
--- * `MiniClueDescGroup` - group description in clue window.
--- * `MiniClueDescSingle` - single target description in clue window.
--- * `MiniClueNextKey` - next key label in clue window.
--- * `MiniClueNextKeyWithPostkeys` - next key label with postkeys in clue window.
--- * `MiniClueSeparator` - separator in clue window.
--- * `MiniClueTitle` - window title.
---
--- To change any highlight group, modify it directly with |:highlight|.
---
--- # Disabling ~
---
--- To disable creating triggers, set `vim.g.miniclue_disable` (globally) or
--- `vim.b.miniclue_disable` (for a buffer) to `true`. Considering high number
--- of different scenarios and customization intentions, writing exact rules
--- for disabling module's functionality is left to user. See
--- |mini.nvim-disabling-recipes| for common recipes.
--- # Key query process ~
---
--- ## General info ~
---
--- This module implements custom key query process imitating a usual built-in
--- mechanism of user pressing keys in order to execute a mapping. General idea
--- is the same: narrow down key combinations until the target is reached.
---
--- Main goals of its existence are:
---
--- - Allow reaching certain mappings be independent of 'timeoutlen'. That is,
--- there is no fixed timeout after which currently typed keys are executed.
---
--- - Enable automated showing of next key clues after user-supplied delay
--- (also independent of 'timeoutlen').
---
--- - Allow emulating configurable key presses after certain key combination is
--- reached. This granular control allows creating so called "submodes".
--- See more at |MiniClue-examples-submodes|.
---
--- This process is primarily designed for nested `<Leader>` mappings in Normal
--- mode but works in all other main modes: Visual, Insert, Operator-pending
--- (with caveats; no foolproof guarantees), Command-line, Terminal.
---
--- ## Lifecycle ~
---
--- - Key query process starts when user types a trigger: certain keys in certain
--- mode. Those keys are put into key query as a single user input. All possible
--- mode key combinations are filtered to ones starting with the trigger keys.
---
--- Note: trigger is implemented as a regular mapping, so if it has at least
--- two keys, they should be pressed within 'timeoutlen' milliseconds.
---
--- - Wait (indefinitely) for user to press a key. Advance depending on the key:
---
--- - Special key:
---
--- - If `<Esc>` or `<C-c>`, stop the process without any action.
---
--- - If `<CR>`, stop the process and execute current key query, meaning
--- emulate (with |nvim_feedkeys()|) user pressing those keys.
---
--- - If `<BS>`, remove previous user input from the query. If query becomes
--- empty, stop the process without any action.
---
--- - If a key for scrolling clue window (`scroll_down` / `scroll_up`
--- in `config.window`; `<C-d>` / `<C-u>` by default), scroll clue window
--- and wait for the next user key.
--- Note: if clue window is not shown, treated as a not special key.
---
--- - Not special key. Add key to the query while filtering all available
--- key combinations to start with the current key query. Advance:
---
--- - If there is a single available key combination matching current
--- key query, execute it.
---
--- - If there is no key combinations starting with the current query,
--- execute it. This, for instance, allows a seamless execution of
--- operators in presence of a longer key combinations. Example: with
--- `g` as trigger in Normal mode and available mappings `gc` / `gcc`
--- (like from |mini.comment|), this allows typing `gcip` to comment
--- current paragraph, although there are no key combinations
--- starting with `gci`.
---
--- - Otherwise wait for the new user key press.
---
--- ## Clue window ~
---
--- After initiating key query process and after each key press, a timer is
--- started to show a clue window: floating window with information about
--- available next keys along with their descriptions. Note: if window is
--- already shown, its content is updated right away.
---
--- Clues can have these types:
---
--- - "Terminal next key": when pressed, will lead to query execution.
---
--- - "Terminal next key with postkeys": when pressed, will lead to query
--- execution plus some configured postkeys.
---
--- - "Group next key": when pressed, will narrow down available key combinations
--- and wait for another key press. Note: can have configured description
--- (inside `config.clues`) or it will be auto generated based on the number of
--- available key combinations.
---@tag MiniClue-key-query-process
--- # Full starter example ~
---
--- If not sure where to start, try this example with all provided clues from
--- this module plus all |<Leader>| mappings in Normal and Visual modes: >lua
---
--- local miniclue = require('mini.clue')
--- miniclue.setup({
--- triggers = {
--- -- Leader triggers
--- { mode = 'n', keys = '<Leader>' },
--- { mode = 'x', keys = '<Leader>' },
---
--- -- Built-in completion
--- { mode = 'i', keys = '<C-x>' },
---
--- -- `g` key
--- { mode = 'n', keys = 'g' },
--- { mode = 'x', keys = 'g' },
---
--- -- Marks
--- { mode = 'n', keys = "'" },
--- { mode = 'n', keys = '`' },
--- { mode = 'x', keys = "'" },
--- { mode = 'x', keys = '`' },
---
--- -- Registers
--- { mode = 'n', keys = '"' },
--- { mode = 'x', keys = '"' },
--- { mode = 'i', keys = '<C-r>' },
--- { mode = 'c', keys = '<C-r>' },
---
--- -- Window commands
--- { mode = 'n', keys = '<C-w>' },
---
--- -- `z` key
--- { mode = 'n', keys = 'z' },
--- { mode = 'x', keys = 'z' },
--- },
---
--- clues = {
--- -- Enhance this by adding descriptions for <Leader> mapping groups
--- miniclue.gen_clues.builtin_completion(),
--- miniclue.gen_clues.g(),
--- miniclue.gen_clues.marks(),
--- miniclue.gen_clues.registers(),
--- miniclue.gen_clues.windows(),
--- miniclue.gen_clues.z(),
--- },
--- })
--- <
--- # Leader clues ~
---
--- Assume there are these |<Leader>| mappings set up: >lua
---
--- -- Set `<Leader>` before making any mappings and configuring 'mini.clue'
--- vim.g.mapleader = ' '
---
--- local nmap_leader = function(suffix, rhs, desc)
--- vim.keymap.set('n', '<Leader>' .. suffix, rhs, { desc = desc })
--- end
--- local xmap_leader = function(suffix, rhs, desc)
--- vim.keymap.set('x', '<Leader>' .. suffix, rhs, { desc = desc })
--- end
---
--- nmap_leader('bd', '<Cmd>lua MiniBufremove.delete()<CR>', 'Delete')
--- nmap_leader('bw', '<Cmd>lua MiniBufremove.wipeout()<CR>', 'Wipeout')
---
--- nmap_leader('lf', '<Cmd>lua vim.lsp.buf.format()<CR>', 'Format')
--- xmap_leader('lf', '<Cmd>lua vim.lsp.buf.format()<CR>', 'Format')
--- nmap_leader('lr', '<Cmd>lua vim.lsp.buf.rename()<CR>', 'Rename')
--- nmap_leader('lR', '<Cmd>lua vim.lsp.buf.references()<CR>', 'References')
--- <
--- The following setup will enable |<Leader>| as trigger in Normal and Visual
--- modes and add descriptions to mapping groups: >lua
---
--- require('mini.clue').setup({
--- -- Register `<Leader>` as trigger
--- triggers = {
--- { mode = 'n', keys = '<Leader>' },
--- { mode = 'x', keys = '<Leader>' },
--- },
---
--- -- Add descriptions for mapping groups
--- clues = {
--- { mode = 'n', keys = '<Leader>b', desc = '+Buffers' },
--- { mode = 'n', keys = '<Leader>l', desc = '+LSP' },
--- },
--- })
--- <
--- # Clues without mappings ~
---
--- Clues can be shown not only for actually present mappings. This is helpful for
--- showing clues for built-in key combinations. Here is an example of clues for
--- a subset of built-in completion (see |MiniClue.gen_clues.builtin_completion()|
--- to generate clues for all available completion sources): >lua
---
--- require('mini.clue').setup({
--- -- Make `<C-x>` a trigger. Otherwise, key query process won't start.
--- triggers = {
--- { mode = 'i', keys = '<C-x>' },
--- },
---
--- -- Register custom clues
--- clues = {
--- { mode = 'i', keys = '<C-x><C-f>', desc = 'File names' },
--- { mode = 'i', keys = '<C-x><C-l>', desc = 'Whole lines' },
--- { mode = 'i', keys = '<C-x><C-o>', desc = 'Omni completion' },
--- { mode = 'i', keys = '<C-x><C-s>', desc = 'Spelling suggestions' },
--- { mode = 'i', keys = '<C-x><C-u>', desc = "With 'completefunc'" },
--- }
--- })
--- <
--- # Triggers in special buffers ~
---
--- By default triggers are automatically created in listed ('buflisted') and some
--- special non-listed buffers. Use |MiniClue.ensure_buf_triggers()| to manually
--- enable in when you need them. For example: >vim
---
--- au FileType special_ft lua MiniClue.ensure_buf_triggers()
--- <
--- *MiniClue-examples-submodes*
--- # Submodes ~
---
--- Submode is a state initiated after pressing certain key combination ("prefix")
--- during which some keys are interpreted differently.
---
--- In this module submode can be implemented following these steps:
---
--- - Create mappings for each key inside submode. Left hand side of mappings
--- should consist from prefix followed by the key.
---
--- - Create clue for each key inside submode with `postkeys` value equal to
--- prefix. It would mean that after executing particular key combination from
--- this submode, pressing its prefix will be automatically emulated (leading
--- back to being inside submode).
---
--- - Register submode prefix (or some of its starting part) as trigger.
---
--- ## Submode examples ~
---
--- - Submode for moving with |mini.move|:
--- - Press `<Leader>m` to start submode.
--- - Press any of `h`/`j`/`k`/`l` to move selection/line.
--- - Press `<Esc>` to stop submode.
---
--- The code: >lua
---
--- require('mini.move').setup({
--- mappings = {
--- left = '<Leader>mh',
--- right = '<Leader>ml',
--- down = '<Leader>mj',
--- up = '<Leader>mk',
--- line_left = '<Leader>mh',
--- line_right = '<Leader>ml',
--- line_down = '<Leader>mj',
--- line_up = '<Leader>mk',
--- },
--- })
---
--- require('mini.clue').setup({
--- triggers = {
--- { mode = 'n', keys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>m' },
--- },
--- clues = {
--- { mode = 'n', keys = '<Leader>mh', postkeys = '<Leader>m' },
--- { mode = 'n', keys = '<Leader>mj', postkeys = '<Leader>m' },
--- { mode = 'n', keys = '<Leader>mk', postkeys = '<Leader>m' },
--- { mode = 'n', keys = '<Leader>ml', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>mh', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>mj', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>mk', postkeys = '<Leader>m' },
--- { mode = 'x', keys = '<Leader>ml', postkeys = '<Leader>m' },
--- },
--- })
--- <
--- - Submode for iterating buffers and windows with |mini.bracketed|:
--- - Press `[` or `]` to start key query process for certain direction.
--- - Press `b` / `w` to iterate buffers/windows until reach target one.
--- - Press `<Esc>` to stop submode.
---
--- The code: >lua
---
--- require('mini.bracketed').setup()
---
--- require('mini.clue').setup({
--- triggers = {
--- { mode = 'n', keys = ']' },
--- { mode = 'n', keys = '[' },
--- },
--- clues = {
--- { mode = 'n', keys = ']b', postkeys = ']' },
--- { mode = 'n', keys = ']w', postkeys = ']' },
---
--- { mode = 'n', keys = '[b', postkeys = '[' },
--- { mode = 'n', keys = '[w', postkeys = '[' },
--- },
--- })
--- <
--- - Submode for window commands using |MiniClue.gen_clues.windows()|:
--- - Press `<C-w>` to start key query process.
--- - Press keys which move / change focus / resize windows.
--- - Press `<Esc>` to stop submode.
---
--- The code: >lua
---
--- local miniclue = require('mini.clue')
--- miniclue.setup({
--- triggers = {
--- { mode = 'n', keys = '<C-w>' },
--- },
--- clues = {
--- miniclue.gen_clues.windows({
--- submode_move = true,
--- submode_navigate = true,
--- submode_resize = true,
--- })
--- },
--- })
--- <
--- # Window config ~
--- >lua
--- require('mini.clue').setup({
--- triggers = { { mode = 'n', keys = '<Leader>' } },
---
--- window = {
--- -- Show window immediately
--- delay = 0,
---
--- config = {
--- -- Compute window width automatically
--- width = 'auto',
---
--- -- Use double-line border
--- border = 'double',
--- },
--- },
--- })
--- <
---@tag MiniClue-examples
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
---@diagnostic disable:cast-local-type
-- Module definition ==========================================================
local MiniClue = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniClue.config|.
---
---@usage >lua
--- require('mini.clue').setup({}) -- replace {} with your config table
--- -- needs `triggers` field present
--- <
MiniClue.setup = function(config)
-- Export module
_G.MiniClue = MiniClue
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Define behavior
H.create_autocommands()
-- Create default highlighting
H.create_default_hl()
end
--stylua: ignore
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # General info ~
---
--- - To use |<Leader>| as part of the config (either as trigger or inside clues),
--- set it prior to running |MiniClue.setup()|.
---
--- - See |MiniClue-examples| for examples.
---
--- # Clues ~
---
--- `config.clues` is an array with extra information about key combinations.
--- Each element can be one of:
--- - Clue table.
--- - Array (possibly nested) of clue tables.
--- - Callable (function) returning either of the previous two.
---
--- A clue table is a table with the following fields:
--- - <mode> `(string)` - single character describing **single** mode short-name of
--- key combination as in `nvim_set_keymap()` ('n', 'x', 'i', 'o', 'c', etc.).
--- - <keys> `(string)` - key combination for which clue will be shown.
--- "Human-readable" key names as in |key-notation| (like "<Leader>", "<Space>",
--- "<Tab>", etc.) are allowed.
--- - <desc> `(string|nil)` - optional key combination description which will
--- be shown in clue window.
--- - <postkeys> `(string|nil)` - optional postkeys which will be executed
--- automatically after `keys`. Allows creation of submodes
--- (see |MiniClue-examples-submodes|).
---
--- Notes:
--- - Postkeys are literal simulation of keypresses with |nvim_feedkeys()|.
---
--- - Suggested approach to configuring clues is to create mappings with `desc`
--- field while supplying to `config.clues` only elements describing groups,
--- postkeys, and built-in mappings.
---
--- # Triggers ~
---
--- `config.triggers` is an array with information when |MiniClue-key-query-process|
--- should start. Each element is a trigger table with the fields <mode> and
--- <keys> which are treated the same as in clue table.
---
--- # Window ~
---
--- `config.window` defines behavior of clue window.
---
--- `config.window.delay` is a number of milliseconds after which clue window will
--- appear. Can be 0 to show immediately.
---
--- `config.window.config` is a table defining floating window characteristics
--- or a callable returning such table (will be called with identifier of
--- window's buffer already showing all clues). It should have the same
--- structure as in |nvim_open_win()| with the following enhancements:
--- - <width> field can be equal to `"auto"` leading to window width being
--- computed automatically based on its content. Default is fixed width of 30.
--- - <row> and <col> can be equal to `"auto"` in which case they will be
--- computed to "stick" to set anchor ("SE" by default; see |nvim_open_win()|).
--- This allows changing corner in which window is shown: >lua
---
--- -- Pick one anchor
--- local anchor = 'NW' -- top-left
--- local anchor = 'NE' -- top-right
--- local anchor = 'SW' -- bottom-left
--- local anchor = 'SE' -- bottom-right
---
--- require('mini.clue').setup({
--- window = {
--- config = { anchor = anchor, row = 'auto', col = 'auto' },
--- },
--- })
--- <
--- `config.window.scroll_down` / `config.window.scroll_up` are strings defining
--- keys which will scroll clue window down / up which is useful in case not
--- all clues fit in current window height. Set to empty string `''` to disable
--- either of them.
MiniClue.config = {
-- Array of extra clues to show
clues = {},
-- Array of opt-in triggers which start custom key query process.
-- **Needs to have something in order to show clues**.
triggers = {},
-- Clue window settings
window = {
-- Floating window config
config = {},
-- Delay before showing clue window
delay = 1000,
-- Keys to scroll inside the clue window
scroll_down = '<C-d>',
scroll_up = '<C-u>',
},
}
--minidoc_afterlines_end
--- Enable triggers in all listed buffers
MiniClue.enable_all_triggers = function()
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
-- Map only inside valid listed buffers
if vim.fn.buflisted(buf_id) == 1 then H.map_buf_triggers(buf_id) end
end
end
--- Enable triggers in buffer
---
---@param buf_id number|nil Buffer identifier. Default: current buffer.
MiniClue.enable_buf_triggers = function(buf_id)
buf_id = (buf_id == nil or buf_id == 0) and vim.api.nvim_get_current_buf() or buf_id
if not H.is_valid_buf(buf_id) then H.error('`buf_id` should be a valid buffer identifier.') end
H.map_buf_triggers(buf_id)
end
--- Disable triggers in all buffers
MiniClue.disable_all_triggers = function()
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
H.unmap_buf_triggers(buf_id)
end
end
--- Disable triggers in buffer
---
---@param buf_id number|nil Buffer identifier. Default: current buffer.
MiniClue.disable_buf_triggers = function(buf_id)
buf_id = (buf_id == nil or buf_id == 0) and vim.api.nvim_get_current_buf() or buf_id
if not H.is_valid_buf(buf_id) then H.error('`buf_id` should be a valid buffer identifier.') end
H.unmap_buf_triggers(buf_id)
end
--- Ensure all triggers are valid
MiniClue.ensure_all_triggers = function()
MiniClue.disable_all_triggers()
MiniClue.enable_all_triggers()
end
--- Ensure buffer triggers are valid
---
---@param buf_id number|nil Buffer identifier. Default: current buffer.
MiniClue.ensure_buf_triggers = function(buf_id)
MiniClue.disable_buf_triggers(buf_id)
MiniClue.enable_buf_triggers(buf_id)
end
--- Update description of an existing mapping
---
--- Notes:
--- - Uses buffer-local mapping in case there are both global and buffer-local
--- mappings with same mode and LHS. Similar to |maparg()|.
---
---@param mode string Mapping mode (as in `maparg()`).
---@param lhs string Mapping left hand side (as `name` in `maparg()`).
---@param desc string New description to set.
MiniClue.set_mapping_desc = function(mode, lhs, desc)
if type(mode) ~= 'string' then H.error('`mode` should be string.') end
if type(lhs) ~= 'string' then H.error('`lhs` should be string.') end
if type(desc) ~= 'string' then H.error('`desc` should be string.') end
local ok_get, map_data = pcall(vim.fn.maparg, lhs, mode, false, true)
if not ok_get or vim.tbl_count(map_data) == 0 then
local msg = string.format('No mapping found for mode %s and LHS %s.', vim.inspect(mode), vim.inspect(lhs))
H.error(msg)
end
map_data.desc = desc
local ok_set = pcall(vim.fn.mapset, mode, false, map_data)
if not ok_set then H.error(vim.inspect(desc) .. ' is not a valid description.') end
end
--- Generate pre-configured clues
---
--- This is a table with function elements. Call to actually get array of clues.
MiniClue.gen_clues = {}
--- Generate clues for built-in completion
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'i', keys = '<C-x>' }
--- <
---@return table Array of clues.
MiniClue.gen_clues.builtin_completion = function()
--stylua: ignore
return {
{ mode = 'i', keys = '<C-x><C-d>', desc = 'Defined identifiers' },
{ mode = 'i', keys = '<C-x><C-e>', desc = 'Scroll up' },
{ mode = 'i', keys = '<C-x><C-f>', desc = 'File names' },
{ mode = 'i', keys = '<C-x><C-i>', desc = 'Identifiers' },
{ mode = 'i', keys = '<C-x><C-k>', desc = 'Identifiers from dictionary' },
{ mode = 'i', keys = '<C-x><C-l>', desc = 'Whole lines' },
{ mode = 'i', keys = '<C-x><C-n>', desc = 'Next completion' },
{ mode = 'i', keys = '<C-x><C-o>', desc = 'Omni completion' },
{ mode = 'i', keys = '<C-x><C-p>', desc = 'Previous completion' },
{ mode = 'i', keys = '<C-x><C-s>', desc = 'Spelling suggestions' },
{ mode = 'i', keys = '<C-x><C-t>', desc = 'Identifiers from thesaurus' },
{ mode = 'i', keys = '<C-x><C-y>', desc = 'Scroll down' },
{ mode = 'i', keys = '<C-x><C-u>', desc = "With 'completefunc'" },
{ mode = 'i', keys = '<C-x><C-v>', desc = 'Like in command line' },
{ mode = 'i', keys = '<C-x><C-z>', desc = 'Stop completion' },
{ mode = 'i', keys = '<C-x><C-]>', desc = 'Tags' },
{ mode = 'i', keys = '<C-x>s', desc = 'Spelling suggestions' },
}
end
--- Generate clues for `g` key
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = 'g' }
--- { mode = 'x', keys = 'g' }
--- <
---@return table Array of clues.
MiniClue.gen_clues.g = function()
local gr_clue = vim.fn.has('nvim-0.11') == 1 and { mode = 'n', keys = 'gr', desc = '+LSP' }
or { mode = 'n', keys = 'gr', desc = 'Virtual replace with character' }
local gr_clue_viz = vim.fn.has('nvim-0.11') == 1 and { mode = 'x', keys = 'gr', desc = '+LSP' } or {}
--stylua: ignore
return {
{ mode = 'n', keys = 'g0', desc = 'Go to leftmost visible column' },
{ mode = 'n', keys = 'g8', desc = 'Print hex value of char under cursor' },
{ mode = 'n', keys = 'ga', desc = 'Print ascii value' },
{ mode = 'n', keys = 'gD', desc = 'Go to definition in file' },
{ mode = 'n', keys = 'gd', desc = 'Go to definition in function' },
{ mode = 'n', keys = 'gE', desc = 'Go backwards to end of previous WORD' },
{ mode = 'n', keys = 'ge', desc = 'Go backwards to end of previous word' },
{ mode = 'n', keys = 'gF', desc = 'Edit file under cursor + jump line' },
{ mode = 'n', keys = 'gf', desc = 'Edit file under cursor' },
{ mode = 'n', keys = 'gg', desc = 'Go to line (def: first)' },
{ mode = 'n', keys = 'gH', desc = 'Start Select line mode' },
{ mode = 'n', keys = 'gh', desc = 'Start Select mode' },
{ mode = 'n', keys = 'gI', desc = 'Start Insert at column 1' },
{ mode = 'n', keys = 'gi', desc = 'Start Insert where it stopped' },
{ mode = 'n', keys = 'gJ', desc = 'Join lines without extra spaces' },
{ mode = 'n', keys = 'gj', desc = 'Go down by screen lines' },
{ mode = 'n', keys = 'gk', desc = 'Go up by screen lines' },
{ mode = 'n', keys = 'gM', desc = 'Go to middle of text line' },
{ mode = 'n', keys = 'gm', desc = 'Go to middle of screen line' },
{ mode = 'n', keys = 'gN', desc = 'Select previous search match' },
{ mode = 'n', keys = 'gn', desc = 'Select next search match' },
{ mode = 'n', keys = 'go', desc = 'Go to byte' },
{ mode = 'n', keys = 'gP', desc = 'Put text before cursor + stay after it' },
{ mode = 'n', keys = 'gp', desc = 'Put text after cursor + stay after it' },
{ mode = 'n', keys = 'gQ', desc = 'Switch to "Ex" mode' },
{ mode = 'n', keys = 'gq', desc = 'Format text (operator)' },
{ mode = 'n', keys = 'gR', desc = 'Enter Virtual Replace mode' },
gr_clue,
{ mode = 'n', keys = 'gs', desc = 'Sleep' },
{ mode = 'n', keys = 'gT', desc = 'Go to previous tabpage' },
{ mode = 'n', keys = 'gt', desc = 'Go to next tabpage' },
{ mode = 'n', keys = 'gU', desc = 'Make uppercase (operator)' },
{ mode = 'n', keys = 'gu', desc = 'Make lowercase (operator)' },
{ mode = 'n', keys = 'gV', desc = 'Avoid reselect' },
{ mode = 'n', keys = 'gv', desc = 'Reselect previous Visual area' },
{ mode = 'n', keys = 'gw', desc = 'Format text + keep cursor (operator)' },
{ mode = 'n', keys = 'gx', desc = 'Execute app for file under cursor' },
{ mode = 'n', keys = 'g<C-]>', desc = '`:tjump` to tag under cursor' },
{ mode = 'n', keys = 'g<C-a>', desc = 'Dump a memory profile' },
{ mode = 'n', keys = 'g<C-g>', desc = 'Show information about cursor' },
{ mode = 'n', keys = 'g<C-h>', desc = 'Start Select block mode' },
{ mode = 'n', keys = 'g<Tab>', desc = 'Go to last accessed tabpage' },
{ mode = 'n', keys = "g'", desc = "Jump to mark (don't affect jumplist)" },
{ mode = 'n', keys = 'g#', desc = 'Search backwards word under cursor' },
{ mode = 'n', keys = 'g$', desc = 'Go to rightmost visible column' },
{ mode = 'n', keys = 'g%', desc = 'Cycle through matching groups' },
{ mode = 'n', keys = 'g&', desc = 'Repeat last `:s` on all lines' },
{ mode = 'n', keys = 'g*', desc = 'Search word under cursor' },
{ mode = 'n', keys = 'g+', desc = 'Go to newer text state' },
{ mode = 'n', keys = 'g,', desc = 'Go to newer position in change list' },
{ mo 10BC8 de = 'n', keys = 'g-', desc = 'Go to older text state' },
{ mode = 'n', keys = 'g;', desc = 'Go to older position in change list' },
{ mode = 'n', keys = 'g<', desc = 'Display previous command output' },
{ mode = 'n', keys = 'g?', desc = 'Rot13 encode (operator)' },
{ mode = 'n', keys = 'g@', desc = "Call 'operatorfunc' (operator)" },
{ mode = 'n', keys = 'g]', desc = '`:tselect` tag under cursor' },
{ mode = 'n', keys = 'g^', desc = 'Go to leftmost visible non-whitespace' },
{ mode = 'n', keys = 'g_', desc = 'Go to lower line' },
{ mode = 'n', keys = 'g`', desc = "Jump to mark (don't affect jumplist)" },
{ mode = 'n', keys = 'g~', desc = 'Swap case (operator)' },
{ mode = 'x', keys = 'gf', desc = 'Edit selected file' },
{ mode = 'x', keys = 'gJ', desc = 'Join selected lines without extra spaces' },
{ mode = 'x', keys = 'gq', desc = 'Format selection' },
gr_clue_viz,
{ mode = 'x', keys = 'gV', desc = 'Avoid reselect' },
{ mode = 'x', keys = 'gw', desc = 'Format selection + keep cursor' },
{ mode = 'x', keys = 'g<C-]>', desc = '`:tjump` to selected tag' },
{ mode = 'x', keys = 'g<C-a>', desc = 'Increment with compound' },
{ mode = 'x', keys = 'g<C-g>', desc = 'Show information about selection' },
{ mode = 'x', keys = 'g<C-x>', desc = 'Decrement with compound' },
{ mode = 'x', keys = 'g]', desc = '`:tselect` selected tag' },
{ mode = 'x', keys = 'g?', desc = 'Rot13 encode selection' },
}
end
--- Generate clues for marks
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = "'" }
--- { mode = 'n', keys = "g'" }
--- { mode = 'n', keys = '`' }
--- { mode = 'n', keys = 'g`' }
--- { mode = 'x', keys = "'" }
--- { mode = 'x', keys = "g'" }
--- { mode = 'x', keys = '`' }
--- { mode = 'x', keys = 'g`' }
--- <
--- Note: if you use "g" as trigger (like to enable |MiniClue.gen_clues.g()|),
--- don't add "g'" and "g`" as triggers: they already will be taken into account.
---
---@return table Array of clues.
---
---@seealso |mark-motions|
MiniClue.gen_clues.marks = function()
local describe_marks = function(mode, prefix)
local make_clue = function(register, desc) return { mode = mode, keys = prefix .. register, desc = desc } end
return {
make_clue('^', 'Latest insert position'),
make_clue('.', 'Latest change'),
make_clue('"', 'Latest exited position'),
make_clue("'", 'Line before jump'),
make_clue('`', 'Position before jump'),
make_clue('[', 'Start of latest changed or yanked text'),
make_clue(']', 'End of latest changed or yanked text'),
make_clue('(', 'Start of sentence'),
make_clue(')', 'End of sentence'),
make_clue('{', 'Start of paragraph'),
make_clue('}', 'End of paragraph'),
make_clue('<', 'Start of latest visual selection'),
make_clue('>', 'End of latest visual selection'),
}
end
--stylua: ignore
return {
-- Normal mode
describe_marks('n', "'"),
describe_marks('n', "g'"),
describe_marks('n', "`"),
describe_marks('n', "g`"),
-- Visual mode
describe_marks('x', "'"),
describe_marks('x', "g'"),
describe_marks('x', "`"),
describe_marks('x', "g`"),
}
end
--- Generate clues for registers
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = '"' }
--- { mode = 'x', keys = '"' }
--- { mode = 'i', keys = '<C-r>' }
--- { mode = 'c', keys = '<C-r>' }
--- <
---@param opts table|nil Options. Possible keys:
--- - <show_contents> `(boolean)` - whether to show contents of all possible
--- registers. If `false`, only description of special registers is shown.
--- Default: `false`.
---
---@return table Array of clues.
---
---@seealso |registers|
MiniClue.gen_clues.registers = function(opts)
opts = vim.tbl_deep_extend('force', { show_contents = false }, opts or {})
local describe_registers
if opts.show_contents then
describe_registers = H.make_clues_with_register_contents
else
describe_registers = function(mode, prefix)
local make_clue = function(register, desc) return { mode = mode, keys = prefix .. register, desc = desc } end
return {
make_clue('0', 'Latest yank'),
make_clue('1', 'Latest big delete'),
make_clue('"', 'Default register'),
make_clue('#', 'Alternate buffer'),
make_clue('%', 'Name of the current file'),
make_clue('*', 'Selection clipboard'),
make_clue('+', 'System clipboard'),
make_clue('-', 'Latest small delete'),
make_clue('.', 'Latest inserted text'),
make_clue('/', 'Latest search pattern'),
make_clue(':', 'Latest executed command'),
make_clue('=', 'Result of expression'),
make_clue('_', 'Black hole'),
}
end
end
--stylua: ignore
return {
-- Normal mode
describe_registers('n', '"'),
-- Visual mode
describe_registers('x', '"'),
-- Insert mode
describe_registers('i', '<C-r>'),
{ mode = 'i', keys = '<C-r><C-r>', desc = '+Insert literally' },
describe_registers('i', '<C-r><C-r>'),
{ mode = 'i', keys = '<C-r><C-o>', desc = '+Insert literally + not auto-indent' },
describe_registers('i', '<C-r><C-o>'),
{ mode = 'i', keys = '<C-r><C-p>', desc = '+Insert + fix indent' },
describe_registers('i', '<C-r><C-p>'),
-- Command-line mode
describe_registers('c', '<C-r>'),
{ mode = 'c', keys = '<C-r><C-r>', desc = '+Insert literally' },
describe_registers('c', '<C-r><C-r>'),
{ mode = 'c', keys = '<C-r><C-o>', desc = '+Insert literally' },
describe_registers('c', '<C-r><C-o>'),
}
end
--- Generate clues for window commands
---
--- Contains clues for the following triggers: >lua
---
--- { mode = 'n', keys = '<C-w>' }
--- <
--- Note: only non-duplicated commands are included. For full list see |CTRL-W|.
---
---@param opts table|nil Options. Possible keys:
--- - <submode_move> `(boolean)` - whether to make move (change layout)
--- commands a submode by using `postkeys` field. Default: `false`.
--- - <submode_navigate> `(boolean)` - whether to make navigation (change
--- focus) commands a submode by using `postkeys` field. Default: `false`.
--- - <submode_resize> `(boolean)` - whether to make resize (change size)
--- commands a submode by using `postkeys` field. Default: `false`.
---
---@return table Array of clues.
MiniClue.gen_clues.windows = function(opts)
local default_opts = { submode_navigate = false, submode_move = false, submode_resize = false }
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
local postkeys_move, postkeys_navigate, postkeys_resize = nil, nil, nil
if opts.submode_move then postkeys_move = '<C-w>' end
if opts.submode_navigate then postkeys_navigate = '<C-w>' end
if opts.submode_resize then postkeys_resize = '<C-w>' end
--stylua: ignore
return {
{ mode = 'n', keys = '<C-w>+', desc = 'Increase height', postkeys = postkeys_resize },
{ mode = 'n', keys = '<C-w>-', desc = 'Decrease height', postkeys = postkeys_resize },
{ mode = 'n', keys = '<C-w><', desc = 'Decrease width', postkeys = postkeys_resize },
{ mode = 'n', keys = '<C-w>>', desc = 'Increase width', postkeys = postkeys_resize },
{ mode = 'n', keys = '<C-w>=', desc = 'Make windows same dimensions' },
{ mode = 'n', keys = '<C-w>]', desc = 'Split + jump to tag' },
{ mode = 'n', keys = '<C-w>^', desc = 'Split + edit alternate file' },
{ mode = 'n', keys = '<C-w>_', desc = 'Set height (def: very high)' },
{ mode = 'n', keys = '<C-w>|', desc = 'Set width (def: very wide)' },
{ mode = 'n', keys = '<C-w>}', desc = 'Show tag in preview' },
{ mode = 'n', keys = '<C-w>b', desc = 'Focus bottom', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>c', desc = 'Close' },
{ mode = 'n', keys = '<C-w>d', desc = 'Split + jump to definition' },
{ mode = 'n', keys = '<C-w>F', desc = 'Split + edit file name + jump' },
{ mode = 'n', keys = '<C-w>f', desc = 'Split + edit file name' },
{ mode = 'n', keys = '<C-w>g', desc = '+Extra actions' },
{ mode = 'n', keys = '<C-w>g]', desc = 'Split + list tags' },
{ mode = 'n', keys = '<C-w>g}', desc = 'Do `:ptjump`' },
{ mode = 'n', keys = '<C-w>g<C-]>', desc = 'Split + jump to tag with `:tjump`' },
{ mode = 'n', keys = '<C-w>g<Tab>', desc = 'Focus last accessed tab', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>gF', desc = 'New tabpage + edit file name + jump' },
{ mode = 'n', keys = '<C-w>gf', desc = 'New tabpage + edit file name' },
{ mode = 'n', keys = '<C-w>gT', desc = 'Focus previous tabpage', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>gt', desc = 'Focus next tabpage', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>H', desc = 'Move to very left', postkeys = postkeys_move },
{ mode = 'n', keys = '<C-w>h', desc = 'Focus left', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>i', desc = 'Split + jump to declaration' },
{ mode = 'n', keys = '<C-w>J', desc = 'Move to very bottom', postkeys = postkeys_move },
{ mode = 'n', keys = '<C-w>j', desc = 'Focus down', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>K', desc = 'Move to very top', postkeys = postkeys_move },
{ mode = 'n', keys = '<C-w>k', desc = 'Focus up', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>L', desc = 'Move to very right', postkeys = postkeys_move },
{ mode = 'n', keys = '<C-w>l', desc = 'Focus right', postkeys = postkeys_navigate },
{ mode = 'n', keys = '<C-w>n', desc = 'Open new' },
0