How to define a new Vim operator with a parameter?

Consider one of the plugins for writing custom text objects. For example: https://github.com/kana/vim-textobj-user


Here is an example implementation of the command described in the question, for illustrative purposes.

nnoremap <silent> s :set opfunc=Surround<cr>g@
vnoremap <silent> s :<c-u>call Surround(visualmode(), 1)<cr>

function! Surround(vt, ...)
    let s = InputChar()
    if s =~ "\<esc>" || s =~ "\<c-c>"
        return
    endif
    let [sl, sc] = getpos(a:0 ? "'<" : "'[")[1:2]
    let [el, ec] = getpos(a:0 ? "'>" : "']")[1:2]
    if a:vt == 'line' || a:vt == 'V'
        call append(el, s)
        call append(sl-1, s)
    elseif a:vt == 'block' || a:vt == "\<c-v>"
        exe sl.','.el 's/\%'.sc.'c\|\%'.ec.'c.\zs/\=s/g|norm!``'
    else
        exe el 's/\%'.ec.'c.\zs/\=s/|norm!``'
        exe sl 's/\%'.sc.'c/\=s/|norm!``'
    endif
endfunction

To get user input, the function InputChar() is used, assuming that the required argument is a single character.

function! InputChar()
    let c = getchar()
    return type(c) == type(0) ? nr2char(c) : c
endfunction

If it is necessary to accept a string argument, change the call to InputChar() in Surround() to the call to input(), instead.


The title of the question might cause misunderstanding. What you want to do is to define a new operator like y, d and c, neither motions nor text objects, isn't it? :help :map-operator describes how to define a new operator. To take a parameter like the surround plugin, use getchar() in your 'operatorfunc'.

Though :help :map-operator describes the basics, it's a bit troublesome to deal with arguments passed to 'operatorfunc'. You can use vim-operator-user to simplify the handling of arguments. With this plugin, surround-like operator can be written as follows:

function! OperatorSurround(motion_wise)
  let _c = getchar()
  let c = type(_c) == type(0) ? nr2char(_c) : _c
  if c ==# "\<Esc>" || c == "\<C-c>"
    return
  endif

  let bp = getpos("'[")
  let ep = getpos("']")
  if a:motion_wise ==# 'char'
    call setpos('.', ep)
    execute "normal! \"=c\<Return>p"
    call setpos('.', bp)
    execute "normal! \"=c\<Return>P"
  elseif a:motion_wise ==# 'line'
    let indent = matchstr(getline('.'), '^\s*')
    call append(ep[1], indent . c)
    call append(bp[1] - 1, indent . c)
  elseif a:motion_wise ==# 'block'
    execute bp[1].','.ep[1].'substitute/\%'.ep[2].'c.\zs/\=c/'
    execute bp[1].','.ep[1].'substitute/\%'.bp[2].'c\zs/\=c/'
    call setpos('.', bp)
  else
  endif
endfunction
call operator#user#define('surround', 'OperatorSurround')
map s  <Plug>(operator-surround)

If you really want to define your own text objects, please consider vim-textobj-user.

Tags:

Vim