2

Current Behavior

I am building a plugin for fast, efficient normal mode commands over multiple lines. A representative function of what I am currently doing is the following:

User Interface

  1. It asks the user for a text object within which to perform the normal mode command
  2. It asks for a regex describing lines on which to perform a command
  3. It asks for the command to run
  4. It runs the command

Code

function! s:lines(...) abort
  return [line("'["), line("']")]
endfunction

function! s:gObject(...) abort
  if !a:0
    let &operatorfunc = matchstr(expand('<sfile>'), '[^. ]*$')
    return 'g@'
  else
    let [lnum1, lnum2] = <SID>lines(a:0, a:1) 
    let search = input("Run (Leave empty for last search): g/\\v")
    let prompt = lnum1 . "," . lnum2 . "g/\\v" . search . "/". "norm "
    let cmd = input("Run: " . a:prompt)
    exec a:prompt . cmd
  endif
endfunction

Desired Outcome

I now want to add repeatability. I want to be able to hit the . key and replay the entire sequence. This means it should perform the same motion to select a text object, perform the same search and run the same command as in the previous run.

I can accomplish this by recording a macro before I run the function, then replaying the macro will produce exactly the behavior that I want. I want that exact outcome, but without the user overhead of declaring a macro.

What I have tried

  1. Having vim-repeat installed and pressing .. This reruns the text selection, but not the input commands (hence the question).
  2. Recording a macro of the sequence and replaying it. This leads to poor UX.

Thanks in advance for your help.

1
  • Also posted on reddit Commented Nov 2, 2019 at 12:07

1 Answer 1

1

Using repeat.vim is the right approach; it allows you to tweak what gets re-executed, and also offers repeat functionality for cases not covered by the built-in . command. (And my visualrepeat plugin extends this further to visual mode repeats.)

If I understand you right, you want to skip the user query and just re-use the previous answer on repeat. For that, you need to register a different <Plug>-mapping with repeat#set() than what is used to trigger your plugin. Normally, I'd be like this:

function! s:Impl()
    let l:search = input(...)
    " action here, using l:search
    call repeat#set("\<Plug>PeculiarMapping")
endfunction
nnoremap <silent> <Plug>PeculiarMapping :<C-u>call <SID>Impl()<CR>
if ! hasmapto('<Plug>PeculiarMapping', 'n')
    nmap <Leader>p <Plug>PeculiarMapping
endif

To reuse the queried l:search on repeat, parameterize the s:Impl() with a flag, and then either save the input in a script-local variable, or recall it. Add a <Plug>-mapping variant for repeat that sets the isRepeat flag:

let s:search = ''
function! s:Impl( isRepeat )
    if ! a:isRepeat
        let s:search = input(...)
    endif
    " action here, using s:search
    call repeat#set("\<Plug>PeculiarRepeat")    " Different repeat mapping
endfunction
nnoremap <silent> <Plug>PeculiarRepeat :<C-u>call <SID>Impl(1)<CR>
nnoremap <silent> <Plug>PeculiarMapping :<C-u>call <SID>Impl(0)<CR>
if ! hasmapto('<Plug>PeculiarMapping', 'n')
    nmap <Leader>p <Plug>PeculiarMapping
endif

Further comments

I try to avoid using input(), as it makes the command inherently interactive, so it cannot be reused for scripting, or even easily invoked by another mapping. If you just need to first obtain some parameter(s) from the user before doing the work, I prefer a custom :command that takes argument(s). For a single parameter, this is straightforward and no parsing is involved. For multiple parameters, I try to adhere to the conventions of existing Ex commands (the most complex, :substitute takes {pattern}, {replacement}, [flags] and even a [count]); the various flags taken by :command offer many variations, and you can further aid the user via a custom completion function.

With a custom command, this can be repeated via @: or the command history, and the user can decide whether to reuse arguments (fully / partially). If you think that a command takes too long to type, a supplementary mapping can enter command-line mode, put the command in there, maybe even add boilerplace (like the /// separators of :substitute) and position the cursor, so the user just has to fill in parameters and press <Enter>.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.