0

A g command can find the lines globally and then apply actions, like the following would find lines that contain foo and delete those lines: :g/foo/d

An s command is more well-known for its substitution power: :s/foo/bar/ would substitute the first foo of the current line with bar.

I wonder if it is possible to create my own excmd which takes this form :<excmd>/<1st param>/<2nd param>/<options>, which, in turn calls a function like MyFunc( param1, param2, options ).

Having this syntactic sugar would greatly help since the function should deal with quotes, whitespaces and other special characters (like !, %) a lot, and using / as the delimiter would greatly avoid much escaping problems.

Many thanks in advance!

1 Answer 1

1

Yes of course. You'll just need to parse the parameter you receive.

Use the first char as delimiter, and split() the string with this delimiter. Of course, if you wish to support stuff like :Whatever/st\/uff/other, it'll be a little bit more complex.

I remember to have given a few answers on SO/vi.SE that implement such command: Here, here, and here.

For instance, in lh-style I have the following:

command! -nargs=1 -range -complete=custom,s:CompleteConvertNames
      \ ConvertNames <line1>,<line2>call s:ConvertNames(<f-args>)

" Function: s:ConvertNames(repl_arg) {{{3
" Syntax: ConvertNames/{regex}/{convertion_type}
function! s:ConvertNames(repl_arg) range abort
  let sep = a:repl_arg[0]
  let fields = split(a:repl_arg, sep)
  if len(fields) != 2 && len(fields) != 3
    throw ":NameConvert/{regex}/{convertion_type}/[{opt}] expects exactly two or three parameters"
  endif
  let convertion_type = fields[1]
  let i = lh#list#find_if(s:k_convertions, 'v:1_[0]=='.string(convertion_type))
  if i == -1
    throw "convertion (".convertion_type.") not found"
  endif
  " build the action to execute
  let ConvertFunc = function(s:k_convertions[i][1])
  let action = '\=(ConvertFunc(submatch(0)))'
  let cmd = a:firstline . ',' . a:lastline . 's'
        \. sep . fields[0]
        \. sep . action
        \. sep.(len(fields)>=3 ? fields[2] : '')
  " echomsg cmd
  exe cmd
endfunction

EDIT: Here is another (untested) example:

command! -nargs=1 
      \ Hello call s:Hello(<f-args>)

function! s:ConvertNames(repl_arg) range abort
  let sep = a:repl_arg[0]
  let fields = split(a:repl_arg, sep)
  let cmd = 'echo "Hello".'.string(join(fields, ',')))
  exe cmd
endfunction
4
  • Thank you for your answer! Sorry I don't understand how it works.. May I have an example of using :Hello/world/name which does :echo 'Hello world, name!'?
    – Sunny Pun
    Dec 27, 2017 at 4:24
  • That && in the if seems like a bug
    – D. Ben Knoble
    Dec 27, 2017 at 15:04
  • @DavidBenKnoble. I had to read it several times, but it doesn't seems so. It's equivalent to !( (len == 3) || (len == 2) ) which seems to be exactly what I wanted to test. Anyway, thank you for checking the code :) Dec 27, 2017 at 21:59
  • I misread; it is correct, if still difficult to parse. No matter.
    – D. Ben Knoble
    Dec 27, 2017 at 22:05

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.