Switch to last-active tab in VIM - vim

In Vim, is there a way to quickly toggle between the current tab and the last-active tab? Sort of the way '' toggles between the current line and the last-active line. Plugins / keyboard mappings / voodoo all acceptable.

Put this in your .vimrc:
if !exists('g:lasttab')
let g:lasttab = 1
endif
nmap <Leader>tl :exe "tabn ".g:lasttab<CR>
au TabLeave * let g:lasttab = tabpagenr()
Then, in normal mode, type \tl to swap to the tab you viewed last.

Fix the potential issue when a tab is closed:
" Switch to last-active tab
if !exists('g:Lasttab')
let g:Lasttab = 1
let g:Lasttab_backup = 1
endif
autocmd! TabLeave * let g:Lasttab_backup = g:Lasttab | let g:Lasttab = tabpagenr()
autocmd! TabClosed * let g:Lasttab = g:Lasttab_backup
nmap <silent> <Leader>` :exe "tabn " . g:Lasttab<cr>

I use buffers and not tabs, but I am able to switch between the current and latest used buffer using :b#
Basics of using buffers are:
:e filename to open file in new buffer
:bn to go to next buffer
:bp to go to previous buffer
:bd to close current buffer

here's a solution in lua for folks that uses neovim also make sure to change <A-S-b> to your favorite keybinding.
-- switching to last active tab
vim.api.nvim_create_autocmd("TabLeave", {
pattern = "*",
callback = function()
vim.api.nvim_set_keymap('n', '<A-S-b>', '<cmd>tabn ' .. vim.api.nvim_tabpage_get_number(0) .. '<CR>', { noremap = true, silent = true })
end
})

Related

Automatically Quit Vim if NERDTree and TagList are the only 2 Buffers Left

I have this code taken from another StackOverflow user Conner, from this question Automatically quit Vim if NERDTree and TagList are the last and only buffers
(There wasn't an option for me to comment on that question, so my only option was to ask a new one).
The question is: How do I close Vim editor (in Linux Mint) if only NERDTree and TagList are the only two buffers left?
The answer provided was:
" 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()
But that does not work properly. Is closes whenever I close the last "exciting" buffer (one that is non-NERDTree or non-TagList), but it also closes whenever I try to open a new file from NERDTree (by double clicking on a filename in the "explorer").
Honestly, I do not understand this code too well. I have tried to mess around with it but I couldn't get the results I would like.
How do I alter this code to not close when I open a new file from NERDTree explorer?
Thank you, Conner, and the rest of the community!
From the Taglist manual, put on your .vimrc file
let Tlist_Exit_OnlyWindow=1
I don't use NERDTree, but you may succeed with the following
autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTreeType") && b:NERDTreeType == "primary") | q | endif

Is it possible to just view files content when traverse NERDTree?

I can traverse NERDTree, but to see file content I press go, and once opened file's buffer stays open until I explicitely close it. That makes viewing files too uncomfortable.
when I traverse NERDTree nodes I'd like to see the highlighted file content in a temporary viewer buffer and I'd like to explicitely select some of traversed files for editing, say by pressing e.
When I close NERDTree buffer, the temporary viewer buffer shall close as well, and there should only be opened buffers for those explicitely selected files, not for all viewed files.
Is that possible?
Looks like that could be a nice feature request for NERDTree :)
Meanwhile, you could put in your ~/.vimrc something like the following:
let g:nerd_preview_enabled = 0
let g:preview_last_buffer = 0
function! NerdTreePreview()
" Only on nerdtree window
if (&ft ==# 'nerdtree')
" Get filename
let l:filename = substitute(getline("."), "^\\s\\+\\|\\s\\+$","","g")
" Preview if it is not a folder
let l:lastchar = strpart(l:filename, strlen(l:filename) - 1, 1)
if (l:lastchar != "/" && strpart(l:filename, 0 ,2) != "..")
let l:store_buffer_to_close = 1
if (bufnr(l:filename) > 0)
" Don't close if the buffer is already open
let l:store_buffer_to_close = 0
endif
" Do preview
execute "normal go"
" Close previews buffer
if (g:preview_last_buffer > 0)
execute "bwipeout " . g:preview_last_buffer
let g:preview_last_buffer = 0
endif
" Set last buffer to close it later
if (l:store_buffer_to_close)
let g:preview_last_buffer = bufnr(l:filename)
endif
endif
elseif (g:preview_last_buffer > 0)
" Close last previewed buffer
let g:preview_last_buffer = 0
endif
endfunction
function! NerdPreviewToggle()
if (g:nerd_preview_enabled)
let g:nerd_preview_enabled = 0
augroup nerdpreview
autocmd!
augroup END
else
let g:nerd_preview_enabled = 1
augroup nerdpreview
autocmd!
autocmd CursorMoved * nested call NerdTreePreview()
augroup END
endif
endfunction
This is probably quite naive and nasty code, but with some tweaking could do what you intend to do.
Edited, changes in version 2:
Added nested on the autocommand so syntax highlight works
Not enabled by default, execute :call NerdPreviewToggle() to enable/disable
I built on DavidEG's answer by taking tabs into account, because I ran into multiple nerdtree tab edge-cases.
function! PreviewNERDTreeFile()
if !exists('t:previous_preview_buffer') | let t:previous_preview_buffer = 0 | endif
let filename = substitute(getline('.'), '^\s*\|\s*$', '','g')
let should_close_buffer_next_time = 1
if (bufnr(filename) > 0) | let should_close_buffer_next_time = 0 | endif
normal go
if t:previous_preview_buffer > 0
exe 'bwipeout ' . t:previous_preview_buffer
let t:previous_preview_buffer = 0
endif
if should_close_buffer_next_time
let t:previous_preview_buffer = bufnr(filename)
endif
endfunction
Here's a NERDTree extension that uses the last active buffer as a preview window, and either hijacks that window or splits it with the original content depending on if you open with o, s, i, gs, gi etc.
https://github.com/numEricL/nerdtree-live-preview

Vim - show diff on commit in mercurial;

In my .hgrc I can provide an editor or a command to launch an editor with options on commit.
I want to write a method or alias that launches $ hg ci, it would not only open up message in Vim, but also would split window and there print out $ hg diff.
I know that I can give parameters to vim by using +{command} option. So launching $ vim "+vsplit" does the split but any other options goes to first opened window. So I assume i need a specific function, yet I have no experience in writing my own Vim scripts.
The script should:
Open new vertical split with empty buffer (with vnew possibly)
In empty buffer launch :.!hg diff
Set empty buffer file type as diff :set ft=diff
I've written such function:
function! HgCiDiff()
vnew
:.!hg diff
set ft=diff
endfunction
And in .hgrc I've added option: editor = vim "+HgCiDiff()"
It kind of works, but I would like that splited window would be in right side (now it opens up in left) and mercurial message would be focused window. Also :wq could be setted as temporary shortcut to :wq<CR>:q! (having an assumption that mercurial message is is focused).
Any suggestions to make this a bit more useful and less chunky?
UPDATE: I found vim split guide so changing vnew with rightbelow vnew opens up diff on the right side.
So I expanded my own code:
function! HgCiDiff()
"In .hgrc editor option I call vim "+HgCiDiff()"
"It opens new split with diff inside
rightbelow vnew
:.!hg diff
set ft=diff
saveas! /tmp/hgdiff.txt
execute "normal \<c-w>w"
endfunction
Yet It missed :wq mapping as :wqa, yet using :wqa is not that hard.
Sources of my vimrc is located here: http://hg.jackleo.info/vim-configs/src/08df5cb9d143/vimrc
Sources of my hgrc is located here: http://hg.jackleo.info/home-configs/src/22f5fb47a7d2/.hgrc
Update: as suggested by Randy Morris I updated my code and now it works just as I wanted. Thanks! Also added few extra features as the time went by.
function! HgCiDiff()
"In .hgrc editor option I call vim "+HgCiDiff()"
"It opens new split with diff inside
rightbelow vnew
setlocal buftype=nofile
:.!hg diff
setlocal ft=diff
wincmd p
setlocal spell spelllang=en_us
cnoremap wq wqa
cnoremap q qa
start
endfunction
Edit
Hmm I think this might not be what you are after on second reading. I understand you want a multi-file (unified) diff. I'd really use a hg-aware UI tool and a separate vim editor for the commit message. Sorry about that.
I'll leave the 'original' response stand in case you didn't know VCSCommand + Hg + Vim yet:
My weapon of choice is to abstract it all away with
vcscommand.vim : CVS/SVN/SVK/git/hg/bzr integration plugin
You would
:VCSVimDiff
to diffsplit against the repo version (also with Leadercv)
:VCSVimDiff <revid>
to compare against a specific revision.
My solution consists of three vim files. It doesn't require hg configuration changes, and only shows the diff for the files you're committing, if you've used hg commit file1 file2:
~/.vim/ftdetect/hg.vim
au BufRead,BufNewFile /tmp/hg-editor-*.txt set filetype=hg
~/.vim/syntax/hg.vim
" Vim syntax file
" Language: hg commit file
" Maintainer: Marius Gedminas <marius#gedmin.as>
" Filenames: /tmp/hg-editor-*.txt
" Last Change: 2012 July 8
" Based on gitcommit.vim by Tim Pope
if exists("b:current_syntax")
finish
endif
syn case match
syn sync minlines=50
if has("spell")
syn spell toplevel
endif
syn match hgComment "^HG: .*"
hi def link hgComment Comment
let b:current_syntax = "hg"
~/.vim/ftplugin/hg.vim
" Show diff while editing a Mercurial commit message
" Inspired by http://stackoverflow.com/questions/8009333/vim-show-diff-on-commit-in-mercurial
" and Michael Scherer' svn.vim
function! HgCiDiff()
let i = 0
let list_of_files = ''
while i <= line('$') && getline(i) != 'HG: --'
let i = i + 1
endwhile
while i <= line('$')
let line = getline(i)
if line =~ '^HG: \(added\|changed\)'
let file = substitute(line, '^HG: \(added\|changed\) ', '', '')
let file = "'".substitute(file, "'", "'\''", '')."'"
let list_of_files = list_of_files . ' '.file
endif
let i = i + 1
endwhile
if list_of_files == ""
return
endif
pclose
new
setlocal ft=diff previewwindow bufhidden=delete nobackup noswf nobuflisted nowrap buftype=nofile
silent exec ':0r!hg diff ' . list_of_files
setlocal nomodifiable
goto 1
redraw!
" nooo idea why I have to do this
syn enable
endfunction
call HgCiDiff()
Here's my variation based on Marius Gedminas and JackLeo's versions:
function! HgCiDiff()
" find files that were changed (not interested in added or deleted)
let changed_files = []
let pattern = '\vHG: changed \zs(.+)\ze'
while search("HG: changed", "W") > 0
let line_text = getline(line("."))
call add(changed_files, matchstr(line_text, pattern))
endwhile
let diff_cmd = "hg diff " . join(changed_files, " ")
" Reset cursor to beginning of the buffer
call cursor(1, 1)
rightbelow vnew
setlocal buftype=nofile
let diff_output = system(diff_cmd)
call append(0, split(diff_output, "\n"))
" Reset cursor to beginning of the buffer
call cursor(1, 1)
setlocal ft=diff
wincmd p
setlocal spell spelllang=en_us
cnoremap wq wqa
cnoremap q qa!
startinsert
endfunction

Setting netrw like NERDTree

I used nmap <silent> <f2> :NERDTreeToggle<cr> to toggle nerdtree window. How can I do the same with netrw?
nerdtree window is not shown in the buffer list(:ls). netrw is listed in the buffer list. How can I make it not listed?
:bn command works but :bp command does not work in the netrw window. Is this a bug?
The 'Vexplore' command opens a vertical directory browser. You can build on this by adding the following code to your .vimrc file to toggle the vertical browser with Ctrl-E (for example):
" Toggle Vexplore with Ctrl-E
function! ToggleVExplorer()
if exists("t:expl_buf_num")
let expl_win_num = bufwinnr(t:expl_buf_num)
if expl_win_num != -1
let cur_win_nr = winnr()
exec expl_win_num . 'wincmd w'
close
exec cur_win_nr . 'wincmd w'
unlet t:expl_buf_num
else
unlet t:expl_buf_num
endif
else
exec '1wincmd w'
Vexplore
let t:expl_buf_num = bufnr("%")
endif
endfunction
map <silent> <C-E> :call ToggleVExplorer()<CR>
The code above tries to open the Explorer window on the left hand side of the screen at all times; I use it with multiple split vertical windows open.
[OPTIONAL] You might like to add the following lines to your .vimrc to improve the browsing experience:
" Hit enter in the file browser to open the selected
" file with :vsplit to the right of the browser.
let g:netrw_browse_split = 4
let g:netrw_altv = 1
" Change directory to the current buffer when opening files.
set autochdir
Starting with netrw v150, there's :Lexplore, which will toggle a netrw window on the left-hand side.
I just did some improvements on Nick's solution which fixes:
opens 100% high window (independent from window splits)
:Lexplore opens it on left side, :Lexplore! on the right
listing the directory of the current file (even on remote directories)
Put these lines to the end of your .vimrc:
com! -nargs=* -bar -bang -complete=dir Lexplore call netrw#Lexplore(<q-args>, <bang>0)
fun! Lexplore(dir, right)
if exists("t:netrw_lexbufnr")
" close down netrw explorer window
let lexwinnr = bufwinnr(t:netrw_lexbufnr)
if lexwinnr != -1
let curwin = winnr()
exe lexwinnr."wincmd w"
close
exe curwin."wincmd w"
endif
unlet t:netrw_lexbufnr
else
" open netrw explorer window in the dir of current file
" (even on remote files)
let path = substitute(exists("b:netrw_curdir")? b:netrw_curdir : expand("%:p"), '^\(.*[/\\]\)[^/\\]*$','\1','e')
exe (a:right? "botright" : "topleft")." vertical ".((g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize) . " new"
if a:dir != ""
exe "Explore ".a:dir
else
exe "Explore ".path
endif
setlocal winfixwidth
let t:netrw_lexbufnr = bufnr("%")
endif
endfun
Suggested options to behave like NERDTree:
" absolute width of netrw window
let g:netrw_winsize = -28
" do not display info on the top of window
let g:netrw_banner = 0
" tree-view
let g:netrw_liststyle = 3
" sort is affecting only: directories on the top, files below
let g:netrw_sort_sequence = '[\/]$,*'
" use the previous window to open file
let g:netrw_browse_split = 4
Toggle function
Here is my version of toggle function, based on Nick's answer. Now you can use hotkey from any pane, not only from netrw's pane. In Nick's version it causes an error, also I did some code cleanup and remap it to Ctrl-O, because Ctrl-E is used by default to scroll down by one line.
" Toggle Vexplore with Ctrl-O
function! ToggleVExplorer()
if exists("t:expl_buf_num")
let expl_win_num = bufwinnr(t:expl_buf_num)
let cur_win_num = winnr()
if expl_win_num != -1
while expl_win_num != cur_win_num
exec "wincmd w"
let cur_win_num = winnr()
endwhile
close
endif
unlet t:expl_buf_num
else
Vexplore
let t:expl_buf_num = bufnr("%")
endif
endfunction
map <silent> <C-O> :call ToggleVExplorer()<CR>
Variable "t:expl_buf_num" is global for current tab, so you can have one Explorer per tab. You can change it to "w:expl_buf_num" if you want to be able to open Explorer in every window.
Keep focus in Explorer
Also I like to have this at my .vimrc:
" Open file, but keep focus in Explorer
autocmd filetype netrw nmap <c-a> <cr>:wincmd W<cr>
Actually,
let g:netrw_browse_split = 4
let g:netrw_altv = 1
works best for me.
*g:netrw_browse_split* when browsing, <cr> will open the file by:
=0: re-using the same window
=1: horizontally splitting the window first
=2: vertically splitting the window first
=3: open file in new tab
=4: act like "P" (ie. open previous window)
Note that |g:netrw_preview| may be used
to get vertical splitting instead of
horizontal splitting.
I think the best behavior is described by option 4. By pressing enter, file is opened on the other split, avoiding an overpopulation of splits.
" Toggle Vexplore with Ctrl-E
function! ToggleVExplorer()
Lexplore
vertical resize 30
endfunction
map <silent> <C-E> :call ToggleVExplorer()<CR>
Simplify
As a similar and simpler aprroach to Nick's, you could make it toggleable (and very NERDTree-like) with F9 with this in your .vimrc:
" ---------------------------------------------------------------
" File Explorer start
let g:netrw_banner = 0
let g:netrw_liststyle = 3
let g:netrw_browse_split = 4
let g:netrw_altv = 1
let g:netrw_winsize = 15
" Toggle Vexplore with F9
map <silent> <F9> :Lexplore<CR>
" File Explorer end
" ---------------------------------------------------------------

Vim keep window position when switching buffers

A problem I've been having with Vim in general is that when I switch buffers in a window (either :[n]b or MiniBufExpl) the cursor position stays the same, but the window always positions itself so the row the cursor on is in the middle.
This is really annoying me since I visually remember where the top/bottom parts of the window are, not where they would be should the cursor be positioned in the middle of the window.
Is there a setting I can change to preserve a window's position over a buffer?
It's interesting to note that it didn't bother me until I've read your question, lol.
Try this:
if v:version >= 700
au BufLeave * let b:winview = winsaveview()
au BufEnter * if(exists('b:winview')) | call winrestview(b:winview) | endif
endif
That script posted by #dnets always sets the cursor at the top of the screen for me, albeit at the same position in the file.
I changed it to this (copied from http://vim.wikia.com/wiki/Avoid_scrolling_when_switch_buffers)
" Save current view settings on a per-window, per-buffer basis.
function! AutoSaveWinView()
if !exists("w:SavedBufView")
let w:SavedBufView = {}
endif
let w:SavedBufView[bufnr("%")] = winsaveview()
endfunction
" Restore current view settings.
function! AutoRestoreWinView()
let buf = bufnr("%")
if exists("w:SavedBufView") && has_key(w:SavedBufView, buf)
let v = winsaveview()
let atStartOfFile = v.lnum == 1 && v.col == 0
if atStartOfFile && !&diff
call winrestview(w:SavedBufView[buf])
endif
unlet w:SavedBufView[buf]
endif
endfunction
" When switching buffers, preserve window view.
if v:version >= 700
autocmd BufLeave * call AutoSaveWinView()
autocmd BufEnter * call AutoRestoreWinView()
endif
And it now works as I want, screen and cursor position saved.

Resources