sdeleon28
sdeleon28

Reputation: 329

Vim: Create custom command that takes a movement

I often transform lines of texts to arrays. For example, this:

Monday
Tuesday
Wednesday

Becomes:

[
  'Monday',
  'Tuesday',
  'Wednesday',
]

I can make a map that changes one of the lines (e.g. Monday to 'Monday',) like so:

:nnoremap gsa ^i'<Esc>A,

What I'd like to do is have that command take a movement or a text object so that I can execute it like gsaip or gsip3j.

How can I accomplish this?

Upvotes: 7

Views: 2269

Answers (4)

Luc Hermitte
Luc Hermitte

Reputation: 32926

If you really want to add a comma after the last item, I would have done it this way, in order to leave registers unaltered.

function! s:to_list() range abort
  let words = getline(a:firstline, a:lastline)
  exe a:firstline.','.a:lastline.'d _' " remove the "_" to fill the unnamed register
  let lines = ['['] + map(words, '"  ".string(v:val).","') + [']']
  call append(a:firstline-1, lines)
endfunction

command! -nargs=0 -range=% ToList <line1>,<line2>call s:to_list()

In order to get rid of the last two lines, I've edited the last item of map() result or played with join():

  let lines = "[\n" . join(map(words, '"  ".string(v:val)'), ",\n") . "\n]"
  put!=lines

Upvotes: 0

sidyll
sidyll

Reputation: 59287

You can use 'operatorfunc' with g@ to have a map with a motion. The help gives a full explanation and example under the :map-operator topic. Basically you set the function you want to call in your map and use g@. Vim will wait to a motion and then set the marks '[ and '] and call your function.

Inside your function you can go creative, here I just made a quick example of how you could apply this principle to what you need.

:nnoremap <silent> gsa :set opfunc=TransformToArray<cr>g@

function! TransformToArray(type)
  if a:type == 'line'
    let s=line("'[") " saving beginning mark
    ']s/\(\s*\).*\zs/\r\1]
    exec s ",']-1s/\\s*\\zs.*/  '&',"
    exec s 's/\(\s*\)\zs\ze  /[\r\1'
  elseif a:type == 'char'
    " ...
  endif
endfunction

Upvotes: 7

Put this into your .vimrc file :

vnoremap <silent> gsa :call Brackets()<CR>

function! Brackets()
    execute "normal! I'"
    if line(".") == a:lastline
        execute "normal! A'\<cr>]"
        execute a:firstline."s:^:[\r:" 
    else
        execute "normal! A',"
    endif
endfunction

Select the visual-line block you want for example vip and then press gsa.

Upvotes: 1

Ingo Karkat
Ingo Karkat

Reputation: 172520

To be precise, you seem to want to apply the mapping to each of the lines covered by a motion or text object.

You can establish such range via visual mode: vip or v2j. Then, you can use :normal gsa (typed; Vim automatically inserts the selected range (:'<,'>) if you enter command-line mode from visual mode) to apply your custom mapping to each of the lines (the cursor is positioned on the first column for each line, as per :help :normal-range).

Upvotes: 0

Related Questions