Can we create a new op-pending command in Vim? - vim

Does anyone know if it's possible to create a new op-pending command?
e.g. I'd like to replace a sequence such as vf(r<space>w with ,cf(. Specifically here, the idea is to "clear" the text from the cursor position up to and including the next opening brace and then put the cursor at the beginning of the next word.
I may just be missing something in the help files (or my Google-fu is off today), so a pointer to the right place would be much appreciated.

You want to use :set opfunc and g#. The documentation is pretty good, :h g#.
nnoremap <silent> ,c :set opfunc=Clearing<cr>g#
vnoremap <silent> ,c :<c-u>set opfunc=Clearing<cr>g#
function! Clearing(type, ...)
let sel_save = &selection
let &selection = "inclusive"
let reg_save = ##
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>r "
elseif a:type == 'line'
silent exe "normal! '[V']r "
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]r "
else
silent exe "normal! `[v`]r "
endif
norm! `]w
let &selection = sel_save
let ## = reg_save
endfunction

I think :h map-operator is what you're looking for.

Related

load external command in the same split after repeating it

I want to load some text coming from a command line command into a new vim split. I got this working but if I run the command again it keeps opening new splits.
What I want to achieve is getting this into the same split. How can I do this?
nnoremap <leader>q :execute 'new <bar> 0read ! bq query --dry_run --use_legacy_sql=false < ' expand('%')<cr>
I would suggest using the preview window via the :pedit command.
nnoremap <leader>q :execute 'pedit <bar> wincmd p <bar> 0read ! bq query --dry_run --use_legacy_sql=false < ' expand('%')<cr>
However we can do even better by doing the following:
Making a "query" operator using g# and 'opfunc'
A query command (feels very vim-like to do so)
Using stdin instead of a filename
Example:
function! s:query(str)
pedit [query]
wincmd p
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal noswapfile
%delete _
call setline(1, systemlist('awk 1', a:str))
endfunction
function! s:query_op(type, ...)
let selection = &selection
let &selection = 'inclusive'
let reg = ##
if a:0
normal! gvy
elseif a:type == 'line'
normal! '[V']y
else
normal! `[v`]y
endif
call s:query(##)
let &selection = selection
let ## = reg
endfunction
command! -range=% Query call s:query(join(getline(<line1>, <line2>), "\n"))
nnoremap \qq :.,.+<c-r>=v:count<cr>Query<cr>
nnoremap \q :set opfunc=<SID>query_op<cr>g#
xnoremap \q :<c-u>call <SID>query_op(visualmode(), 1)<cr>
Note: I am using awk 1 as my "query" command. Change to meet your needs.
For more help see:
:h :pedit
:h :windcmd
:h operator
:h g#
:h 'opfunc'
:h systemlist()

vimscript replace line not working

Newcomer to VimL trying to write a mapping that does the following:
foo
|----> cursor
bar
baz
lr2j should repalce foo with baz.
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
let saved_register = ##
silent execute "normal! S\<esc>my`]dd'yPjddk^ :delmarks y"
let ## = saved_register
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
Instead I get
Error detected while processing function <SNR>108_LineReplace: line 5: E20: Mark not set
I've tried different permutations of execute "normal! ..." command to no avail. Can anyone spot the error?
I should note that when I test out the normal commands everything works fine and the mark 'y exists.
Use :move and :delete to simply things:
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
']move '[
-delete_
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
For more help see:
:h :d
:h :m
:h :range
#xaizek is right; the correct way is to store the mark from the motion:
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
let lnum = getpos("']")[1]
let saved_register = ##
silent execute "normal S\<esc>my" . lnum . "Gdd'yPjddk^ :delmarks y"
let ## = saved_register
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#

Vim - pass register to operator function

I'm trying to implement ChangePaste operator. It should replace text with the one from register.
It's working fine with motions, so I can use cp<motion> and the text will be replaced from default register.
Now I would like to be able to use it with different registers. I'm looking for information how to pass selected register to operator function. So, if one type "acpiw I would like the script to replace an inner word with register a content. Is that possible at all?
Code so far:
nmap <silent> cp :set opfunc=ChangePaste<CR>g#
function! ChangePaste(type, ...)
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>\"_c" . #"
elseif a:type == 'line'
silent exe "normal! '[V']\"_c" . #"
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]\"_c" . #"
else
silent exe "normal! `[v`]\"_c" . #"
endif
endfunction
Edit:
Solution using v:register and buffer variable:
nmap <silent> cp :let b:changepaste_buffer = v:register<cr>:set opfunc=ChangePaste<CR>g#
function! ChangePaste(type, ...)
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>\"_c" . getreg(b:changepaste_register)
elseif a:type == 'line'
silent exe "normal! '[V']\"_c" . getreg(b:changepaste_register)
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]\"_c" . getreg(b:changepaste_register)
else
silent exe "normal! `[v`]\"_c" . getreg(b:changepaste_register)
endif
endfunction
As mentioned by #romainl in the comments, you can access the v:register variable in your function.
No need to even save it as a buffer variable.

