How to call a plugin from my .vimrc file? - function

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.

Related

Turn vimscript key mapping into 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!

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.

Returning insert mode cursor to same place after function call

I've been trying to write a bit of Vimscript to call a function when I insert the same character twice, in my particular case I wanted that if you inserted semi colon twice for it to actually move the semi colon to the end of the line.
command! Semi call Semi()
inoremap ; <C-O>:Semi2<CR>
function! Semi()
let x = getpos(".")
" If we are in the last column..
if col(".")+1 == col("$")
let insert_semi = getline(".") . ";"
call setline(".", insert_semi)
let x[2] += 1
call setpos(".", x)
return
endif
let char = getline(".")[x[2] - 2]
if char == ";"
" if prev char was a semicolon also, remove and append to the end
else
" insert semicolon normally...
endif
endfunction
The problem I am having is when calling this function on the last column, you have to exit insert mode to call this function the cursor will go into normal mode and move the cursor to the last column. Is there any way to tell whether the cursor was appending to the end of the line or inserting before the last column and when function call is finished return it to the same position?
I am well aware that I could use an insert mapping on ;; however I dislike this behaviour, where Vim goes into a waiting for next key mode and does not display what you have written. This issue is not only to do with my problem listed but a more general problem which also occurs in the first column.
If your function does not use insert mode to append the ';' -- e.g. by pasting from a buffer -- you can use the gicommand to return to the place, where you exited insert mode.
I would advise against using i_CTRL-O, it triggers InsertLeave and InsertEnter events, which may affect other plugins. I would use :inoremap <expr> ; here. See :help :map-expr. Inside that expression (i.e. your function), record the current cursor position and compare it with the last recorded one. If it's next to it, return the keys to undo the inserts and redo at the end (<BS><BS><End>;), else just return ;.
You don't need a function for that:
inoremap ;; ;;<Esc>h"_xxm`$p``a
or cleaner:
inoremap ;; <Esc>m`A;<Esc>``a

Referencing filetype from function

I'm trying to write a function that acts on any filetype (thus is not a particular ftplugin), that has divergent behavior based upon what the filetype is. e.g.
if (filetype=='objc')
"do something
elseif (filetype='cpp')
"do something else
endif
I read through the filetype docs, but nothing seems to reference this. Advice?
Try accessing the option value:
if (&ft == 'objc')
...
You can use & to access the value:
if ( &ft == 'cpp' )
...
etc.

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>