Turn vimscript key mapping into function - function

I am trying to make a function in vimscript that will activate on every cursor movement in visual mode, i.e. it will automatically highlight all the matches of the current visual selection.
The mapping from https://vi.stackexchange.com/questions/20077/automatically-highlight-all-occurrences-of-the-selected-text-in-visual-mode:
xnoremap <silent> <cr> "*y:silent! let searchTerm = '\V'.substitute(escape(#*, '\/'), "\n", '\\n', "g") <bar> let #/ = searchTerm <bar> echo '/'.#/ <bar> call histadd("search", searchTerm) <bar> set hls<cr>
works perfectly for me but I am failing to implement it into function. This is what I tried:
function! HighlightVisual(mode)
if mode()=~#"^[vV\<C-v>]"
call feedkeys('"*y')
let searchTerm = '\V'.substitute(escape(#*, '\/'), "\n", '\\n', "g")
let #/ = searchTerm
echo '/'.#/
call histadd("search", searchTerm)
set hls
endif
endfunction
autocmd CursorMoved * call HighlightVisual(mode())
However, it is not working.
What am I doing wrong? I think the function is terminated each time a call is invoked but have no idea how to workaround that.

I have made it work. Firstly, I found from https://vi.stackexchange.com/questions/28404/vim-does-not-call-functions-correctly-when-wrapped-in-another-function that "Vim doesn't like updating Screen too often" so redraw was necessary after set hls. And secondly, y was forcing normal mode and gv was moving cursor triggering the autocmd CursorMoved inside the function thus making an infinite loop. I made a workaround by set eventignore=CursorMoved and resetting it back at the end of function. I am saving selected word to register h (as in highlight) but you can choose any by changing "hy and #h. Moreover, I added highlight toggle when not needed. So, if somebody wants to use the feature of automatically highlighting visual matches, here is the code:
function! HighlightVisual(mode)
if mode()=~#"^[vV\<C-v>]"
set eventignore=CursorMoved
normal "hy
normal gv
let searchTerm = '\V'.substitute(escape(#h, '\/'), "\n", '\\n', "g")
let #/ = searchTerm
call histadd("search", searchTerm)
set hls
redraw
set eventignore=""
endif
endfunction
autocmd CursorMoved * :call HighlightVisual(mode())
vnoremap <silent> <ESC> :<C-u>set nohlsearch<CR>
; just copy-paste it in your .vimrc/init.vim.
Big thanks to trusktr as I used his answer in https://vi.stackexchange.com/questions/20077/automatically-highlight-all-occurrences-of-the-selected-text-in-visual-mode to build my function I wanted for sooo long.
Happy coding!

Related

How to call a plugin from my .vimrc file?

I am using a VIM plugin called Goyo (for writing markdown files). It is similar to Distraction Free mode in SublimeText. I want to create a write-mode in my .vimrc that I can toggle. This toggle will set various options on in write-mode, such as set spell, set wrap etc.
I have everything working here, except calling the Goyo function. How can I execute the Goyo plugin from within my ToggleWrite() function?
Here is my code:
" Write toggle switch
let b:write = "no"
function! ToggleWrite()
if exists("b:write") && b:write == "yes"
let b:write = "no"
set nowrap
set nolinebreak
set textwidth=100
set wrapmargin=0
set nospell
" ↓↓↓ I want to call this ↓↓↓
":Goyo
else
let b:write = "yes"
set wrap
set linebreak
set textwidth=100
set wrapmargin=0
set spell
" ↓↓↓ I want to call this ↓↓↓
":Goyo 60x100%
endif
endfunction
" Set up the toggle sequence
nmap <expr> ,w ToggleWrite()
I put my comment as an answer:
Your mapping uses <expr>, which is not right in your case. You should try this mapping instead:
nmap ,w :call ToggleWrite()<cr>
or
nmap <silent> ,w :call ToggleWrite()<cr>
<expr> lets you make "custom" mappings, depending on the return of a function. It's rarely used in common cases.

vim keep cursor position when counting matches

I have a function to count and return the number of matches of some text:
function! count_matches()
redir => matches_cnt
silent! %s/\[\d*\]//gn
redir END
return split(matches_cnt)[0]
endfunction
I created a map to insert the return value of count_matches() at the current position:
noremap <C-A> Go[foo<C-R>=count_matches()<CR>]
However the cursor jumps to the beginning of the line after executing the silent %s/[\d*]//gn command. So when I press control+a vim inserts "[foo", then the function is being executed, the search command resets the cursor position and the return value is inserted at the beginning of the line resulting in "1][foo" instead of "[foo1]".
Can I somehow prevent count from changing the cursor position, or reset the cursor position after counting the matches?
The script also leads to an error, if the pattern is not found. How can I get the function to return 1 without an error for zero matches?
Even better then just to save the cursor position, is to save the complete viewport. (But that only works, if you do not change the window layout)
See :help winsaveview()
let wsv = winsaveview()
MoveTheCursorAround
call winrestview(wsv)
In your particular case, I would take another approach:
inoremap <expr> <f3> len(split(join(getline(1,'$'),"\n"), '\[\d\+\]',1))
Which takes the whole text, and splits it on the pattern \[\d\+\] and then counts how many elements there are. Or if you like to add some text:
inoremap <expr> <f3> '['.len(split(join(getline(1,'$'),"\n"), '\[\d\+\]',1)).']'
This will add the [ in front and ] after the number. Adjust the mapping key and text to your personal taste. (Note, you do not need the winsaveview() function, cause the cursor won't move).
It is perhaps not such a good idea to use that function on a multi MB text size. ;)
This is the same function, reworked to return 1 when there's no match:
function! count_matches()
redir => matches_cnt
try
silent! %s/\[\d*\]//gn
catch
echo 1
endtry
redir END
return split(matches_cnt)[0]
endfunction
See :help getpos()
let save_cursor = getpos(".")
MoveTheCursorAround
call setpos('.', save_cursor)
My solution:
function! CountWithCursorKeep(...)
let currentCursor = getcurpos()
let pattern = expand('<cword>')
if a:0 > 0 | let pattern = a:1 | endif
execute(':%s#' . pattern . '##gn')
call setpos('.', currentCursor)
endfunction
nmap ,ns :call CountWithCursorKeep(<C-R>/)<cr>
nmap ,nw :call CountWithCursorKeep(expand('<cword>'))<cr>
nmap ,nW :call CountWithCursorKeep(expand('<cWORD>'))<cr>
command! -nargs=? -bar -complete=tag CountMatch call CountWithCursorKeep(<f-args>)
You can use :CountMatch pattern to get how many times pattern occurs in current file.

vim key sequence as function argument

I want to execute the command "yiw:s/\<<C-r>"\>/<C-r>"/g<Left><Left>" by key sequence.
So I make a mapping
nnoremap <F7> yiw:s/\<<C-r>"\>/<C-r>"/g<Left><Left>
This mapping copy the word under cursor, then the string :s/\<">/"/g" (where " are substituted by the copied word) appears in the command line and the cursor in the command line is at the end of replacement statement.
I also want to save cursor position before this command and restore after.
function! SafeCommand(cmd)
let line = line('.')
let col = col('.')
// execute cmd here
call cursor( line, col )
endfunction
How to do that?
Normally, you'd just put the entire (complex) command in a function, and invoke that function from the :nnoremap. But that doesn't work for incomplete commands, like the template :substitute that your mapping represents. For that, you need to include the save / restore parts into the command-line (though that's ugly):
:fun! Save()
let s:line = line('.')
let s:col = col('.')
:endfun
:fun! Restore()
call cursor( s:line, s:col )
:endfun
:nnoremap <F7> yiw:call Save()<Bar>s/\<<C-r>"\>/<C-r>"/g<Bar>call Restore()<Left><Left><Left><Left><Left><Left><Left><Left><Left><Left><Left><Left><Left><Left><Left><Left>

Skipping a window in VIM with Ctrl-W_W

When I have several windows open in VIM, I'd like to always skip one of them (the one containing my project using Aric Blumer's Project plugin), whenever I press Ctrl-W_W.
In other words I'd like to cycle through my document windows as if the Project window wasn't one of them. When I actually do want to go into the project window, I'll use the mapping I created especially for this purpose.
Is there a way to mark a window so that it's skipped by Ctrl-W_W or would I need a script? I'm loving Vim but am still in the steep part of the learning curve.
You would have to write a function (it's easier to maintain) that cycles to the next window, and skip it if it matches the name of the windows you don't want to go into.
Something like:
function! s:NextWindowBut(skip,dir)
let w0 = winnr()
let nok = 1
while nok
" exe "normal! \<c-W>w"
" or better
exe 'wincmd '.a:dir
let w = winnr()
let n = bufname('%')
let nok = (n=~a:skip) && (w != w0)
" echo "skip(".n."):".(n=~a:skip)." w!=w0:".(w != w0)." --> ".nok
endwhile
if w == w0
echomsg "No other acceptable window"
endif
endfunction
nnoremap <silent> <C-W>w :call <sid>NextWindowBut('thepattern','w')<cr>
nnoremap <silent> <C-W>W :call <sid>NextWindowBut('thepattern','W')<cr>

html indenting in vim

I don't know if this is possible, but does anyone know of an indent script that will support this scenario?
(| is cursor)
given
<div>|<div>
if I press enter, I want to see
<div>
|
</div>
instead of
<div>
|<div>
delimitMate will take care of this for you.
You will however, need two additional settings...
add the >:< pair to the list of html files:
au FileType html let delimitMate_matchpairs = "(:),[:],{:},>:<"
and tell it what pattern you'd like to add after inserting a
au FileType html let b:delimitMate_expand_cr = "\<CR>\<CR>\<Up>\<Tab>"
(this will, instead of inserting two a , insert two s, press up, then insert a tab)
Ended up going with brian Carpers answer, only modified very slightly
"fancy html indenting
function! NewlineInTag()
let lnum = getline('.')
let cnum = col('.')
let chars = strpart(lnum, cnum - 2, 3)
if chars =~ '></'
return "\<CR>\<ESC>\<UP>$o"
else
return "\<CR>"
endif
endfunction
autocmd FileType eruby,html imap <CR> <C-R>=NewlineInTag()<CR>
You could do something like this:
function! NewlineInTag()
let lnum = getline('.')
let cnum = col('.')
let chars = strpart(lnum, cnum - 2, 2)
if chars =~ '><'
return "\<CR>\<ESC>\<UP>$o"
else
return "\<CR>"
endif
endfunction
imap <CR> <C-R>=NewlineInTag()<CR>