Binding a key to execute multiple expression maps in vim - vim

nnoremap <expr> <C-b> ':set bg='.(&bg=='dark' ? "light" : "dark")."<cr>"
nnoremap <expr> <C-b> ':!'.(&bg=='dark' ? "dark" : "light")."<cr>"
Both work indivisually, but when I set the keybinding to
nnoremap <expr> <C-b> ':set bg='.(&bg=='dark' ? "light" : "dark")."<cr>" \| ':!'.(&bg=='dark' ? "dark" : "light")."<cr>"
Only the first one gets executed. How do I make the keybind execute both of these commands?

By adding the <expr> modifier you tell Vim that the whole right hand side of the mapping is an expression but the <bar> in the middle breaks the expression.
This means that your second expression, that conditionally executes an external command, must be fused with the first one in one way or another. There would be a few ways to do it in this specific case but the simplest is to concatenate the two instead of separating them with a <bar>:
nnoremap <expr> <C-b> ':set bg='.(&bg=='dark' ? "light" : "dark")."<cr>".':!'.(&bg=='dark' ? "dark" : "light")."<cr>"
You could also have a single ternary condition.
But that one-liner is getting dangerously long. That's usually where turning your unmaintainable logic into a readable function starts to make sense:
function! ToggleBackground()
let toggles = { "dark": "light", "light": "dark" }
let &background = toggles[&background]
call system(toggles[&background])
endfunction
nnoremap <C-b> <Cmd>call ToggleBackground()<CR>

Related

Is it wrong to have two statements inside an if on vimscript?

I recently decided to revamp my .vimrc.
As per instructions here, I added a small snippet in a file called ultisnips_tab_hack.vim, which is sourced in my .vimrc.
The last line of that snippet is this:
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>"
Which from what I could tell was mapping Enter to an expression that would test if the pop-up menu was visible. If it was, the inserted text would be decided by the ExpandSnippetOrCarriageReturn() function that is defined in the snippet. Else a simple carriage return would be inserted.
This snippet is supposed to make YouCompleteMe and Ultisnips behave, and, as per this other comment, the last line should be modified to the following to make it also behave with vim-endwise.
let g:endwise_no_mappings = 1
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>\<C-R>=EndwiseDiscretionary()\<CR>"
Which is the same except that if the pop-up menu is not visible, it will also call the vim-endwise function to decide if something else should be inserted. And also, the endwise mapping is disabled, so that it won't conflict with our custom mapping.
So, I was trying to make a solution that would work whether vim-endwise was loaded or not (because I wanted to load it only on ruby files). And what I tried was:
if exists("*EndwiseDiscretionary")
let g:endwise_no_mappings = 1
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>\<C-R>=EndwiseDiscretionary()\<CR>"
else
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>"
endif
To my surprise, that gave me the following weird error on vim when I pressed Enter (and vim-endwise was loaded).
E15: Invalid Expression: ExpandSnippetOrCarriageReturn()\
And also, the following text was inserted: pumvisible() ? " instead of a carriage return.
To my even-more-surprise, though, the following caused no errors at all, whether vim-endwise was loaded or not.
let g:endwise_no_mappings = 1
if exists("*EndwiseDiscretionary")
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>\<C-R>=EndwiseDiscretionary()\<CR>"
else
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>"
endif
I've been bitten by some weird vimscript stuff before, so I'm now wondering if two statements inside an if causes this kind of problems and I'm supposed to always extract them to a function.
Or maybe I need a special separator that I don't know of, or something like that. Maybe it's a problem with a variable assignment right above a mapping?
Can any vim guru please elucidate me?
TL;DR
Causes weird E15: Invalid Expression: ExpandSnippetOrCarriageReturn()\ error when I press Enter on insert mode:
if exists("*EndwiseDiscretionary")
let g:endwise_no_mappings = 1
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>\<C-R>=EndwiseDiscretionary()\<CR>"
else
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>"
endif
Work as intended:
let g:endwise_no_mappings = 1
if exists("*EndwiseDiscretionary")
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>\<C-R>=EndwiseDiscretionary()\<CR>"
else
inoremap <expr> <CR> pumvisible() ? "\<C-R>=ExpandSnippetOrCarriageReturn()\<CR>" : "\<CR>"
endif

how do I conditionally bind a key to do two different actions in vim?

