change from insert to normal mode when switching to another tab? - vim

Say I have multiple tabs with multiple buffers in split screens.
When I am in edit mode in one buffer and switch to another tab (ctrl-pageDown), I am still in insert mode.
Is there a way to automatically switch to normal mode when changing tabs ?
Even better, is it possible to return to insert mode when coming back to the original buffer ?

You could try adding something very simple like
autocmd TabEnter * stopinsert
to your .vimrc.

In BufLeave you could call a function which would check what mode you're in and set a buffer variable and then in BufEnter check if it exists and go to that mode.
See help on mode(), b:var.
Here is some sample stuff for .vimrc. Having written it just now for this purpose, I've started using it myself and I think it'll be useful.
au BufLeave * call ModeSelectBufLeave()
au BufEnter * call ModeSelectBufEnter()
function! ModeSelectBufLeave()
let b:mode_select_mode = mode()
" A more complex addition you could make: if mode() == v, V, <C-V>, s, S, or <C-S>, store the selection and restore it in ModeSelectBufEnter
endfunction
function! ModeSelectBufEnter()
let l:mode = mode()
stopinsert " First, go into normal mode
if (l:mode == "i" || l:mode == "R" || l:mode == "Rv") &&
\ (!exists('b:mode_select_mode') ||
\ b:mode_select_mode == "n" ||
\ b:mode_select_mode == "v" ||
\ b:mode_select_mode == "V" ||
\ b:mode_select_mode == "\<C-V>" ||
\ b:mode_select_mode == "s" ||
\ b:mode_select_mode == "S" ||
\ b:mode_select_mode == "\<C-S>")
normal l
" Compensate for the left cursor shift in stopinsert if going from an
" insert mode to a normal mode
endif
if !exists('b:mode_select_mode')
return
elseif b:mode_select_mode == "i"
startinsert
elseif b:mode_select_mode == "R"
startreplace
elseif b:mode_select_mode == "Rv"
startgreplace
endif
endfunction

I have the following in my .vimrc:
nmap <C-b> :b#<CR>
imap <C-b> <ESC>:b#<CR>
This lets me hit Ctrl+b when in normal or insert mode to switch to the alternate buffer but leaving me in normal mode.
As for your question, you could do this:
imap <C-b> <ESC>:bnext<CR>i
This will let you hit Ctrl+b when in insert mode and switch to the next buffer putting you in insert mode when you get there.
If you find yourself switching back and forth between the same two buffers, my original mappings above may be more useful. Of course if you use all three, you'll need a different key combination for the last one.

Related

How to reopen a closed file in vim? [duplicate]

