In standard Vim/Neovim the ; and , keys repeats the f, F, t and T motions.
But for the other motions or actions (e.g. }, ]m, Ctrl+w>) there are no builtin ways to repeat them.
The goal of this plugin is allow to repeat the other motions or actions using the ; and , keys.
It is particularly useful for double characters motions likes next/previous method: [m/]m.
It is also useful for motions with a count 10j (10 lines down).
It is possible to configure some motions to be repeatable only if they are executed with a count above 1.
The repetition of the motions can be configured as to be done in the direction of:
- The initial motion (
;repeat the motion,,undo the motion, the Vim/Neovim default) or of - The document (
;goes forward,,goes backward)
e.g. When the [m motion will be repeated:
If "direction of the document" has been selected then:
- The forward repetition
;will do]mand - The backward repetition
,will do[m
If "direction of the initial motion" has been selected then:
- The forward repetition
;will do[mand - The backward repetition
,will do]m
The advantages of remotions on the other existing plugins identified are:
- It supports also plugin motions/operation defined both globally or at buffer level
- It supports repetition of motion including their count if different from 1 (optional)
Tested on Vim >= 8.2 and Neovim >= 0.8.3
For vim-plug users:
Plug 'vds2212/vim-remotions'For lazy.vim users:
local motions = {
para = { backward = "{", forward = "}" },
sentence = { backward = "(", forward = ")" },
change = { backward = "g,", forward = "g;" },
class = { backward = "[[", forward = "]]" },
classend = { backward = "[]", forward = "][" },
method = { backward = "[m", forward = "]m" },
methodend = { backward = "[M", forward = "]M" },
arg = { backward = "[a", forward = "]a" },
buffer = { backward = "[b", forward = "]b" },
location = { backward = "[l", forward = "]l" },
quickfix = { backward = "[q", forward = "]q" },
tag = { backward = "[t", forward = "]t" },
diagnostic = { backward = "[g", forward = "]g" },
}
return {
"vds2212/vim-remotions",
event = { "BufRead", "BufWinEnter", "BufNewFile" },
config = function()
vim.g.remotions_motions = motions
end,
}It is possible to configure the motions that should be considered: For each motion three information have to be provided and some options can be set:
-
The
nameof the motion (e.g.para) -
The
backwardmotion key sequence (e.g.{) -
The
forwardmotion key sequence (e.g.}) -
The
repeat_if_countoption for the motion. If set the motion is repeatable only if it has been executed with a count above of 1. -
The
repeat_countoption for the motion. If present overridesg:remotions_repeat_countof the motion. Remark: Not all motions support count. If the original motion does not support count the repetition will also not. -
The
directionoption for the motion. If present overridesg:remotions_directionof the motion.
Here is the Remotions default:
let g:remotions_motions = {
\ 'TtFf' : {},
\ 'para' : { 'backward' : '{', 'forward' : '}' },
\ 'change' : { 'backward' : 'g,', 'forward' : 'g;' },
\ 'class' : { 'backward' : '[[', 'forward' : ']]' },
\ 'classend' : { 'backward' : '[]', 'forward' : '][' },
\ 'method' : { 'backward' : '[m', 'forward' : ']m' },
\ 'methodend' : { 'backward' : '[M', 'forward' : ']M' },
\
\ 'buffer' : { 'backward' : '[b', 'forward' : ']b'},
\ 'location' : { 'backward' : '[l', 'forward' : ']l'},
\ 'quickfix' : { 'backward' : '[q', 'forward' : ']q'},
\ 'tag' : { 'backward' : '[t', 'forward' : ']t'},
\
\ 'diagnostic' : { 'backward' : '[g', 'forward' : ']g'},
\ }Here is an more extensive list of motions:
let g:remotions_motions = {
\ 'TtFf' : {},
\ 'para' : { 'backward' : '{', 'forward' : '}' },
\ 'sentence' : { 'backward' : '(', 'forward' : ')' },
\ 'change' : { 'backward' : 'g,', 'forward' : 'g;' },
\ 'class' : { 'backward' : '[[', 'forward' : ']]' },
\ 'classend' : { 'backward' : '[]', 'forward' : '][' },
\ 'method' : { 'backward' : '[m', 'forward' : ']m' },
\ 'methodend' : { 'backward' : '[M', 'forward' : ']M' },
\
\ 'line' : {
\ 'backward' : 'k',
\ 'forward' : 'j',
\ 'repeat_if_count' : 1,
\ 'repeat_count': 1
\ },
\ 'displayline' : {
\ 'backward' : 'gk',
\ 'forward' : 'gj',
\ },
\
\ 'char' : { 'backward' : 'h',
\ 'forward' : 'l',
\ 'repeat_if_count' : 1,
\ 'repeat_count': 1
\ },
\
\ 'word' : {
\ 'backward' : 'b',
\ 'forward' : 'w',
\ 'repeat_if_count' : 1,
\ 'repeat_count': 1
\ },
\ 'fullword' : { 'backward' : 'B',
\ 'forward' : 'W',
\ 'repeat_if_count' : 1,
\ 'repeat_count': 1
\ },
\ 'wordend' : { 'backward' : 'ge',
\ 'forward' : 'e',
\ 'repeat_if_count' : 1,
\ 'repeat_count': 1
\ },
\
\ 'pos' : { 'backward' : '<C-i>', 'forward' : '<C-o>' },
\
\ 'page' : { 'backward' : '<C-u>', 'forward' : '<C-d>' },
\ 'pagefull' : { 'backward' : '<C-b>', 'forward' : '<C-f>' },
\
\ 'undo' : { 'backward' : 'u', 'forward' : '<C-r>', 'direction' : 1 },
\
\ 'linescroll' : { 'backward' : '<C-e>', 'forward' : '<C-y>' },
\ 'columnscroll' : { 'backward' : 'zh', 'forward' : 'zl' },
\ 'columnsscroll' : { 'backward' : 'zH', 'forward' : 'zL' },
\
\ 'vsplit' : { 'backward' : '<C-w><', 'forward' : '<C-w>>' },
\ 'hsplit' : { 'backward' : '<C-w>-', 'forward' : '<C-w>+' },
\
\ 'arg' : { 'backward' : '[a', 'forward' : ']a'},
\ 'buffer' : { 'backward' : '[b', 'forward' : ']b'},
\ 'location' : { 'backward' : '[l', 'forward' : ']l'},
\ 'quickfix' : { 'backward' : '[q', 'forward' : ']q'},
\ 'tag' : { 'backward' : '[t', 'forward' : ']t'},
\
\ 'diagnostic' : { 'backward' : '[g', 'forward' : ']g'},
\ }Remark: The TtFf motion corresponds to the e, f motions.
The entry can be used to specify the options for that motion (i.e.: repeat_if_count, repeat_count, direction).
The following is an example of usage with lazy.nvim:
local motions = {
para = { backward = "{", forward = "}" },
sentence = { backward = "(", forward = ")" },
change = { backward = "g,", forward = "g;" },
class = { backward = "[[", forward = "]]" },
classend = { backward = "[]", forward = "][" },
method = { backward = "[m", forward = "]m" },
methodend = { backward = "[M", forward = "]M" },
line = { backward = "k", forward = "j", repeat_if_count = 1, repeat_count = 1 },
char = { backward = "h", forward = "l", repeat_if_count = 1, repeat_count = 1 },
word = { backward = "b", forward = "w", repeat_if_count = 1, repeat_count = 1 },
fullword = { backward = "B", forward = "W", repeat_if_count = 1, repeat_count = 1 },
wordend = { backward = "ge", forward = "e", repeat_if_count = 1, repeat_count = 1 },
pos = { backward = "<C-i>", forward = "<C-o>" },
page = { backward = "<C-u>", forward = "<C-d>" },
pagefull = { backward = "<C-b>", forward = "<C-f>" },
undo = { backward = "u", forward = "<C-r>", direction = 1 },
linescroll = { backward = "<C-e>", forward = "<C-y>" },
charscroll = { backward = "zh", forward = "zl" },
vsplit = { backward = "<C-w><", forward = "<C-w>>" },
hsplit = { backward = "<C-w>-", forward = "<C-w>+" },
arg = { backward = "[a", forward = "]a" },
buffer = { backward = "[b", forward = "]b" },
location = { backward = "[l", forward = "]l" },
quickfix = { backward = "[q", forward = "]q" },
tag = { backward = "[t", forward = "]t" },
diagnostic = { backward = "[g", forward = "]g" },
}
local leap_motions = {
leap_fwd = {
backward = "<Plug>(leapbackward)",
forward = "<Plug>(leapforward)",
motion = "s",
motion_plug = "<Plug>(leap-forward-to)",
},
leap_bck = {
backward = "<Plug>(leapforward)",
forward = "<Plug>(leapbackward)",
motion = "S",
motion_plug = "<Plug>(leap-backward-to)",
},
}
return {
"vds2212/vim-remotions",
event = { "BufRead", "BufWinEnter", "BufNewFile" },
config = function()
if vim.g.loaded_leap then
vim.g.remotions_motions = vim.tbl_deep_extend("error", motions, leap_motions)
else
vim.g.remotions_motions = motions
end
end,
}Note: If using leap.nvim, you'll need to manually set vim.g.loaded_leap until this leap issue is resolved.
The direction of the repeated motion can be configured using the g:remotions_direction
" Set the direction of the repetition to the direction of the initial move (the Vim default):
let g:remotions_direction = 0" Set the direction of the repetition to the direction of the document:
let g:remotions_direction = 1Remark: This g:remotions_direction setting is overridden by the direction option of the motion if set.
Some motions support count (e.g. i, j, }, etc.). E.g.: 2j make the cursor go 2 lines below.
When a motion is repeated via ; or , the original count is not take into consideration.
This is also the Vim default for the f and t motions.
If you want that the original count is taken in consideration:
" Make the ; and , key also repeat the count when supported by the original move
let g:remotions_repeat_count = 1Remark: This g:remotions_repeat_count is overridden by the repeat_count option of the motion if set.
Motion plugins are special because:
- They are triggered by a key or a key combination that is not the one used to repeat the action
- The hijacking of the trigger could requires some hint
Here are the configurations proposed for some of the popular ones:
Here is a solution for repeating the vim-easymotion motions:
let g:remotions_motions = {
\ 'leap_fwd' : {
\ 'backward' : '<Plug>(easymotion-prev)',
\ 'forward' : '<Plug>(easymotion-next)',
\ 'motion': '<leader><leader>',
\ 'motion_plug' : '<Plug>(easymotion-prefix)'
\ },
\ }Here is a solution for repeating the leap.nvim motions:
lua require('leap').add_repeat_mappings('<Plug>(leapforward)', '<Plug>(leapbackward)')
let g:remotions_motions = {
\ 'leap_fwd' : {
\ 'backward' : '<Plug>(leapbackward)',
\ 'forward' : '<Plug>(leapforward)',
\ 'motion': 's',
\ 'motion_plug' : '<Plug>(leap-forward-to)'
\ },
\ 'leap_bck' : {
\ 'backward' : '<Plug>(leapbackward)',
\ 'forward' : '<Plug>(leapforward)',
\ 'motion': 's',
\ 'motion_plug' : '<Plug>(leap-backward-to)'
\ },
\ }Here is a solution for repeating the vim-sneak motions:
let g:remotions_motions = {
\ 'sneak_fwd' : {
\ 'backward' : '<Plug>Sneak_,',
\ 'forward' : '<Plug>Sneak_;',
\ 'motion': 's',
\ 'motion_plug' : '<Plug>Sneak_s'
\ },
\ 'sneak_bckwd' : {
\ 'backward' : '<Plug>Sneak_,',
\ 'forward' : '<Plug>Sneak_;',
\ 'motion': 's',
\ 'motion_plug' : '<Plug>Sneak_S'
\ },The repmo.vim or its new version is mature plugin used by a number of people.
In my experience repmo works well for builtin commands.
But when the builtin command are overridden for a specific filetype (e.g. ]m for the python filetype).
or when it is overridden by a filetype plugin (e.g. ]m with pythonsense)
I had difficulty to make it working (I failed).
I'm wondering also how repmo handle commands that are overridden by two different plugins for two different filetype.
These difficulty made me write remotions.
If one of the previous statements about repmo is imprecise or wrong I'll more than happy to rectify the text. Raise an issue and I will adapt the text of this readme file.
The repeatable-motions.vim is a mature plugin.
It introduces four keys to repeat the motions:
- Ctrl j, Ctrl k for the vertical motions.
- Ctrl h, Ctrl l for the horizontal motions.
In my experience repeatable-motions works well for builtin commands. It supports a number of builtin motions.
But when the builtin command are overridden for a specific filetype (e.g. ]m for the python filetype).
or when it is overridden by a filetype plugin (e.g. ]m with pythonsense)
I had difficulty to make it working (I failed).
If one of the previous statements about repeatable-motions is imprecise or wrong I'll more than happy to rectify the text. Raise an issue and I will adapt the text of this readme file.
According to the documentation repeat-motion
only repeat a set of predefined builtin motions (i.e. k, j, h, l, w, b, W, B, e, E, ge, gE)