I'd like to bind <C-n> to do one of two things in Vim depending on the state of the editor. If I have tabs open I'd like it to switch to the next tab, otherwise I'd like it to open a new tab. I've looked at the help and come up with this, but it's not working, and I'm a viml noob.
function TabBind()
if range(tabpagenr()) < 2
nno <C-n> :tabnew
else
nno <C-n> :tabn
endif
endfunction
Is this possible? and if so how?
The idea is that you map a function that decides what to do on the fly.
function TabBind()
if tabpagenr('$') < 2
tabnew
else
tabn
endif
endfunction
nno <C-n> :call TabBind()<cr>
You can also define such simple thing as one-liners. For instance I have the following mapping to go to the next diff (in diff mode), or to the next error message otherwise.
nnoremap <expr> <silent> <F3> (&diff ? "]c:call \<sid>NextDiff()\<cr>" : ":cn\<cr>")
In your case, your mapping will be:
nnoremap <expr> <silent> <c-n> (tabpagenr('$') < 2 ? ":tabnew\<cr>" : ":tabn\<cr>")

How to map a sequence in vim conditionally to run external programs without printing the else clause

How can I map a sequence in vim conditionally to run any of two external programs in such way that the screen is not cleared to show the else clause?
For example:
:nmap <c-l> :if filereadable('Makefile')<CR>!make<CR>else<CR>!ls<CR>endif<CR>
ctrl+m executes make but then clears the screen and prints the following at the bottom of it:
: else
: !ls
: endif
Press ENTER or type command to continue
You can use an expression mapping (:help map-expr)
:nnoremap <expr> <c-m> filereadable('Makefile') ? ':make<CR>' : ':!ls<CR>'
Notes:
You should use :noremap; it makes the mapping immune to remapping and recursion.
<C-m> is the same as <CR>; there's currently no way to distinguish the two; better use different keys. See this answer for more information.
you need map <expr>
e.g.:
nnoremap <expr> <c-t> line('.')>=6? ':!ls<cr>' : ':!seq 10<cr>'
in your example:
:nnoremap <expr> <c-m> filereadable('Makefile') ? ':make<CR>' : ':!ls<CR>'
for detail info:
:h :map-<expr>
note that, if you map <c-m>, the Enter will follow that mapping too. better use another key combination, unless you intend to do so.

Vim: search and highlight but do not jump

