Houl/repmo-vim

Support for [[, ]], [], ][, [m, ]m, ]M, [M of ftplugins

Closed this issue · 30 comments

More and more ftplugins in $VIMRUNTIME or third-party sources add ft-specific vertical motion mappings [[, ]], [], ][, [m, ]m, ]M, [M.

The builtin mappings work for curly bracket based languages such as java and c++:

// cpp written in K&R style
// - curly braces for namespaces, classes and funcs on separate lines,
// - but 'egyptian style braces' for everything else such as if, while, switch, etc.
class Scalar
{
  friend Scalar operator*(const Scalar&, const Scalar&);
}; // []

class Matrix
{ // [[

  friend Vector operator-(const Vector&, const Vector&)
  {
  // ...
  } // [M

  friend Vector operator*(const Matrix&, const Vector&)
  { // [m

  while x>0 do { // [{
  // CURSOR <--------------------------------------
  } // ]}

  } // ]M

  friend Vector operator+(const Vector&, const Vector&)
  { // ]m
  // ...
  }
}; // ][

class Vector
{ // ]]
  friend Vector operator*(const Matrix&, const Vector&);
};

However, if you do not follow certain coding style, they won't work as expected. Also your cursor is not located on the actual start of the block. See for example the suggestion in :h section, the c++ plugin lh-cpp or the discussion for c# on stackoverflow.
For javascript there exists the plugin https://github.com/okcompute/vim-javascript-motions which does not follow the conventions of the builtin square bracket commands and defines a different set (overwriting some other builtin mappings such as [I,]I & [D,]D and uses ]],[[ with a different meaning).

Consider following languages with ftplugins which have overwritten the builtin ones:

$VIMRUNTIME/ftplugin/*

Third Party

Is it possible to make them work together with repmo-vim? IMHO the pinky finger action is the weakness of these mappings.

Houl commented

I've looked at the python script.

I can tell you, it can be done, but it's cumbersome. It's less than ideal to use the mapping definitions as-is. Ideally, mappings come in form of <Plug> mappings.

Problem:
I would like to enhance the mappings outside of the original scripts, but this requires the <SID> to be known.

Anyway ...
Basically, one can just make the original right-hand-sides arguments of repmo#Key() (quoted with string()):

" original mappings from the Python ft-plugin (as of Vim 7.4.2251):
" nnoremap <silent> <buffer> ]] :call <SID>Python_jump('n', '\v%$\|^(class\|def)>', 'W')<cr>
" nnoremap <silent> <buffer> [[ :call <SID>Python_jump('n', '\v^(class\|def)>', 'Wb')<cr>

" repmo mappings (<silent> omitted, but could be added):
nnoremap <expr><buffer> ]] repmo#Key(':<C-U>call <SID>Python_jump(''n'', ''\v%$\|^(class\|def)>'', ''W'')<cr>', ':<C-U>call <SID>Python_jump(''n'', ''\v^(class\|def)>'', ''Wb'')<cr>')
nnoremap <expr><buffer> [[ repmo#Key(':<C-U>call <SID>Python_jump(''n'', ''\v^(class\|def)>'', ''Wb'')<cr>', ':<C-U>call <SID>Python_jump(''n'', ''\v%$\|^(class\|def)>'', ''W'')<cr>')

" script-local function from ftplugin\python.vim copied for convience (with bug fix):
fun! <SID>Python_jump(mode, motion, flags) range
    let cnt = v:count1
    if a:mode == 'x'
	normal! gv
    endif
    normal! 0
    mark '
    while cnt > 0
	call search(a:motion, a:flags)
	let cnt = cnt - 1
    endwhile

    normal! ^
endfun

Also I've added <C-U> to eat the count.

The Python script I used for my example contained a bug: the v:count1 is always 1, because a previous :normal resets the count (happens to be the same in the current version). Looks like people don't use the count much -- but repmo doesn't take action at all without given count.

Thanks for your response.

vimtex

The plugin vimtex uses <plug> mappings

https://github.com/lervag/vimtex/blob/master/autoload/vimtex/motion.vim#L21-L36
https://github.com/lervag/vimtex/blob/master/autoload/vimtex.vim#L405-L416

and therefore, if I got it right, following setup for latex files should be fine:

" repeat the last [count]motion or the last zap-key:
map <expr> ; repmo#LastKey(';')|sunmap ;
map <expr> , repmo#LastRevKey(',')|sunmap ,

" add these mappings when repeating with `;' or `,':
noremap <expr> f repmo#ZapKey('f')|sunmap f
noremap <expr> F repmo#ZapKey('F')|sunmap F
noremap <expr> t repmo#ZapKey('t')|sunmap t
noremap <expr> T repmo#ZapKey('T')|sunmap T

noremap <expr> <C-E> repmo#SelfKey('<C-E>', '<C-Y>')
noremap <expr> <C-Y> repmo#SelfKey('<C-Y>', '<C-E>')

nnoremap <expr><buffer> ]] repmo#Key('<plug>(vimtex-]])', '<plug>(vimtex-[[)')
nnoremap <expr><buffer> [[ repmo#Key('<plug>(vimtex-[[)', '<plug>(vimtex-]])')
nnoremap <expr><buffer> ][ repmo#Key('<plug>(vimtex-][)', '<plug>(vimtex-[])')
nnoremap <expr><buffer> [] repmo#Key('<plug>(vimtex-[])', '<plug>(vimtex-][)')
\documentclass{article}

\begin{document}

\chapter{section 1)
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet.

\chapter{section 2)
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet.

\chapter{section 3)
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet.

\chapter{section 4)
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet.

\chapter{section 5)
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet.

\end{document}

However, repeating ]] with ; and , does not work. What do I still miss?

Python

I guess I would hope to convince the new maintainer of the python script to use <plug> mappings. Unfortunately, all ftplugin files are affected by this:

/Applications/MacVim.app/Contents/Resources/vim/runtime/ftplugin
❯ rg '<buffer> ]]'
abaqus.vim
70:noremap <silent><buffer> ]] /^\*\a<CR>:nohlsearch<CR>
85:let b:undo_ftplugin .= "|unmap <buffer> [[|unmap <buffer> ]]"

cobol.vim
64:            \ " | sil! exe 'unmap  <buffer> ]]'" .
84:    noremap <silent> <buffer> ]] m':call search('\c^\%(\s*\<Bar>.\{6\}\s\+\)\zs[A-Za-z0-9-]\+\s\+\%(division\<Bar>section\)\.','W')<CR>

context.vim
63:nnoremap <silent><buffer> ]] :<C-U>call <SID>move_around(v:count1, "beginsection", "W",  v:false) <CR>
64:vnoremap <silent><buffer> ]] :<C-U>call <SID>move_around(v:count1, "beginsection", "W",  v:true)  <CR>

eiffel.vim
58:  nnoremap <silent> <buffer> ]] :<C-U>call <SID>DoMotion(sections, v:count1, 'W')<CR>
59:  xnoremap <silent> <buffer> ]] :<C-U>exe "normal! gv"<Bar>call <SID>DoMotion(sections, v:count1, 'W')<CR>
88:    \ "| silent! execute 'unmap <buffer> [[' | silent! execute 'unmap <buffer> ]]'" .

hamster.vim
38:noremap <silent><buffer> ]] :call search('^\s*sub\>', "W")<CR>

j.vim
49:sunmap <buffer> ]]
60:let b:undo_ftplugin .= ' | silent! execute "unmap <buffer> ]]"'

mf.vim
45:nnoremap <silent><buffer> ]] :<C-U>call <SID>move_around(v:count1, "beginsection", "W",  v:false) <CR>
46:vnoremap <silent><buffer> ]] :<C-U>call <SID>move_around(v:count1, "beginsection", "W",  v:true)  <CR>

mp.vim
54:nnoremap <silent><buffer> ]] :<C-U>call <SID>move_around(v:count1, "beginsection", "W",  v:false) <CR>
55:vnoremap <silent><buffer> ]] :<C-U>call <SID>move_around(v:count1, "beginsection", "W",  v:true)  <CR>

python.vim
35:execute "nnoremap <silent> <buffer> ]] :call <SID>Python_jump('n', '". b:next_toplevel."', 'W')<cr>"
44:execute "onoremap <silent> <buffer> ]] :call <SID>Python_jump('o', '". b:next_toplevel."', 'W')<cr>"
53:execute "xnoremap <silent> <buffer> ]] :call <SID>Python_jump('x', '". b:next_toplevel."', 'W')<cr>"

ruby.vim
175:  nnoremap <silent> <buffer> ]] :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>','rubyModule\<Bar>rubyClass','','n')<CR>
179:  xnoremap <silent> <buffer> ]] :<C-U>call <SID>searchsyn('\<\%(class\<Bar>module\)\>','rubyModule\<Bar>rubyClass','','v')<CR>
184:        \."| sil! exe 'unmap <buffer> [[' | sil! exe 'unmap <buffer> ]]' | sil! exe 'unmap <buffer> []' | sil! exe 'unmap <buffer> ]['"

rust.vim
108:nnoremap <silent> <buffer> ]] :call rust#Jump('n', 'Forward')<CR>
110:xnoremap <silent> <buffer> ]] :call rust#Jump('v', 'Forward')<CR>
112:onoremap <silent> <buffer> ]] :call rust#Jump('o', 'Forward')<CR>
177:		\|nunmap <buffer> ]]
179:		\|xunmap <buffer> ]]
181:		\|ounmap <buffer> ]]

vim.vim
42:nnoremap <silent><buffer> ]] m':call search('^\s*fu\%[nction]\>', "W")<CR>
43:vnoremap <silent><buffer> ]] m':<C-U>exe "normal! gv"<Bar>call search('^\s*fu\%[nction]\>', "W")<CR>

zimbu.vim
139:nnoremap <silent> <buffer> ]] m`:call ZimbuGoEndBlock()<CR>
Houl commented

<Plug> mappings require enabled remapping:

nmap <expr><buffer> ]] repmo#Key('<plug>(vimtex-]])', '<plug>(vimtex-[[)')

Thanks for the help. However, it is still not working. I have also moved the nmap into ~/.vim/after/ftplugins/tex.vim:

nmap <expr><buffer> ]] repmo#Key('<plug>(vimtex-]])', '<plug>(vimtex-[[)')
nmap <expr><buffer> [[ repmo#Key('<plug>(vimtex-[[)', '<plug>(vimtex-]])')
nmap <expr><buffer> ][ repmo#Key('<plug>(vimtex-][)', '<plug>(vimtex-[])')
nmap <expr><buffer> [] repmo#Key('<plug>(vimtex-[])', '<plug>(vimtex-][)')

And running :verbose nmap ]] within the tex file indicates the correct definition

n  ]]           @repmo#Key('<Plug>(vimtex-]])', '<Plug>(vimtex-[[)')
        Last set from ~/.vim/after/ftplugin/tex.vim  

and :verbose nmap ; returns

nox;             repmo#LastKey(';')
        Last set from ~/.vim/vimrc    

But the movement with ; and , does not work.

Houl commented

Maybe check if a count works in the first place?

The implementation functions of (vimtex-]]) and (vimtex-[[) don't make use of the count.
Or: if a count is given, it's used incorrectly.

Indeed, using 1]] and then ; and , works!

So vimtex must be fixed. Can you describe what the author of vimtex should do to fix it?

Houl commented

1st: also use :<C-U> with Normal mode mappings and/or define functions with range.
2nd: do something with the count.

Houl commented

... make sure to ask for v:count or v:count1 before the first :normal.

Houl commented

Btw: actually, it doesn't make sense to define <Plug> mappings with <buffer>. Because: <Plug> mappings should have distinct names (and usually have) and when there are many ft=tex buffers, then these can share the same global <Plug> mappings. Also makes it slightly easier to redefine the mappings for debugging (no reloading of all buffers necessary). But this is just my opinion.

Houl commented

To the original issue: when it's ok to have repmo mappings inside the original ftplugin script, then I'd prefer <SID> mappings over <Plug> mappings (because the <Plug> mappings are of no use outside).

I guess not everyone wants to use the plugin repmo and therefore the original ftplugin scripts should ideally written in a way that one can facilitate <plug> mappings in the vimrc of a vim user.

There is one thing I do not like: when all original ftplugin scripts are updated to use mappings with support for counts, the user has to define nmap <expr><buffer> ]] repmo#Key('... for every language individually. Is there any chance to generalize this? So the user can enable this for all languages which have those mappings or will get those mappings.

Houl commented

Then I'd consider to have a global mapping

nmap <expr> ]] repmo#Key('<Plug>(motion-]])')

and then let each ftplugin define a different nnoremap <buffer> <Plug>(motion-]]) ....
Bonus: the user can define a global fallback motion for <Plug>(motion-]]).

Houl commented

Variant: The original (maybe global) <Plug> mapping could even be kept intact by making <Plug>(motion-]]) refer to it.

I haven't yet managed to get your python suggestion to work.
I have changed $VIMRUNTIME/ftplugin/python.vim as following:

diff --git a/python.vim.bk b/python.vim
index d52a338..a1c275f 100644
--- a/python.vim.bk
+++ b/python.vim
@@ -32,56 +32,44 @@ let b:prev='\v^\s*(class\|def\|async def)>'
 let b:next_end='\v\S\n*(%$\|^\s*(class\|def\|async def)\|^\S)'
 let b:prev_end='\v\S\n*(^\s*(class\|def\|async def)\|^\S)'
 
-execute "nnoremap <silent> <buffer> ]] :call <SID>Python_jump('n', '". b:next_toplevel."', 'W')<cr>"
-execute "nnoremap <silent> <buffer> [[ :call <SID>Python_jump('n', '". b:prev_toplevel."', 'Wb')<cr>"
-execute "nnoremap <silent> <buffer> ][ :call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
-execute "nnoremap <silent> <buffer> [] :call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
-execute "nnoremap <silent> <buffer> ]m :call <SID>Python_jump('n', '". b:next."', 'W')<cr>"
-execute "nnoremap <silent> <buffer> [m :call <SID>Python_jump('n', '". b:prev."', 'Wb')<cr>"
-execute "nnoremap <silent> <buffer> ]M :call <SID>Python_jump('n', '". b:next_end."', 'W', 0)<cr>"
-execute "nnoremap <silent> <buffer> [M :call <SID>Python_jump('n', '". b:prev_end."', 'Wb', 0)<cr>"
-
-execute "onoremap <silent> <buffer> ]] :call <SID>Python_jump('o', '". b:next_toplevel."', 'W')<cr>"
-execute "onoremap <silent> <buffer> [[ :call <SID>Python_jump('o', '". b:prev_toplevel."', 'Wb')<cr>"
-execute "onoremap <silent> <buffer> ][ :call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
-execute "onoremap <silent> <buffer> [] :call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
-execute "onoremap <silent> <buffer> ]m :call <SID>Python_jump('o', '". b:next."', 'W')<cr>"
-execute "onoremap <silent> <buffer> [m :call <SID>Python_jump('o', '". b:prev."', 'Wb')<cr>"
-execute "onoremap <silent> <buffer> ]M :call <SID>Python_jump('o', '". b:next_end."', 'W', 0)<cr>"
-execute "onoremap <silent> <buffer> [M :call <SID>Python_jump('o', '". b:prev_end."', 'Wb', 0)<cr>"
-
-execute "xnoremap <silent> <buffer> ]] :call <SID>Python_jump('x', '". b:next_toplevel."', 'W')<cr>"
-execute "xnoremap <silent> <buffer> [[ :call <SID>Python_jump('x', '". b:prev_toplevel."', 'Wb')<cr>"
-execute "xnoremap <silent> <buffer> ][ :call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
-execute "xnoremap <silent> <buffer> [] :call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
-execute "xnoremap <silent> <buffer> ]m :call <SID>Python_jump('x', '". b:next."', 'W')<cr>"
-execute "xnoremap <silent> <buffer> [m :call <SID>Python_jump('x', '". b:prev."', 'Wb')<cr>"
-execute "xnoremap <silent> <buffer> ]M :call <SID>Python_jump('x', '". b:next_end."', 'W', 0)<cr>"
-execute "xnoremap <silent> <buffer> [M :call <SID>Python_jump('x', '". b:prev_end."', 'Wb', 0)<cr>"
-
 if !exists('*<SID>Python_jump')
-  fun! <SID>Python_jump(mode, motion, flags, ...) range
-      let l:startofline = (a:0 >= 1) ? a:1 : 1
-
-      if a:mode == 'x'
-          normal! gv
-      endif
-
-      if l:startofline == 1
-          normal! 0
-      endif
-
-      let cnt = v:count1
-      mark '
-      while cnt > 0
-          call search(a:motion, a:flags)
-          let cnt = cnt - 1
-      endwhile
+  function! <SID>Python_jump(mode, motion, flags) range
+    let cnt = v:count1
+    if a:mode == 'x'
+      normal! gv
+    endif
+    normal! 0
+    mark '
+    while cnt > 0
+      call search(a:motion, a:flags)
+      let cnt = cnt - 1
+    endwhile
+    normal! ^
+  endfunction
+endif
 
-      if l:startofline == 1
-          normal! ^
+for mode in ['n', 'o', 'x']
+  execute mode."noremap <silent> <plug>[M-motion :<C-U>call <SID>Python_jump('".mode."', '". b:prev_end."', 'Wb', 0)<cr>"
+  execute mode."noremap <silent> <plug>[[-motion :<C-U>call <SID>Python_jump('".mode."', '". b:prev_toplevel."', 'Wb')<cr>"
+  execute mode."noremap <silent> <plug>[]-motion :<C-U>call <SID>Python_jump('".mode."', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
+  execute mode."noremap <silent> <plug>[m-motion :<C-U>call <SID>Python_jump('".mode."', '". b:prev."', 'Wb')<cr>"
+  execute mode."noremap <silent> <plug>]M-motion :<C-U>call <SID>Python_jump('".mode."', '". b:next_end."', 'W', 0)<cr>"
+  execute mode."noremap <silent> <plug>][-motion :<C-U>call <SID>Python_jump('".mode."', '". b:next_endtoplevel."', 'W', 0)<cr>"
+  execute mode."noremap <silent> <plug>]]-motion :<C-U>call <SID>Python_jump('".mode."', '". b:next_toplevel."', 'W')<cr>"
+  execute mode."noremap <silent> <plug>]m-motion :<C-U>call <SID>Python_jump('".mode."', '". b:next."', 'W')<cr>"
+endfor
+
+if !exists("no_plugin_maps") && !exists("no_python_maps")
+  for key1 in ['[', ']']
+    for key2 in ['M', '[', ']', 'm']
+      let keys = key1.key2
+      if !hasmapto('<Plug>'.keys.'-motion')
+        for mode in ['n', 'o', 'x']
+          execute mode."map ".keys." <plug>".keys."-motion"
+        endfor
       endif
-  endfun
+    endfor
+  endfor
 endif
 
 if has("browsefilter") && !exists("b:browsefilter")

My ~/.vim/after/ftplugin/python.vim looks like

for mode in ['n', 'o', 'x']
    execute mode."map <expr><buffer> ]] repmo#SelfKey('<plug>]]-motion', '<plug>[[-motion')"
    execute mode."map <expr><buffer> [[ repmo#SelfKey('<plug>[[-motion', '<plug>]]-motion')"
    execute mode."map <expr><buffer> ][ repmo#SelfKey('<plug>][-motion', '<plug>[]-motion')"
    execute mode."map <expr><buffer> [] repmo#SelfKey('<plug>[]-motion', '<plug>][-motion')"
    execute mode."map <expr><buffer> ]m repmo#SelfKey('<plug>]m-motion', '<plug>[m-motion')"
    execute mode."map <expr><buffer> [m repmo#SelfKey('<plug>[m-motion', '<plug>]m-motion')"
    execute mode."map <expr><buffer> ]M repmo#SelfKey('<plug>]M-motion', '<plug>[M-motion')"
    execute mode."map <expr><buffer> [M repmo#SelfKey('<plug>[M-motion', '<plug>]M-motion')"
endfor

the working setup for tex files with 1]], and then ; and , in ~/.vim/after/ftplugin/tex.vim

nmap <expr><buffer> ]] repmo#Key('<plug>(vimtex-]])', '<plug>(vimtex-[[)')
nmap <expr><buffer> [[ repmo#Key('<plug>(vimtex-[[)', '<plug>(vimtex-]])')
nmap <expr><buffer> ][ repmo#Key('<plug>(vimtex-][)', '<plug>(vimtex-[])')
nmap <expr><buffer> [] repmo#Key('<plug>(vimtex-[])', '<plug>(vimtex-][)')

and finally my vimrc:

map <expr> ; repmo#LastKey(';')|sunmap ;
map <expr> , repmo#LastRevKey(',')|sunmap ,

" add these mappings when repeating with `;' or `,':
noremap <expr> f repmo#ZapKey('f')|sunmap f
noremap <expr> F repmo#ZapKey('F')|sunmap F
noremap <expr> t repmo#ZapKey('t')|sunmap t
noremap <expr> T repmo#ZapKey('T')|sunmap T

noremap <expr> <C-E> repmo#SelfKey('<C-E>', '<C-Y>')
noremap <expr> <C-Y> repmo#SelfKey('<C-Y>', '<C-E>')

Verifying the setup in a python file:

x  ]]           @repmo#SelfKey('<Plug>]]-motion', '<Plug>[[-motion')
        Last set from ~/.vim/after/ftplugin/python.vim
o  ]]           @repmo#SelfKey('<Plug>]]-motion', '<Plug>[[-motion')
        Last set from ~/.vim/after/ftplugin/python.vim
n  ]]           @repmo#SelfKey('<Plug>]]-motion', '<Plug>[[-motion')
        Last set from ~/.vim/after/ftplugin/python.vim
n  ]]            <Plug>]]-motion
        Last set from /Applications/MacVim.app/Contents/Resources/vim/runtime/ftplugin/python.vim  
nox;             repmo#LastKey(';')
        Last set from ~/.vim/vimrc   

And counts also work! The remaining issue seems to be getting repmo to work.

Houl commented

Yep, it's not yet resembling the idea. The idea was (here only for [[ in Normal mode):

One global, uniquely named Python specific <Plug> mapping, eg :nnoremap <Plug>(python-[[) :<C-U>call PythonNormalMotion('[[')<CR> (the right-hand-side is some dummy implementation). It's what we have right now in ftplugin scripts. (It almost doesn't matter if it's global or buffer-local, but all the buffer-local copies are wasted imho). And instead of a <Plug> mapping, this could also be an <SID> mapping (eg <SID>[[), as the interface is <Plug>[[-motion below.

One buffer-local <Plug> mapping, canonically named <Plug>[[-motion (I even like that name for now, it cannot be confused with a filetype mapping): :nmap <buffer> <Plug>[[-motion <Plug>(python-[[). Each forecoming ftplugin is nice enough to define this mapping in a similar way. repmo is not involved yet.

The user then has the following options for his vimrc:
Map :nmap [[ <Plug>[[-motion. This will make [[ do the right thing if supported by the ftplugin. He might also want to define a global fallback mapping: :nnoremap <Plug>[[-motion [[.
When the user wants to use repmo he can define :nmap <expr> [[ repmo#Key('<Plug>[[-motion', '<Plug>]]-motion').

Houl commented

Btw: do not use :nmap <expr> [[ repmo#SelfKey('<Plug>[[-motion', '<Plug>]]-motion'). SelfKey is only for :nnoremap <expr> [[ repmo#SelfKey('[[', ']]'), ie when the left-hand-side occurs in the argument (maybe it wasn't clear enough from the readme example).

Houl commented

Modified ftplugin/python.vim, this also introduces g:no_bracket_maps.
User's supporting vimrc settings

Thank you very much!

I can repeat now 1]] with ; and , in a python file. Afterwards I can switch to 1]m and repeat.
Also fFtT work with ; and ,.

However, I was hoping I do not need to add a count of 1.

Also your suggested mappings for <C-E/Y> in the README.md cannot be repeated:

noremap <expr> <C-E> repmo#SelfKey('<C-E>', '<C-Y>')
noremap <expr> <C-Y> repmo#SelfKey('<C-Y>', '<C-E>')

Hi,

I could resolve my issue when providing no counts by changing v:count to v:count1 in autoload/repmo.vim:

diff --git a/repmo.vim.bk b/repmo.vim
index 7923657..bb40ac1 100644
--- a/repmo.vim.bk
+++ b/repmo.vim
@@ -11,15 +11,15 @@ if !exists("s:last")
 endif
 
 func! repmo#Key(key, revkey) "{{{
-    if v:count >= 1
-	call extend(s:last, {'repmo': 1, 'key': a:key, 'revkey': a:revkey, 'count': v:count, 'remap': 1})
+    if v:count1 >= 1
+	call extend(s:last, {'repmo': 1, 'key': a:key, 'revkey': a:revkey, 'count': v:count1, 'remap': 1})
     endif
     return a:key
 endfunc "}}}
 
 func! repmo#SelfKey(key, revkey) "{{{
-    if v:count >= 1
-	call extend(s:last, {'repmo': 1, 'key': a:key, 'revkey': a:revkey, 'count': v:count, 'remap': 0})
+    if v:count1 >= 1
+	call extend(s:last, {'repmo': 1, 'key': a:key, 'revkey': a:revkey, 'count': v:count1, 'remap': 0})
 	exec "noremap <Plug>(repmo-lastkey) \<C-V>". a:key
 	exec "noremap <Plug>(repmo-lastrevkey) \<C-V>". a:revkey
     endif
@@ -32,8 +32,8 @@ func! repmo#LastKey(zaprepkey) "{{{
 	return a:zaprepkey
     endif
     let lastkey = get(s:last, 'remap', 1) ? get(s:last, 'key', '') : "\<Plug>(repmo-lastkey)"
-    if v:count >= 1
-	let s:last.count = v:count
+    if v:count1 >= 1
+	let s:last.count = v:count1
 	return lastkey
     else
 	return get(s:last, 'count', ''). lastkey
@@ -46,8 +46,8 @@ func! repmo#LastRevKey(zaprepkey) "{{{
 	return a:zaprepkey
     endif
     let lastrevkey = get(s:last, 'remap', 1) ? get(s:last, 'revkey', '') : "\<Plug>(repmo-lastrevkey)"
-    if v:count >= 1
-	let s:last.count = v:count
+    if v:count1 >= 1
+	let s:last.count = v:count1
 	return lastrevkey
     else
 	return get(s:last, 'count', ''). lastrevkey

However, this means if I use counts e.g. 2fe, the count is forgotten when repeating.

Houl commented

I did intentionally put "... for which a count was given" in the description line.

Any chance that you make this optional?

Houl commented

First thing I'd try is (in the ftplugin) (untested) map <buffer><expr><script> <Plug>[m-motion v:count==0 ? '1<SID>([m)' : '<SID>([m)'. (not working)

Houl commented

I think it will cause trouble. I will not attempt to change repmo now to support this.

Too bad. I would have appreciated it. To be honest I use rarely counts and prefer to repeat the motion.

Also I am not entirely convinced that your decision fits into the idea of the plugin repmo itself. I am a bit surprised that it seems to be rather difficult to implement this. Because from a naive point of view you just have to set the variable, which keeps track of the count, to one if none is given.

Houl commented

Weird, I remembered problems, but they somehow vanished.
Ok, then put :let g:repmo_require_count = 0 in your vimrc and enjoy the update.

Thanks for your reconsideration. If this works reliable, I see no reason to make this the default behavior. But I don't mind adding a line to my vimrc to disable requiring a count.

I just noticed that your suggested python file given in the gist misses for the normal mode commands <C-U>:

- execute "nnoremap <silent> <SID>(]]) :call <SID>Python_jump('n', '". b:next_toplevel."', 'W')<cr>"
- execute "nnoremap <silent> <SID>([[) :call <SID>Python_jump('n', '". b:prev_toplevel."', 'Wb')<cr>"
- execute "nnoremap <silent> <SID>(][) :call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
- execute "nnoremap <silent> <SID>([]) :call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
- execute "nnoremap <silent> <SID>(]m) :call <SID>Python_jump('n', '". b:next."', 'W')<cr>"
- execute "nnoremap <silent> <SID>([m) :call <SID>Python_jump('n', '". b:prev."', 'Wb')<cr>"
- execute "nnoremap <silent> <SID>(]M) :call <SID>Python_jump('n', '". b:next_end."', 'W', 0)<cr>"
- execute "nnoremap <silent> <SID>([M) :call <SID>Python_jump('n', '". b:prev_end."', 'Wb', 0)<cr>"
+ execute "nnoremap <silent> <SID>(]]) :<C-U>call <SID>Python_jump('n', '". b:next_toplevel."', 'W')<cr>"
+ execute "nnoremap <silent> <SID>([[) :<C-U>call <SID>Python_jump('n', '". b:prev_toplevel."', 'Wb')<cr>"
+ execute "nnoremap <silent> <SID>(][) :<C-U>call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
+ execute "nnoremap <silent> <SID>([]) :<C-U>call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
+ execute "nnoremap <silent> <SID>(]m) :<C-U>call <SID>Python_jump('n', '". b:next."', 'W')<cr>"
+ execute "nnoremap <silent> <SID>([m) :<C-U>call <SID>Python_jump('n', '". b:prev."', 'Wb')<cr>"
+ execute "nnoremap <silent> <SID>(]M) :<C-U>call <SID>Python_jump('n', '". b:next_end."', 'W', 0)<cr>"
+ execute "nnoremap <silent> <SID>([M) :<C-U>call <SID>Python_jump('n', '". b:prev_end."', 'Wb', 0)<cr>"

Also b:undo_ftplugin should actually be prepended:

-    let b:undo_ftplugin = (exists('b:undo_ftplugin') ? '|' : '').
+    let b:undo_ftplugin = (exists('b:undo_ftplugin') ? b:undo_ftplugin.'|' : '').

Also there were minor mistakes in the original file:

- execute "onoremap <silent> <SID>(][) :call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
- execute "onoremap <silent> <SID>([]) :call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
+ execute "onoremap <silent> <SID>(][) :call <SID>Python_jump('o', '". b:next_endtoplevel."', 'W', 0)<cr>"
+ execute "onoremap <silent> <SID>([]) :call <SID>Python_jump('o', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
- execute "xnoremap <silent> <SID>(][) :call <SID>Python_jump('n', '". b:next_endtoplevel."', 'W', 0)<cr>"
- execute "xnoremap <silent> <SID>([]) :call <SID>Python_jump('n', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
+ execute "xnoremap <silent> <SID>(][) :call <SID>Python_jump('x', '". b:next_endtoplevel."', 'W', 0)<cr>"
+ execute "xnoremap <silent> <SID>([]) :call <SID>Python_jump('x', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"

I've tried to keep the code block more compact by using a few for loops. My version of the new code lines in $VIMRUNTIME/ftplugin/python.vim look like.

if !exists('*<SID>Python_jump')
  function! <SID>Python_jump(mode, motion, flags) range
    let cnt = v:count1
    if a:mode == 'x'
      normal! gv
    endif
    normal! 0
    mark '
    while cnt > 0
      call search(a:motion, a:flags)
      let cnt = cnt - 1
    endwhile
    normal! ^
  endfunction
endif

for mode in ['n', 'o', 'x']
  if mode == 'n'
    let ctrlu='<C-U>'
  else
    let ctrlu=''
  endif
  execute mode.'nnoremap <silent> <SID>(]]) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:next_toplevel."', 'W')<cr>"
  execute mode.'nnoremap <silent> <SID>([[) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:prev_toplevel."', 'Wb')<cr>"
  execute mode.'nnoremap <silent> <SID>(][) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:next_endtoplevel."', 'W', 0)<cr>"
  execute mode.'nnoremap <silent> <SID>([]) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:prev_endtoplevel."', 'Wb', 0)<cr>"
  execute mode.'nnoremap <silent> <SID>(]m) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:next."', 'W')<cr>"
  execute mode.'nnoremap <silent> <SID>([m) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:prev."', 'Wb')<cr>"
  execute mode.'nnoremap <silent> <SID>(]M) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:next_end."', 'W', 0)<cr>"
  execute mode.'nnoremap <silent> <SID>([M) :'.ctrlu."call <SID>Python_jump('".mode."', '". b:prev_end."', 'Wb', 0)<cr>"
endfor

for keys in ['[M', ']M', '[m', ']m', '[[', ']]',  '[]', '][']
  execute 'map <buffer> <Plug>'.keys.'-motion <SID>('.keys.')'
endfor

if !exists('no_plugin_maps') && !exists('no_python_maps')
  for keys in ['[M', ']M', '[m', ']m', '[[', ']]',  '[]', '][']
    if !hasmapto('<plug>'.keys.'-motion')
      execute 'map <buffer> '.keys.' <plug>'.keys.'-motion|sunmap <buffer> '.keys
      let b:undo_ftplugin = (exists('b:undo_ftplugin') ? b:undo_ftplugin.'|' : '').'unmap <buffer> '.keys
    endif
  endfor
endif

If you think this is ok, I would send this to the current maintainer to get the changes into upstream.

Houl commented

Looks like a work in progress. Maybe continue here?

Yes, we can work on a separate repo to modify the default and selected third-party ftplugins so that they can be repeated by repmo.

I just want to list here a few additional plugin/ftplugin motions which haven't been mentioned yet:

matchit

$VIMRUNTIME/pack/dist/opt/matchit/plugin/matchit.vim adds following motions

nnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'n') <CR>
nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR>
vnoremap <silent> %  :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv``
vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv``
onoremap <silent> %  v:<C-U>call <SID>Match_wrapper('',1,'o') <CR>
onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR>
nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR>
nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W",  "n") <CR>
vmap [% <Esc>[%m'gv``
vmap ]% <Esc>]%m'gv``
onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR>
onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W",  "o") <CR>

They are useful to cycle through b:match_words, e.g. if, elseif, else, endif in vimscript:

if
...
elseif
...
elseif
...
else
...
endif

They also have to be modified.

matchup

Note, there is a new plugin called matchup which offers the same motion commands as matchit which are written as <plug> mappings, so you can already set them up to work together with repmo:

for keys in [ ['%', 'g%'], ['[%', ']%'] ]
  execute 'map <expr> '.keys[0]." repmo#Key('<plug>(matchup-".keys[0].")', '<plug>(matchup-".keys[1].")')|sunmap ".keys[0]
  execute 'map <expr> '.keys[1]." repmo#Key('<plug>(matchup-".keys[1].")', '<plug>(matchup-".keys[0].")')|sunmap ".keys[1]
endfor

Move to next comment ]" (sql/vim), ]# (hamster), ]- (eiffel)

Inspired by the builtin motions ]*, ]/ which move the cursor to the next c style comment block, a few ftplugins add similar motions to easily jump to the next comment block in the respective language.

The builtin mappings ]*, ]/ can be made repeatable by:

    for keys in [ ['[*', ']*'], ['[/', ']/'], ['[#', ']#'] ]
        " [#, ]# c preprocessor (couldn't this be done with '%,g%,[%,]%' ?)
        " [*, ]* or [/, ]/ block comment used in c,c++,c#,Css,D,Go,Java,JavaScript,Objective-c,PHP,Rust,Sql,Swift,...
        execute 'noremap <expr> '.keys[0]." repmo#SelfKey('".keys[0]."', '".keys[1]."')|sunmap ".keys[0]
        execute 'noremap <expr> '.keys[1]." repmo#SelfKey('".keys[1]."', '".keys[0]."')|sunmap ".keys[1]
    endfor

However, the following have to be modified

  • $VIMRUNTIME/ftplugin/vim.vim
nnoremap <silent><buffer> ]" :call search('^\(\s*".*\n\)\@<!\(\s*"\)', "W")<CR>
vnoremap <silent><buffer> ]" :<C-U>exe "normal! gv"<Bar>call search('^\(\s*".*\n\)\@<!\(\s*"\)', "W")<CR>
nnoremap <silent><buffer> [" :call search('\%(^\s*".*\n\)\%(^\s*"\)\@!', "bW")<CR>
vnoremap <silent><buffer> [" :<C-U>exe "normal! gv"<Bar>call search('\%(^\s*".*\n\)\%(^\s*"\)\@!', "bW")<CR>
  • $VIMRUNTIME/ftplugin/sql.vim
exec 'nnoremap <silent><buffer> ]" :call search('."'".b:comment_start."'".', "W" )<CR>'
exec 'nnoremap <silent><buffer> [" :call search('."'".b:comment_end."'".', "W" )<CR>'
exec 'xnoremap <silent><buffer> ]" :<C-U>exec "normal! gv"<Bar>call search('."'".b:comment_start."'".', "W" )<CR>'
exec 'xnoremap <silent><buffer> [" :<C-U>exec "normal! gv"<Bar>call search('."'".b:comment_end."'".', "W" )<CR>'
  • $VIMRUNTIME/ftplugin/hamster.vim
noremap <silent><buffer> ]# :call search('^\s*#\@!', "W")<CR>
noremap <silent><buffer> [# :call search('^\s*#\@!', "bW")<CR>
  • $VIMRUNTIME/ftplugin/eiffel.vim
nnoremap <silent> <buffer> ]- :<C-U>call <SID>DoMotion(comment_block_start, 1, 'W')<CR>
xnoremap <silent> <buffer> ]- :<C-U>exe "normal! gv"<Bar>call <SID>DoMotion(comment_block_start, 1, 'W')<CR>
nnoremap <silent> <buffer> [- :<C-U>call <SID>DoMotion(comment_block_end, 1, 'Wb')<CR>
xnoremap <silent> <buffer> [- :<C-U>exe "normal! gv"<Bar>call <SID>DoMotion(comment_block_end, 1, 'Wb')<CR>

Ft-specific [{ and ]}

Repeat builtin with

for keys in [ ['[{', ']}'], ['[(', '])'] ]
    execute 'noremap <expr> '.keys[0]." repmo#SelfKey('".keys[0]."', '".keys[1]."')|sunmap ".keys[0]
    execute 'noremap <expr> '.keys[1]." repmo#SelfKey('".keys[1]."', '".keys[0]."')|sunmap ".keys[1]
endfor

The only language in $VIMRUNTIME which defines them is currently sql. Again they are not <plug> mappings and have to be modified.

$VIMRUNTIME/ftplugin/sql.vim

exec "nnoremap <buffer> <silent> ]} :call search('".s:ftplugin_sql_objects."', 'W')<CR>"
exec "nnoremap <buffer> <silent> [{ :call search('".s:ftplugin_sql_objects."', 'bW')<CR>"
exec 'xnoremap <buffer> <silent> ]} /'.s:ftplugin_sql_objects.'<CR>'
exec 'xnoremap <buffer> <silent> [{ ?'.s:ftplugin_sql_objects.'<CR>'

Unimpaired

No change necessary: they are already <plug> motions. Following works:

nmap <expr> ]q repmo#Key('<plug>unimpairedQNext', '<plug>unimpairedQPrevious')
nmap <expr> ]q repmo#Key('<plug>unimpairedQNext', '<plug>unimpairedQPrevious')
nmap <expr> [q repmo#Key('<plug>unimpairedQPrevious', '<plug>unimpairedQNext')
nmap <expr> ]l repmo#Key('<plug>unimpairedLNext', '<plug>unimpairedLPrevious')
nmap <expr> [l repmo#Key('<plug>unimpairedLPrevious', '<plug>unimpairedLNext')
nmap <expr> ]n repmo#Key('<plug>unimpairedContextNext', '<plug>unimpairedContextPrevious')
nmap <expr> [n repmo#Key('<plug>unimpairedContextPrevious', '<plug>unimpairedContextNext')
omap <expr> ]n repmo#Key('<plug>unimpairedContextNext', '<plug>unimpairedContextPrevious')
omap <expr> [n repmo#Key('<plug>unimpairedContextPrevious', '<plug>unimpairedContextNext')