How to shortcut (nmap) windo command expression? - vim

For closing all quickfix windows, I use the following vim command expression:
:windo if &buftype == 'quickfix' || &buftype == 'locationlist' | lclose | endif
Whenever I try to shortcut it like:
nmap <S-q> :let #a = "%:windo if &buftype == 'quickfix' || &buftype == 'locationlist' | lclose | endif"
by using nmap in my init.vim, I got an error:
E749: empty buffer
E488: Trailing characters
How to solve that?

Explanation
The | is a command separator; unfortunately (this is a common pitfall), it also ends any :map command, and the remainder is interpreted immediately, instead of being part of the mapping.
:help map-bar lists three different solutions; the most common is using the special <Bar> notation instead of |.
Your mapping
nmap <S-q> :let #a = "%:windo if &buftype == 'quickfix' <Bar><Bar> &buftype == 'locationlist' <Bar> lclose <Bar> endif"
Is the %:windo a typo? The % is suspicious.
The mapping is missing the trailing <CR>; it will linger until you press <Enter> yourself. Is that intended?
Why do you assign the commands to register a instead of executing it?
You should use :noremap; it makes the mapping immune to remapping and recursion.
'buftype' is always quickfix, also for location lists; you can drop the second branch in the test.
noremap <S-q> :windo if &buftype == 'quickfix' <Bar> lclose <Bar> endif<CR>

Related

Vim searched patterns in middle screen

Is there any working command / remapping so that when I do
:grep *mysearch* *here*
the results I iterate with cnext, can appear in the middle of the screen directly ?
Ideally I want n to iterate over the results and have the line in the middle screen so I don't have to find where is the cursor in my file each time I see the next results.
I tried many things such as
map :<Leader>n :cnext zz<CR> OR :cnext | :zz | <CR>
or any variations but nothing works ...
Any ideas ?
Thank you !
Based on the technique described here, you can do something like this:
" Keep quickfix result centered, if possible, when jumping from result to result.
cabbrev <silent> <expr> cn ((getcmdtype() == ':' && getcmdpos() == 3) ? 'cn <bar> normal zz<cr>' : 'cn')
cabbrev <silent> <expr> cnf ((getcmdtype() == ':' && getcmdpos() == 4) ? 'cnf <bar> normal zz<cr>' : 'cnf')
cabbrev <silent> <expr> cp ((getcmdtype() == ':' && getcmdpos() == 3) ? 'cp <bar> normal zz<cr>' : 'cp')
cabbrev <silent> <expr> cpf ((getcmdtype() == ':' && getcmdpos() == 4) ? 'cpf <bar> normal zz<cr>' : 'cpf')
I originally had this in my dotfiles but have since extracted it into a plugin with a few search-related features called Ferret.
Mappings are not really a good solution for command-line mode; but the main error with your try was some mistakes around zz, which is a normal-mode command. It could be:
:cmap <Leader>n :cnext<bar>normal zz
But :cabbr is really better for this case.
An simplified version of wincent's answer is both of these:
:cabbr cn cnext<bar>normal zz
:cabbr cn cnext\|normal zz
But you can't access to the original command by doing this : cn (with a space), like with wincent's answer.
Another good way to abbreviate commands : cmdalias.vim, which allows to only recognize abbreviations at start of command-line.

conditional map in normal mode?

Is it possible in vim to do a conditional map, in normal mode?
I've seen it for insert mode.
I want to remap gq, depending on the outcome of a function.
Something like:
nnoremap gq if(g:set_formatprg()) | gq | else | = | endif
Notice that g:set_formatprg() will not always have the same value, so it cannot be replaced by
if(!g:set_formatprg()) | nnoremap gq = | endif
An expression map makes it easy
nnoremap <expr> gq g:set_formatprg() ? 'gq' : '='
For more help see
:h map-expression

How can I make the tilde operator change ‘==’ to ‘!=’ in Vim?