The super star (*) key in Vim will search for the word under the cursor and jump forward to the next match. The user can jump to the next matches with the n key. If hlsearch is enabled, it will also highlight the matches.
I want to be able to press * and get the highlighted matches and be able to navigate the matches using the n key. However, I do not want Vim to jump to the next match when * is pressed, it should remain on the current word. Is there a way to do this?
I would map:
nnoremap * *``
Works exactly like you want, except that it adds a jump in the jump list. To prevent that you need:
nnoremap * :keepjumps normal! mi*`i<CR>
I found this works pretty well, there's no blink and it doesn't need an intermediate register.
nnoremap <silent> * :let #/= '\<' . expand('<cword>') . '\>' <bar> set hls <cr>
Or if you want the g* behavior:
nnoremap <silent> g* :let #/=expand('<cword>') <bar> set hls <cr>
The best solution:
don't add a jump to the jump list
the behavior of the star key is not be changed
so, try the plugin:
http://www.vim.org/scripts/script.php?script_id=4335
Much better than:
" a jump adds to the jump list
nnoremap * *``
" I got a dead loop on macvim
nnoremap * :keepjumps normal *``<cr>
" the behavior is changed
nnoremap <silent> <Leader>* :let #/='\<<C-R>=expand("<cword>")<CR>\>'<CR>:set hls<CR>
I haven't seen this one yet:
nmap <silent> * "syiw<Esc>: let #/ = #s<CR>
It's very short and does not involve jumping around which can result in blinking.
Explanation: copy the word under cursor to s register and then set the search register (/) to the content of s register. The search register is not writeable directly, that's why the let is necessary and hence the silent to keep vim's command line clean.
I have the following in my .vimrc, which I think works better than the other alternatives:
" Put word under cursor into search register and highlight
nnoremap <silent> <Leader>* :let #/='\<<C-R>=expand("<cword>")<CR>\>'<CR>:set hls<CR>
vnoremap <silent> <Leader>* :<C-U>
\let old_reg=getreg('"')<Bar>let old_regtype=getregtype('"')<CR>
\gvy:let #/=substitute(
\escape(#", '/\.*$^~['), '\_s\+', '\\_s\\+', 'g')<CR>
\gV:call setreg('"', old_reg, old_regtype)<CR>:set hls<CR>
If you want to keep the current view and add the search to the history, try this [not so efficient] solution:
noremap * msHmt`s*`tzt`s
It is using the marks s (save) and t (top).
A simple solution came to my mind: put map * *# in .vimrc file (it will blink though).
Many answers here outline rather simple mappings that work well for common cases, but may have side effects (like flickering by jumping back and forth) or lack robustness (may break if some regexp characters are defined as keyword characters).
If you're looking for a robust implementation and don't mind installing a plugin, you can choose from a plethora of alternatives, many of which also offer additional search-related improvements:
My SearchHighlighting plugin changes the * command, extends it to visual selections, and offers optional auto-searching of the word under the cursor.
star search changes the behavior of * to not jump to the next match, and includes the visual search from the next plugin
vim-visual-star-search provides searching of the visual selection
visualstar.vim provides searching of the visual selection
select & search can use either n/N or * in the visual selection, and can avoid jumping.
vim-asterisk provides a z* mapping that also doesn't jump, visual *, more intuitive smartcase handling, and can keep the cursor position when jumping (like ,*)
searchant.vim hooks into the built-in search commands and provides a separate highlighting for the match last jumped to.
The other answers here are good, particularly #rodrigo's, but I wanted to write a solution that preserves scroll position and does so without affecting any of the marks.
This works for me:
function! StarPositionSave()
let g:star_position_cursor = getpos('.')
normal! H
let g:star_position_top = getpos('.')
call setpos('.', g:star_position_cursor)
endfunction
function! StarPositionRestore()
call setpos('.', g:star_position_top)
normal! zt
call setpos('.', g:star_position_cursor)
endfunction
nnoremap <silent> * :call StarPositionSave()<CR>*:call StarPositionRestore()<CR>
Putting normal! * in the function directly doesn't seem to work, as (at least in neovim) it suppresses search highlighting from being triggered (as if :nohlsearch was run).
My solution:
nnoremap <silent><expr> * v:count ? '*'
\ : ':silent execute "keepjumps normal! *" <Bar> call winrestview(' . string(winsaveview()) . ')<CR>'
nnoremap <silent><expr> g* v:count ? 'g*'
\ : ':silent execute "keepjumps normal! g*" <Bar> call winrestview(' . string(winsaveview()) . ')<CR>'
Edit: Recent Vim has <Cmd> mapping, so you can use below to avoid CmdlineEnter/Leave to be fired.
nnoremap <expr> * v:count ? '*'
\ : '<Cmd>silent keepjumps normal! *<CR><Cmd>call winrestview(' .. string(winsaveview()) .. ')<CR>'
nnoremap <expr> g* v:count ? 'g*'
\ : '<Cmd>silent keepjumps normal! g*<CR><Cmd>call winrestview(' .. string(winsaveview()) .. ')<CR>'
Pros:
No flickering.
Jump list remains unchanged.
With a count, it invokes original *; you can use 1* if you miss the original *.
Marks and registers are untouched.
Using original *, its behavior is almost identical with * (except for jumping).
This means 'smartcase' is ignored as * do.
No need to install plugins.
Similar to * we have
[I ..................... it shows where the word under the cursor appears
I also have some useful lines on my vimrc that can, maybe, help you
" When double click a word vim will hightlight all other ocurences
" see CountWordFunction()
" [I shows lines with word under the cursor
nnoremap <silent> <2-LeftMouse> :let #/='\V\<'.escape(expand('<cword>'), '\').'\>'<cr>:set hls<cr>:CountWord<cr>
nnoremap <Leader>* :let #/='\V\<'.escape(expand('<cword>'), '\').'\>'<cr>:set hls<cr>:CountWord<cr>
if !exists('*CountWordFunction')
fun! CountWordFunction()
try
let l:win_view = winsaveview()
exec "%s/" . expand("<cword>") . "//gn"
finally
call winrestview(l:win_view)
endtry
endfun
endif
command! -nargs=0 CountWord :call CountWordFunction()
cnoreabbrev cw CountWord
nnoremap <F3> :CountWord<CR>
I'm adding this answer because I found the other answers either, centered the line in the view (which I found distracting), or seemed overly complicated.
The following command works well for me:
noremap * :let #/ = "\\<<C-r><C-w>\\>"<cr>:set hlsearch<cr>
It simply sets the pattern to the whole word under the cursor and then turns on (or updates) the highlighting for the search pattern.
NOTE: It doesn't modify the search history (which I prefer but might not be quite what you want).
just do
nnoremap * *N
nnoremap # #n
works with this http://www.vim.org/scripts/script.php?script_id=4335 too like so:
vnoremap * :<C-u>call VisualStarSearchSet('/')<CR>/<C-R>=#/<CR><CR>N
vnoremap # :<C-u>call VisualStarSearchSet('?')<CR>?<C-R>=#/<CR><CR>n
nnoremap <silent> ml :<c-u>let #/ = '\<'.expand('<cword>').'\>'\|set hlsearch<CR>wb
and if you want a visual mode one:
function SetSearchVisualSelection()
let clipboard_original_content=#"
normal gvy " this overwrites clipboard
let raw_search=#"
let #/=substitute(escape(raw_search, '\/.*$^~[]'), "\n", '\\n', "g")
let #"=clipboard_original_content
endfunction
vnoremap ml :call SetSearchVisualSelection()<CR>:set hlsearch<CR>

Ctrl+Space for omni and keyword completion in vim

I Want to use Ctrl+Space for omni-completion (and keyword completion if there is no omni-completion) in vim. I've tried this which I found somewhere on the web:
inoremap <expr> <c-space> pumvisible() ? "\<C-n>" : "\<C-x>\<C-o>\<C-n>\<C-p>\<C-r>=pumvisible() ? \"\\<Down>\" : \"\\<CR>\""
however it's not working. Anyone who is using Ctrl+Space for this too who can show me the correct way (which works) to do it?
Worth noting is that it needs to work in the terminal version of vim NOT gvim.
Try this:
inoremap <expr> <C-Space> pumvisible() \|\| &omnifunc == '' ?
\ "\<lt>C-n>" :
\ "\<lt>C-x>\<lt>C-o><c-r>=pumvisible() ?" .
\ "\"\\<lt>c-n>\\<lt>c-p>\\<lt>c-n>\" :" .
\ "\" \\<lt>bs>\\<lt>C-n>\"\<CR>"
imap <C-#> <C-Space>
The above way is "kind of" working, but it's so unreadable that almost nobody could say what it actually does. The solution above is not good.
Short Answer - Use this:
function! Auto_complete_string()
if pumvisible()
return "\<C-n>"
else
return "\<C-x>\<C-o>\<C-r>=Auto_complete_opened()\<CR>"
end
endfunction
function! Auto_complete_opened()
if pumvisible()
return "\<Down>"
end
return ""
endfunction
inoremap <expr> <Nul> Auto_complete_string()
inoremap <expr> <C-Space> Auto_complete_string()
This answer also respects that there are two possible values (depending on terminal/gvim usage) for Ctrl+Space: <C-Space> and <Nul>.
I use a similar approach as the first one in jedi-vim, but more customizable.
Long Answer - What the above does:
The whole escaping of the above answer is so confusing, that I've split the above answer into a readable format:
function! Auto_complete_string()
if pumvisible()
return "\<C-n>"
else
return "\<C-x>\<C-o>\<C-r>=Auto_complete_opened()\<CR>"
end
endfunction
function! Auto_complete_opened()
if pumvisible()
return "\<c-n>\<c-p>\<c-n>"
else
return "\<bs>\<C-n>"
end
endfunction
inoremap <expr> <Nul> Auto_complete_string()
This clearly shows what it is doing. There's some weird stuff happening in Auto_complete_opened. It's not just doing completion, it's doing two additional things after trying to complete:
When trying to use the omnicompletion, it somehow does a <C-n><C-p><C-n>, which could IMHO just be abbreviated to <C-n>.
In case completion is unsuccessful, it uses a backspace and does a completion again, not <C-o><C-x> but <C-n>, which just doesn't make a lot of sense.
I'm not saying that this is not what some user might want, but it's probably not what most users want! My short answer takes that in credit and gives you a simple way to edit it. You can now just easily change things if you want to (for example <Down> to <C-n>, if you want the first entry to be written from the beginning on).
For iterm2 and vim these lines works for me, I got from jedi-vim
" Next three lines are to enable C-Space to autocomplete, omnicomplete
inoremap <C-Space> <C-x><C-o>
imap <buffer> <Nul> <C-Space>
smap <buffer> <Nul> <C-Space>

Resources