Impairative is a helper plugin for creating pairs of complementing keymaps:
- Toggle options on/off.
- Jump back and forth on certain lists.
- Encode/decode text using certain formats.
Impairative is inspired by unimpaired, but unlike unimpaired - which provides a big predefined set of keymaps - Impairative provides helper functions for easily defining these keymaps.
Impairative does provide, though, an helper function that can be used to easily replicate the keymaps unimpaired defines (although with slight modifications) - making it usable as an unimpaired replacement.
Install Impairative with your plugin manager of choice. There is no need to call require'impairative'.setup, but it can still be used as an entry point.
Impairative can create keymaps for option toggling.
First, create an helper object with the keymap prefixes:
require'impairative'.toggling {
enable = '[o',
disable = ']o',
toggle = 'yo',
}There are two ways to use Impairative's helper objects:
- Set them to a variable and use that variable:
local t = require'impairative'.toggling { ... } t:option { ... } t:option { ... } t:option { ... } t:option { ... }
- Fluent API - since each key-setting method returns the helper, these methods can be chained:
require'impairative'.toggling { ... } :option { ... } :option { ... } :option { ... } :option { ... }
To create keymaps for a Vim option, use the :option method. For example:
require'impairative'.toggling { ... }
:option {
key = 's',
option = 'spell',
}Assuming toggling was called with [o, ]o and yo as above, this will create a [os keymap to enable spellcheck, ]os to disable it, and yos to toggle it.
For options with non-boolean values, a values parameter can be used to tell Impairative what is considered "on" and what is considered "off". For example:
require'impairative'.toggling { ... }
:option {
key = 'v',
option = 'virtualedit',
values = { [true] = 'all', [false] = '' }
}If the option is not part of vim.o, but still acts as a field in a Lua table, the :field method can be used:
require'impairative'.toggling { ... }
:field {
key = 's',
-- Using a regular option for the example because with builtin Vim stuff
-- they all are regular options:
table = vim.o,
field = 'spell',
name = 'Spell Check', -- for generating the description
}The :getter_setter method can be used for more complex cases, where the option at hand is not a simple field. For example:
require'impairative'.toggling { ... }
:getter_setter {
key = 'i',
name = 'inlay hints',
get = vim.lsp.inlay_hint.is_enabled,
set = vim.lsp.inlay_hint.enable,
}Finally, for maximum manual control, the :manual method can be used to directly specify the operation of each keymap:
require'impairative'.toggling { ... }
:manual {
key = "t",
name = "Treesitter context",
enable = "TSContextEnable",
-- Both commands and functions are supported
disable = function()
vim.cmd.TSContextDisable()
end,
toggle = "TSContextToggle",
}Impairative can create keymaps that come in pairs - backward and forward. This is useful for pairs of commands that negate each other - like jumping backward/forward or encoding/decoding text.
First, create an helper object with the keymap prefixes:
require'impairative'.operations {
backward = '[',
forward = ']',
}Just like the toggling helper, the operations helper can either be bound to a variable or used with method chaining.
The simplest way to use the operations helper is the :command_pair method, which binds keys for two Vim commands.
require'impairative'.operations { ... }
:command_pair {
key = 'a',
backward = 'previous',
forward = 'next',
}
-- Note that for the first/last variant we use a second call.
-- Impairative does not know - nor does it care - that they are related.
:command_pair {
key = 'A',
backward = 'first',
forward = 'last',
}For more control, the :function_pair method can be used. It calls a Lua function instead of running a command:
require'impairative'.operations { ... }
:function_pair {
key = 'a',
desc = 'jump to the {previous|next} file in the argument list',
backward = function()
vim.cmd {
cmd = 'previous',
range = {vim.v.count1},
}
end,
forward = function()
vim.cmd {
cmd = 'next',
range = {vim.v.count1},
}
end,
}The desc parameter is used to generate the description for the keymaps. The {previous|next} syntax resolves to previous for the backward keymap and next for the forward keymap.
More often than not, it it is more convenient to unify the backward and forward function into a single function that slightly changes its behavior based on the direction. That can easily be done with the :unified_function method.
require'impairative'.operations { ... }
:unified_function {
key = 'a',
fun = function(direction)
vim.cmd {
cmd = ({
backward = 'previous',
forward = 'next',
})[direction],
range = {vim.v.count1},
}
end,
}The operations helper can be used to pairs of backward-forward jumping commands.
The :jump_in_buf method can be used to jump to a location in the current buffer. It's fun argument must return a Vim iterator (see :help vim.iter) which yields tables with four integer fields - start_line, start_col, end_line and end_col - where lines are 1-based and columns are 0-based.
require'impairative'.operations { ... }
:jump_in_buf {
key = 'h',
desc = 'jump to the {previous|next} markdown hyperlink',
extreme = {
key = 'H',
desc = 'jump to the {first|last} markdown hyperlink',
},
fun = function()
-- This is a pattern for markdown hyperlinks:
local pattern = vim.regex[=[\[.\{-}\](.\{-})]=]
local bufnr = vim.api.nvim_get_current_buf()
-- Helper function provided by Impairative to create iterators over
-- ranges of numbers. Note that this function is only good enough for
-- an example - it does not support lines with multiple hyperlinks and
-- it does not support hyperlinks that spawn multiple lines.
return require'impairative.util'.iter_range(1, vim.fn.line('$'))
:map(function(line)
local from, to = pattern:match_line(bufnr, line - 1)
if from then
return {
start_line = line,
start_col = from,
end_line = line,
end_col = to,
}
end
-- Utilize the fact that Iter:map is actually a filter-map which skips nil results.
end)
end,
}:jump_in_buf will detect the location of the cursor in the buffer and use that to determine the location to jump to.
:jump_in_buf has an extreme option for creating keymaps that jump to the first and last positions in the iterator.
Jumping to different files will have to be done manually, using :command_pair/:function_pair/:unified_function.
Impairative can create pairs of keymaps for manipulating texts and ranges:
require'impairative'.operations { ... }
:text_manipulation {
key = 'c',
line_key = true,
desc = 'convert to {lower|upper} case',
backward = string.lower,
forward = string.upper,
}These can be used either as operators (which a motion) or in visual/select mode. line_key adds a "motion" (actually part of an extra keymap) for operating them on the current line - either by repeating the regular key (like the builtin Vim commands) or - if line_key is set to a string - by using that string as the "motion".
For text manipulations that encode and decode for a certain format, unimpaired has set the convention that "backward" is for encoding and "forward" is for decoding. For the sake of consistency, users of Impairative are encouraged to follow that rule.
Impairative can also create keymaps that manipulate the range directly using :range_manipulation:
require'impairative'.operations { ... }
:range_manipulation {
key = 'r',
desc = '{add|remove} error marks',
fun = function(args)
local bufnr = vim.api.nvim_get_current_buf()
if args.direction == 'backward' then
vim.api.nvim_buf_set_extmark(bufnr, ns, args.start_line - 1, args.start_col - 1, {
end_row = args.end_line - 1,
end_col = args.end_col,
hl_group = 'ErrorMsg',
})
else
local marks = vim.api.nvim_buf_get_extmarks(
bufnr,
ns,
{args.start_line - 1, args.start_col - 1},
{args.end_line, args.end_col},
{overlap = true}
)
for _, mark in ipairs(marks) do
vim.api.nvim_buf_del_extmark(bufnr, ns, mark[1])
end
end
end,
}These are a bit more complex. The function receives an arguments table that contains the following fields:
direction- either'backward'or'forward', depending on the prefix key used.countandcount1- the counts the keymap was invoked with. Don't usevim.v.countandvim.v.count1- they will contain the count of the motion rather then the operator.range_type-'char','line'or'block'.start_line,end_line,start_colandend_col- the selected range. Both lines and columns are 1-based.
To have Impairative register the same keymaps unimpaired does, add this to your init.lua:
require'impairative.replicate-unimpaired'()Users are encouraged though to copy-paste the file and edit it to their own liking.
Impairative's version of the keymaps has several differences from unimpaired's behavior:
- unimpaired's normal mode version of
[eand]ework on the current line. In Impairative, they are operators. - unimpaired's URL encoding encode spaces a
%20. Impairative encodes them as+. - Impairative's C string decoder (
]y/]C) knows how to decode 32bit Unicode codepoints (the ones that start with\U) - Impairative does not implement unimpaired's paste-related keymaps, because in Neovim the
'paste'option is obsolete. - unimpaired's
[nand]nwork as a text object when used after an operator. Imerative's version of them works as one would expect - regular motions. - Impairative, unlike unimpaired, has a
[Nand]Nversion that jumps to the first and last conflict markers.
BETTER-N INTEGRATION
Operations may be integrated with the better-n plugin by setting better_n:
require'impairative'.operations { ... }
:command_pair {
key = 'q',
backward = 'cprevious',
forward = 'cnext',
better_n = true,
}When the mapping is used, n and N will be bound to the forward and backward operations respectively. Note that the integration is only available for function_pair, unified_pair, jump_in_buf, and command_pair operations since it has no useful interpretation for editing operations. Furthermore, the better-n plugin has to installed to enable this functionality.
When the backward mapping is used, n and N will be flipped in accordance to ?, F, and T. This behaviour can be changed through the relative_direction option, either globally during setup or locally during the definition of a pair:
require'impairative'.setup {
better_n = {
relative_direction = false,
},
}
require'impairative'.operations { ... }
:command_pair {
key = 'q',
backward = 'cprevious',
forward = 'cnext',
better_n = {
relative_direction = false,
},
}The following table subsumes the behaviour of n and N after a backward operation depending on relative_direction:
| after backward operation | n |
N |
|---|---|---|
relative_direction = true |
backward | forward |
relative_direction = false |
forward | backward |
To support lazy.vim's opts = configuration style, Impairative's setup function can be used to set up the keymaps:
-- ***********************************
-- * I M P O R T A N T ! ! ! *
-- * *
-- * These are **NOT** the defaults! *
-- * The defaults are to do nothing. *
-- ***********************************
require'impairative'.setup {
-- Configure toggling using an helper
enable = '[o',
disable = ']o',
toggle = 'yo',
toggling = function(h)
h:option {
key = 'n',
option = 'number',
}
h:option {
key = 'r',
option = 'relativenumber',
}
h:option {
key = 's',
option = 'spell',
}
end,
-- Configure operations using an helper
backward = '[',
forward = ']',
operations = function(h)
h:command_pair {
key = 'b',
backward = 'bprevious',
forward = 'bnext',
}
h:command_pair {
key = 'B',
backward = 'bfirst',
forward = 'blast',
}
end,
-- Defaults to false
replicate_unimpaired = true,
}The settings are grouped into three:
enable,disable,toggle, andtoggling- create a toggling helper and pass it to the function. If the mapping leaders are not specified, they'll default to[o,]o, andyo.backward,forward, andoperations- create an operations helper and pass it to the function. If the mapping leaders are not specified, they'll default to[and].replicate_unimpaired- generate the keymaps from unimpaired.
- If your contribution can be reasonably tested with automation tests, add tests.
- Documentation comments must be compatible with both Sumneko Language Server and lemmy-help. If you do something that changes the documentation, please run
make docsto update the vimdoc. - Impairative uses Google's Release Please, so write your commits according to the Conventional Commits format.