Is it possible to cycle around marks in vim?

I am starting to use [' and ]' to jump between my marks in a file, as mentioned here:
http://vim.wikia.com/wiki/Using_marks
However, when I get to the last mark in the file these commands don't wrap around to the top.
I searched around for "cycling" or "wrapping" when it comes to navigating marks but everything I see mentions Ctrl-o and Ctrl-i which is nice but doesn't answer my question.
Is it possible to set an option to wrap top-to-bottom or bottom-to-top when using these shortcuts?
You can create a function to check if you have moved, and if not then go to the beginning of the file and call ]' again. Like this:
nnoremap ]' :call CycleMarksForward()<cr>
function! CycleMarksForward()
let currentPos = getpos(".")
execute "normal! ]'"
let newPos = getpos(".")
if newPos == currentPos
execute "normal! gg]'"
endif
endfunction
You'll need to the same thing for [` ]` and [', although there is probably a way to come up with a generic solution.
Fleshed out:
nnoremap <silent> ]' :call CycleMarks("]'")<cr>
nnoremap <silent> [' :call CycleMarks("['")<cr>
nnoremap <silent> ]` :call CycleMarks("]`")<cr>
nnoremap <silent> [` :call CycleMarks("[`")<cr>
function! CycleMarks(arg)
let currentPos = getpos(".")
execute "normal! " . a:arg
let newPos = getpos(".")
if newPos == currentPos
if a:arg == "]'" || a:arg == "]`"
execute "normal! gg0" . a:arg
else
execute "normal! G$" . a:arg
endif
endif
endfunction
Note: this solution does not handle marks on the first and last line very well, i.e. marks on the last line will be skipped when cycling backwards and marks on the first line will be skipped when cycling forward.

How do one add custom verbs to VIM?

I would like to define a new verb to vim (say 'o') which can operate on any of the existing vim textobjects. Any pointers on how I can go about doing this?
Thanks
AB
These verbs are called operators (see :h operator). If you want to build your own operator you must use the 'operatorfunc' setting then execute g#. The vim documentation explains it best on how to do this, please see (:h :map-operator) Here is the example from the vim documentation:
nmap <silent> <F4> :set opfunc=CountSpaces<CR>g#
vmap <silent> <F4> :<C-U>call CountSpaces(visualmode(), 1)<CR>
function! CountSpaces(type, ...)
let sel_save = &selection
let &selection = "inclusive"
let reg_save = ##
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>y"
elseif a:type == 'line'
silent exe "normal! '[V']y"
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]y"
else
silent exe "normal! `[v`]y"
endif
echomsg strlen(substitute(##, '[^ ]', '', 'g'))
let &selection = sel_save
let ## = reg_save
endfunction
If you want another example please see take a look at Tim Pope's commentary plugin.
For more help
:h operator
:h :map-operator
:h 'opfunc'
:h g#

Resources