I would like the normal-mode command tilde ~, in addition to changing the case of letters, to also be able to change the text == to != and != to ==.
I find that I do this quite often and I'd like a shortcut that still uses the tilde.
This is fairly simple to do in vimscript.
Add the following to your .vimrc or source this code from a different file.
" ----------------------
" Tilde switches ==/!=
" ----------------------
function! TildeSwitch()
" Gets the pair of characters under the cursor, before and behind.
let cur_pair = getline(".")[col(".") - 2 : col(".") - 1]
let next_pair = getline(".")[col(".") - 1 : col(".")]
if cur_pair == "=="
normal! "_ch!
normal! l
elseif next_pair == "=="
normal! r!
elseif cur_pair == "!="
normal! "_ch=
normal! l
elseif next_pair == "!="
normal! r=
else
" If == and != are not found, simply use the regular tilde.
normal! ~
endif
endfunction
nnoremap <silent> ~ :silent call TildeSwitch()<cr>
Toggling between two alternatives (like == and !=) is only a special case of toggling between multiple options. I'd advise against overloading the binary ~ command and instead use <C-A> / <C-X>. The SwapIt - Extensible keyword swapper plugin offers this and actually has a default option to toggle ==, !=, <=, etc.
Let me propose an alternative implementation of this extended ~ command:
nnoremap <silent> ~ :call SwitchNeq()<cr>~
function! SwitchNeq()
let [s, c] = [#/, getpos('.')]
s/[!=]\ze\%#=\|\%#[!=]\ze=/\='!='[submatch(0)=='!']/e
let #/ = s
call setpos('.', c)
endfunction

vim function for toggling colorschemes

At the moment I'm using two different keys to toogle the colorscheme
map <F8> :colors wombat256 <cr>
map <F9> :colors dimtag <cr>
I want to achieve a toggle behavior like this
function! ToggleDimTags()
if (g:colors_name == "wombat256")
colors dimtag
else
colors wombat256
endif
endfunction
My problem is that ToogleDimTags() is resetting the cursor position to the first line on every call, which is undesirable. Any suggestions appreciated.
As discussed in the comments, the problem is that your map calling :execute
behaves a little differently, what you probably want is :call instead:
nnoremap <F5> :call ToggleDimTags()
To clarify what #ZyX was saying, :h :exec contains the following text:
:exe :execute
:exe[cute] {expr1} .. Executes the string that results from the evaluation
of {expr1} as an Ex command.
[...]
So what :execute really does is evaluating the expression looking for a string
that will be executed as an Ex command (also called colon commands). In other words:
exec ToggleDimTags() | " <-- ToggleDimTags() is evaluated and returns 0
exec 0
Which is:
:0
Now, :h :call:
:cal :call E107 E117
:[range]cal[l] {name}([arguments])
Call a function. The name of the function and its arguments
are as specified with |:function|. Up to 20 arguments can be
used. **The returned value is discarded**.
[...]
Update
I've been thinking about your function, and using the ternary operator and a bit
of :execute magic, you can simplify it up to a point where you discard the extra
function:
nnoremap <silent> <F9> :exec "color " .
\ ((g:colors_name == "wombat256") ? "dimtag" : "wombat256")<CR>
Here, this nnoremap will not produce output (<silent>) and is based on
:exec, which is followed by this expression:
"color " . ((g:colors_name == "wombat256") ? "dimtag" : "wombat256")
When g:colors_name is set to wombat256, the expression evaluates to:
"color dimtag"
Or, otherwise:
"color wombat256"
Then either one is evaluated by :exec. Of course you can join the lines
(without forgetting to remove the backslash), I did it like this simply to avoid
a too long line.

How do you exit vimdiff mode in vim, specifically, for Fugitive?

I am using vim with the fugitive extension. It has a :Gdiff command which brings you into vimdiff mode, but what is the right/quick way to close/quit vimdiff mode?
I.e., let's say I am editing the file FooBar.txt under Git repository. I fire up :Gdiff, review my changes in vimdiff, and then I want to get back and continue editing FooBar.txt or any other file :)
UPDATE1: I'm going to give these quick combos a try next working day :)
"vimdiff current vs git head (fugitive extension)
nnoremap <Leader>gd :Gdiff<cr>
"switch back to current file and closes fugitive buffer
nnoremap <Leader>gD :diffoff!<cr><c-w>h:bd<cr>
UPDATE2: My current mappings (closes diff window only!)
"vimdiff current vs git head (fugitive extension)
nnoremap <Leader>gd :Gdiff<cr>
"switch back to current file and closes fugitive buffer
nnoremap <Leader>gD <c-w>h<c-w>c
Also, please help me decide if the following should be an anwser: https://stackoverflow.com/a/15975201/275980
You can execute windo set nodiff noscrollbind and then close the second window.
Update: there is a diffoff command. Use windo diffoff, not what I wrote in previous line.
According to: https://github.com/tpope/vim-fugitive/issues/36
Close the other window. The easiest way to do this if you haven't shifted focus to it is <C-W><C-O>, which means "make this Window the Only window."
I had no luck with diffoff, but I just learned that :Gedit with no argument will bring you back to the working-directory version of the file, as opposed to some earlier version you were reviewing.
And as q (no need for :q) will close the diff sidebar, you can do q followed by :Gedit to get rid of the sidebar and then go back to the current version of the file.
None of the above solutions worked for me. Ended up doing this instead:
nnoremap <Leader>D :Gedit<CR><C-w>h :q<CR><C-w>k
This works fine for me, combining some of the existing ideas here:
function! MyCloseDiff()
if (&diff == 0 || getbufvar('#', '&diff') == 0)
\ && (bufname('%') !~ '^fugitive:' && bufname('#') !~ '^fugitive:')
echom "Not in diff view."
return
endif
" close current buffer if alternate is not fugitive but current one is
if bufname('#') !~ '^fugitive:' && bufname('%') =~ '^fugitive:'
if bufwinnr("#") == -1
b #
bd #
else
bd
endif
else
bd #
endif
endfunction
nnoremap <Leader>gD :call MyCloseDiff()<cr>
An alternative to <C-W><C-O>, if you have multiple windows, would be move to the other diff window and do <C-W>c, which close only one window.
If you close the wrong diff window do a :Gedit
Be careful and don't confuse <C-W>c with <C-W><C-C>
I've found a simple solution for this. You can check it here: https://gist.github.com/radmen/5048080
" Simple way to turn off Gdiff splitscreen
" works only when diff buffer is focused
if !exists(":Gdiffoff")
command Gdiffoff diffoff | q | Gedit
endif
Check the vimdiff toggling between diffthis and diffoff here
at this page.
The code:
nnoremap <silent> <Leader>df :call DiffToggle()<CR>
function! DiffToggle()
if &diff
diffoff
else
diffthis
endif
:endfunction
Method 1:
open a compare by:
:windo diffthis
close it by:
:windo diffoff
Method 2:
I recommend just using the most simple command: :q<CR>
when you want to do it quickly, add the mapping:
" Set mapleader
let mapleader = ","
let g:mapleader = ","
and
" Quickly close the current window
nnoremap <leader>q :q<CR>
It works well for me. Exit vimdiff just by ,q, because normally your cursor in the old file.
this is what I have to leave the vimdiff windows after using :Gdiff
nnoremap gD :q!<CR> :Gedit!<CR>
noremap <leader>do :diffoff \| windo if &diff \| hide \| endif<cr>
Quite diff mode and close other diff windows. (Note: fugitive will auto delete its hidden buffers.)
My function will work both from diff window and file window. But probably won't handle itself with multiple diffs opened. For that you'll need to use fugitive#buffer(n).path() to scan and match.
command! Gdiffoff call Gdiffoff()
function! Gdiffoff()
let diffbufnr = bufnr('^fugitive:')
if diffbufnr > -1 && &diff
diffoff | q
if bufnr('%') == diffbufnr | Gedit | endif
setlocal nocursorbind
else
echo 'Error: Not in diff or file'
endif
endfunction
Add a key binding:
nnoremap <silent> <leader>gD :Gdiffoff<CR>
Yet another way. What I have in fugitive.vim - first save some info (s:gitbufname) when diff starts:
function! s:Diff(vert,...) abort
call sy#toggle()
let s:startcol = winwidth(0)
let &columns=(winwidth(0) * 2 - 20)
...
if getwinvar('#', '&diff')
let s:gitbufname = bufname("%")
wincmd p
call feedkeys(winnr."\<C-W>w", 'n')
endif
...
endfunction
and later when leaving the buffer switch window to the saved buffer and restore:
augroup fugitive_diff
autocmd!
autocmd BufWinLeave *
\ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
\ if exists('s:gitbufname') && winnr() != bufwinnr(s:gitbufname) |
\ let nr = bufnr("%") | exe bufwinnr(s:gitbufname).'wincmd w' | exe 'buf'.nr |
\ endif |
\ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
\ call sy#toggle() |
\ call airline#load_theme() | call airline#update_statusline() |
\ let &columns=s:startcol |
\ endif
...
Was using the code below based on https://stackoverflow.com/a/15113951/10999673 :
if !exists(":Gdiffoff")
command Gdiffoff bw! fugitive://*
endif
but it gave me an error "E93: more than one match for ..." in a 3 way diff, so i instead used the answer from https://stackoverflow.com/a/4867969/10999673 and finally have this:
function! GetBufferList()
return filter(range(1,bufnr('$')), 'buflisted(v:val)')
endfunction
function! GetMatchingBuffers(pattern)
return filter(GetBufferList(), 'bufname(v:val) =~ a:pattern')
endfunction
function! WipeMatchingBuffers(pattern)
let l:matchList = GetMatchingBuffers(a:pattern)
let l:count = len(l:matchList)
if l:count < 1
echo 'No buffers found matching pattern ' . a:pattern
return
endif
if l:count == 1
let l:suffix = ''
else
let l:suffix = 's'
endif
exec 'bw ' . join(l:matchList, ' ')
echo 'Wiped ' . l:count . ' buffer' . l:suffix . '.'
endfunction
command! -nargs=1 Gdiffoff call WipeMatchingBuffers('fugitive://')
I just tweaked, copied and pasted the code into my .vimrc
Running :Gwrite after merging to your satisfaction will close the other two diff panes in addition to updating the git cache to mark the file as merged.

Resources