Is it possible to reopen closed window in vim, that was in split?
Something like ctrl+shift+t with browser tabs?
:vs# will split current window vertically and open the alternate file.
It's so simple that you don't need to bind it to key.
Nice question! I was thinking to something like the following:
nmap <c-s-t> :vs<bar>:b#<CR>
It should work as you want.
No need for SHIFT:
nmap <c-t> :vs<bar>:b#<CR>
In conjunction with CTRL the characters are handled equally by vim, capitalized or not.
Actually also in the answer before, CTRLn and CTRLSHIFTN should both work.
I've gotten this to work by using bufmru.vim!
The following command, :ReopenLastTab, will re-split the last-open buffer:
command ReopenLastTab execute "vsplit" bufname(g:bufmru_bnrs[1])
I installed bufmru using Vundle, as below, but of course you can install it any way you like.
#.vimrc
" Install bufmru with Vundle
Plugin 'vim-scripts/bufmru.vim'
let g:bufmru_switchkey = "<c-t>" " I never use this: the default is Space, but I don't need to use it so set it to something I don't care about.
If anyone need something more generic I made this function.
Just place it in your .vimrc
" open last closed buffer
function! OpenLastClosed()
let last_buf = bufname('#')
if empty(last_buf)
echo "No recently closed buffer found"
return
endif
let result = input("Open ". last_buf . " in (n)ormal (v)split, (t)ab or (s)plit ? (n/v/t/s) : ")
if empty(result) || (result !=# 'v' && result !=# 't' && result !=# 's' && result !=# 'n')
return
endif
if result ==# 't'
execute 'tabnew'
elseif result ==# 'v'
execute "vsplit"
elseif result ==# 's'
execute "split"
endif
execute 'b ' . last_buf
endfunction
nnoremap <C-t> :call OpenLastClosed() <CR>
Call it with Ctrl+t and then select where you want to open that file.

How to prompt a user for multiple entries in a list?

I'm working on a vim plugin. There are a set of valid options for a configuration parameter. I would like the user to be able to select the options they want from a list.
This is similar to inputlist, but inputlist only returns the index of the single chosen element. I'd prefer it return the indexes of all chosen elements.
How would I create a mutliselect in vim?
I don't know exactly which kind of interface you have in mind, but since you mentioned inputlist(), I thought you could simply write a loop whose body would invoke it.
Maybe something like this:
let options_chosen = []
let options_valid = [
\ 'foo',
\ 'bar',
\ 'baz',
\ 'qux',
\ 'norf'
\ ]
for i in range(1,len(options_valid))
let choice = inputlist([ 'Select your options:' ]
\ + map(copy(options_valid), '(v:key+1).". ".v:val'))
if choice >= 1 && choice <= len(copy(options_valid))
let options_chosen += [copy(options_valid)[choice - 1]]
let options_valid = filter(options_valid, 'v:val !=# options_chosen[-1]')
else
break
endif
redraw
endfor
If you execute this code, it should let you choose an option from the list options_valid. After each iteration, it should add the chosen item inside the list options_chosen and remove it from the list options_valid. The loop iterates as many times as there are items in options_valid initially. When you're done, you can stop the loop by hitting Escape.
It may not be what you want, because I don't know what interface you want to present to the user: a command, a mapping, an interactive buffer... But it may be a start, upon which you could build something else.
With an interactive buffer as the interface, I came up with this:
let s:options_valid = ['foo', 'bar', 'baz', 'qux', 'norf']
com! MultipleOptions call s:multiple_options()
fu! s:multiple_options() abort
vnew | exe 'vert resize '.(&columns/3)
setl bh=wipe bt=nofile nobl noswf nowrap
if !bufexists('Multiple Options') | sil file Multiple\ Options | endif
sil! 0put =s:options_valid
sil! $d_
setl noma ro
nno <silent> <buffer> <nowait> q :<c-u>close<cr>
nno <silent> <buffer> <nowait> <cr> :<c-u>call <sid>toggle_option()<cr>
augroup multi_op_close
au!
au WinLeave <buffer> call s:close()
augroup END
endfu
fu! s:close() abort
let g:selected_options = exists('w:options_chosen')
\ ? map(w:options_chosen.lines, 's:options_valid[v:val-1]')
\ : []
au! multi_op_close | aug! multi_op_close
close
endfu
fu! s:toggle_option() abort
if !exists('w:options_chosen')
let w:options_chosen = { 'lines' : [], 'pattern' : '', 'id' : 0 }
else
if w:options_chosen.id
call matchdelete(w:options_chosen.id)
let w:options_chosen.pattern .= '|'
endif
endif
if !empty(w:options_chosen.lines) && count(w:options_chosen.lines, line('.'))
call filter(w:options_chosen.lines, "v:val != line('.')")
else
let w:options_chosen.lines += [ line('.') ]
endif
let w:options_chosen.pattern = '\v'.join(map(
\ copy(w:options_chosen.lines),
\ "'%'.v:val.'l'"
\ ), '|')
let w:options_chosen.id = !empty(w:options_chosen.lines)
\ ? matchadd('IncSearch', w:options_chosen.pattern)
\ : 0
endfu
If you execute the command :MultipleOptions, it should open a temporary vertical viewport, in which the options stored inside the list s:options_valid should be displayed.
From there, you can hit Enter to select or deselect the current line. When an option is selected, its line is colored with the highlighting group IncSearch.
When you're done, you can close the window hitting q, and all your chosen options should be inside g:selected_options.
In lh-vim-lib I provide a lh#ui#check() function that does exactly that. Its behaviour is similar to confirm() in text mode. i.e. I rely on an old trick that hacks the statusline. (This needs to play with "\r", :redraw and so on)
You can see it live in the screencast I've made for lh-tags. Wait for the "Which kinds to you wish to display?" question. (In the screencast you should see the old code with CHECK, CONFIRM and CHOOSE)
BTW, the dialog used in lh-tags to choose one entry here can also be used (with the tagged parameter set to one) to select several entries at once.

Using Vim command to insert text to multiple lines

I know in visual block mode, by <S-i> (I) one can insert in multiple selected lines, however I want to achieve the same effect by a function, let's say I have a functions which can tell the three sub-visual modes (visual-character, visual-line, visual-block) as follows,
function! VisualMappingSpace()
let m = visualmode()
if m ==# 'v'
echo 'character-wise visual'
elseif m == 'V'
echo 'line-wise visual'
elseif m == "\<C-V>"
echo 'block-wise visual'
endif
endfunction
I've tried as follows but it doesn't work. I want to insert soemthing to the lines I select when I hit <space> in visual-block mode.
function! VisualMappingSpace()
let m = visualmode()
if m ==# 'v'
exec "normal y"
elseif m == 'V'
exec "normal y"
elseif m == "\<C-V>"
let g:block_insert_content = input("")
exec "normal I ".g:block_insert_content
endif
endfunction
vnoremap <silent> <Space> :call VisualMappingSpace()<CR>
A visual-mode mapping that enters command-line mode via : will have the visual range ('<,'>) automatically inserted. With :call, that means that your function is invoked once per selected line. You should have noticed via the repeated queries.
To avoid this, insert <C-u> into your mapping; it clears the range.
Second problem: When you insert the queried text, you need to re-create the selection (your mapping left visual mode for command-line mode, remember?) via gv; then, I will work:
function! VisualMappingSpace()
let m = visualmode()
if m ==# 'v'
exec "normal y"
elseif m == 'V'
exec "normal y"
elseif m == "\<C-V>"
let g:block_insert_content = input("")
exec "normal gvI ".g:block_insert_content
endif
endfunction
vnoremap <silent> <Space> :<C-u>call VisualMappingSpace()<CR>
Also note that there is an additional space character before your queried text; I'm not sure you want that: gvI ".

Automatically quit Vim if NERDTree and TagList are the last and only buffers

Basically, my .vimrc starts TagList and NERDTree when Vim is launched, as splits on the left and on the right of the normal file buffer.
I want to close Vim when, closing the last buffer/tab, TagList and NERDTree splits are the only remained. I'm already using vim-nerdtree-tabs and it works great when NERDTree is the only and last buffer open.
I'm aware that such topic has been discussed here on StackOverflow but I cannot find anything related to both NERDTree and TagList.
Thanks
let Tlist_Exit_OnlyWindow = 1
will close Tag_list window if it's the last window, look at http://vim-taglist.sourceforge.net/manual.html for more infomation about Tlist_Exit_OnlyWindow, I'm not sure if you are looking for this, if not, please delete my answer.
Something like... (untested)
fun! NoExcitingBuffersLeft()
if tabpagenr("$") == 1 && winnr("$") == 2
let window1 = bufname(winbufnr(1))
let window2 = bufname(winbufnr(2))
if (window1 == t:NERDTreeBufName || window1 == "__Tag_List__") &&
(window2 == t:NERDTreeBufName || window2 == "__Tag_List__")
quit
endif
endif
endfun
then tie that function to an autocommand...
au WinEnter * call NoExcitingBuffersLeft()<cr>
I don't use either of those plugins, so you may need to adjust the t:NERDTreeBufName and __Tag_List__.
Improving on Conner's idea, I've made a functional solution here.
" If only 2 windows left, NERDTree and Tag_List, close vim or current tab
fun! NoExcitingBuffersLeft()
if winnr("$") == 3
let w1 = bufname(winbufnr(1))
let w2 = bufname(winbufnr(2))
let w3 = bufname(winbufnr(3))
if (exists(":NERDTree")) && (w1 == "__Tag_List__" || w2 == "__Tag_List__" || w3 == "__Tag_List__")
if tabpagenr("$") == 1
exec 'qa'
else
exec 'tabclose'
endif
endif
endif
endfun
autocmd BufWinLeave * call NoExcitingBuffersLeft()
Need vim 7.0+ for the BufWinLeave event.
Closes the tab if more than one tab is open, otherwise quits vim.
This way, the auto-command is tied to when you close the last window that's not NERDTree or Tag_List, rather than upon entering one of the two windows.
This is nice extendable solution. To validate against other plugins/window types just add them to the regex check.
function! s:CloseAddons()
for w in range(1, winnr('$'))
if bufname(winbufnr(w)) !~# '__Tagbar\|NERD_tree_\|coc-explorer'
\ && getbufvar(winbufnr(w), "&buftype") !=? "quickfix"
return
endif
endfor
if tabpagenr('$') ==? 1
execute 'quitall'
else
execute 'tabclose'
endif
endfunction

Vim, reopen last closed window, that was in split

Is it possible to reopen closed window in vim, that was in split?
Something like ctrl+shift+t with browser tabs?
:vs# will split current window vertically and open the alternate file.
It's so simple that you don't need to bind it to key.
Nice question! I was thinking to something like the following:
nmap <c-s-t> :vs<bar>:b#<CR>
It should work as you want.
No need for SHIFT:
nmap <c-t> :vs<bar>:b#<CR>
In conjunction with CTRL the characters are handled equally by vim, capitalized or not.
Actually also in the answer before, CTRLn and CTRLSHIFTN should both work.
I've gotten this to work by using bufmru.vim!
The following command, :ReopenLastTab, will re-split the last-open buffer:
command ReopenLastTab execute "vsplit" bufname(g:bufmru_bnrs[1])
I installed bufmru using Vundle, as below, but of course you can install it any way you like.
#.vimrc
" Install bufmru with Vundle
Plugin 'vim-scripts/bufmru.vim'
let g:bufmru_switchkey = "<c-t>" " I never use this: the default is Space, but I don't need to use it so set it to something I don't care about.
If anyone need something more generic I made this function.
Just place it in your .vimrc
" open last closed buffer
function! OpenLastClosed()
let last_buf = bufname('#')
if empty(last_buf)
echo "No recently closed buffer found"
return
endif
let result = input("Open ". last_buf . " in (n)ormal (v)split, (t)ab or (s)plit ? (n/v/t/s) : ")
if empty(result) || (result !=# 'v' && result !=# 't' && result !=# 's' && result !=# 'n')
return
endif
if result ==# 't'
execute 'tabnew'
elseif result ==# 'v'
execute "vsplit"
elseif result ==# 's'
execute "split"
endif
execute 'b ' . last_buf
endfunction
nnoremap <C-t> :call OpenLastClosed() <CR>
Call it with Ctrl+t and then select where you want to open that file.

Resources