How to enumerate tabs in vim? - vim

Vim is very productive editor and I enjoy using it everyday, but I've found that moving between tabs takes more time than it should.
When I want to switch to another tab I often repeat gt or gT multiple times. Vim provides a better way to reach required tab - n + gt, where n is tab number. But to use it you should count tab number first. It quickly become boring if you open a dozen of tabs.
I think it would be nice to enumerate tabs. A single number on each tab in front of file name, something like this:
1 Readme | 2 main.c | 3 main.h | 4 process.h
I hope it is possible to configure vim to do this by editing config or using some plugin.
Is there a way to achieve it?

You can use the tabline option for setting the label of the tabs in console mode of vim.
See the help at :h setting-tabline which also shows a very basic minimal example, which you can tweak to your need, e.g. for what you want, I would use something like this:
fu! MyTabLabel(n)
let buflist = tabpagebuflist(a:n)
let winnr = tabpagewinnr(a:n)
let string = fnamemodify(bufname(buflist[winnr - 1]), ':t')
return empty(string) ? '[unnamed]' : string
endfu
fu! MyTabLine()
let s = ''
for i in range(tabpagenr('$'))
" select the highlighting
if i + 1 == tabpagenr()
let s .= '%#TabLineSel#'
else
let s .= '%#TabLine#'
endif
" set the tab page number (for mouse clicks)
"let s .= '%' . (i + 1) . 'T'
" display tabnumber (for use with <count>gt, etc)
let s .= ' '. (i+1) . ' '
" the label is made by MyTabLabel()
let s .= ' %{MyTabLabel(' . (i + 1) . ')} '
if i+1 < tabpagenr('$')
let s .= ' |'
endif
endfor
return s
endfu
set tabline=%!MyTabLine()

If you are using gvim:
set guitablabel=(%N)\ %t\ %M
Type :help tabline and :help guitablabel to read more.
There is a function MyTabLine() in the doc.

Related

How to write `tabline` function in vim?

I would like tabs in Vim (not the gVim) look as follows:
Explanation:
Sequence number of tab (1, 2, 3, 4 etc)
Name of file (no path, no shortened path)
If there are more than one file opened, list them in a tab.
If there are duplicate tabs (hence the same file opened in several tabs) they should be highlighted.
If buffer is modified add + at the end of filename.
Could anybody help? I want to have something like this within my .vimrc:
set tabline=%!MyTabLine()
function! MyTabLine()
...
endfunction
I've already wrote my desired tabline function. The behaviour is almost the same, except:
the + sign appears after tab number if any of buffer inside the tab is modified
tab contains only modifiable buffers (it don't clog the line with buffers of netrw file browser, help and read-only ones), but you can change this, just uncomment the desired lines
Here is the code:
set tabline=%!MyTabLine() " custom tab pages line
function! MyTabLine()
let s = ''
" loop through each tab page
for i in range(tabpagenr('$'))
if i + 1 == tabpagenr()
let s .= '%#TabLineSel#'
else
let s .= '%#TabLine#'
endif
if i + 1 == tabpagenr()
let s .= '%#TabLineSel#' " WildMenu
else
let s .= '%#Title#'
endif
" set the tab page number (for mouse clicks)
let s .= '%' . (i + 1) . 'T '
" set page number string
let s .= i + 1 . ''
" get buffer names and statuses
let n = '' " temp str for buf names
let m = 0 " &modified counter
let buflist = tabpagebuflist(i + 1)
" loop through each buffer in a tab
for b in buflist
if getbufvar(b, "&buftype") == 'help'
" let n .= '[H]' . fnamemodify(bufname(b), ':t:s/.txt$//')
elseif getbufvar(b, "&buftype") == 'quickfix'
" let n .= '[Q]'
elseif getbufvar(b, "&modifiable")
let n .= fnamemodify(bufname(b), ':t') . ', ' " pathshorten(bufname(b))
endif
if getbufvar(b, "&modified")
let m += 1
endif
endfor
" let n .= fnamemodify(bufname(buflist[tabpagewinnr(i + 1) - 1]), ':t')
let n = substitute(n, ', $', '', '')
" add modified label
if m > 0
let s .= '+'
" let s .= '[' . m . '+]'
endif
if i + 1 == tabpagenr()
let s .= ' %#TabLineSel#'
else
let s .= ' %#TabLine#'
endif
" add buffer names
if n == ''
let s.= '[New]'
else
let s .= n
endif
" switch to no underlining and add final space
let s .= ' '
endfor
let s .= '%#TabLineFill#%T'
" right-aligned close button
" if tabpagenr('$') > 1
" let s .= '%=%#TabLineFill#%999Xclose'
" endif
return s
endfunction
:help setting-tabline contains a lengthy description, including an example function that sort-of emulates Vim's default tabline. You can use this as a starting point. See :help functions for a complete list of available functions.
Learn how to look up commands and navigate the built-in :help; it is comprehensive and offers many tips. You won't learn Vim as fast as other editors, but if you commit to continuous learning, it'll prove a very powerful and efficient editor.

Show expanded relative path instead of single letters in tabs

When I have multiple tabs open in vim, the path of the file is shown in the tab:
Notice that only the first character of the directories is used:
p/c/game.js
instead of
public/controllers/game.js
Is it possible to show the fully expanded path in the tab, using the full directory names instead of just their first letters?
This should work:
function! MyTabLine()
let s = ''
for i in range(tabpagenr('$'))
" select the highlighting
if i + 1 == tabpagenr()
let s .= '%#TabLineSel#'
else
let s .= '%#TabLine#'
endif
" set the tab page number (for mouse clicks)
let s .= '%' . (i + 1) . 'T'
" the label is made by MyTabLabel()
let s .= ' %{MyTabLabel(' . (i + 1) . ')} '
endfor
" after the last tab fill with TabLineFill and reset tab page nr
let s .= '%#TabLineFill#%T'
" right-align the label to close the current tab page
if tabpagenr('$') > 1
let s .= '%=%#TabLine#%999Xclose'
endif
return s
endfunction
function! MyTabLabel(n)
let buflist = tabpagebuflist(a:n)
let winnr = tabpagewinnr(a:n)
return fnamemodify(bufname(buflist[winnr - 1]), ':p')
endfunction
set tabline=%!MyTabLine()
This code is almost 100% from vim help (see :help setting-tabline). The only tweak is the use of fnamemodify in MyTabLabel to get the full path.

why my tabpagenr always returns 1

I have this loop in my .vimrc to display the tab title as "1: File1.txt" or "2: File2.tx", etc, but both tabpagenr('$') and tabpagenr() always returns 1 no matter how many tabs I open. What am I doing wrong?
for t in range(tabpagenr('$'))
if (t + 1) == tabpagenr()
let &titlestring = t + 1 . ': '
endif
endfor
let &titlestring .= expand("%:M")
if &term == "screen" || &term == "xterm"
set title
endif
It looks like there are some bits missing from your sample code: how do you expect to change your tab labels with only those few lines?
Anyway, without an argument, tabpagenr() returns the number of the current tab. Since you are always in the same tab during your loop, that function always returns the same number.
:help setting-tabline has an example, did you read it?
You didn't tell us on which events your code is executed. If you plainly put this in your ~/.vimrc, it will only be executed once during Vim startup. You need to use :autocmd to update the 'titlestring', at least on every tab page change (i.e. the TabEnter event), or better use an expression in the option to have it continuously evaluated:
:set titlestring=%{tabpagenr()}

How do I search the open buffers in Vim?

I'd like to search for text in all files currently open in vim and display all results in a single place. There are two problems, I guess:
I can't pass the list of open files to :grep/:vim, especially the names of files that aren't on the disk;
The result of :grep -C 1 text doesn't look good in the quickfix window.
Here is a nice example of multiple file search in Sublime Text 2:
Any ideas?
Or
:bufdo vimgrepadd threading % | copen
The quickfix window may not look good for you but it's a hell of a lot more functional than ST2's "results panel" if only because you can keep it open and visible while jumping to locations and interact with it if it's not there.
ack and Ack.vim handle this problem beautifully. You can also use :help :vimgrep. For example:
:bufdo AckAdd -n threading
will create a nice quickfix window that lets you hop to the cursor position.
Like the answer of Waz, I have written custom commands for that, published in my GrepCommands plugin. It allows to search over buffers (:BufGrep), visible windows (:WinGrep), tabs, and arguments.
(But like all the other answers, it doesn't handle unnamed buffers yet.)
I really liked romainl's answer, but there were a few sticky edges that made it awkward to use in practice.
The following in your .vimrc file introduces a user command Gall (Grep all) that addresses the issues that I found irksome.
funct! GallFunction(re)
cexpr []
execute 'silent! noautocmd bufdo vimgrepadd /' . a:re . '/j %'
cw
endfunct
command! -nargs=1 Gall call GallFunction(<q-args>)
This will allow case-sensitive searches like this:
:Gall Error\C
and case-insensitive:
:Gall error
and with spaces:
:Gall fn run
Pros
It will only open the Quickfix window, nothing else.
It will clear the Quickfix window first before vimgrepadd-ing results from each buffer.
The Quickfix window will contain the locations of all matches throughout the open buffers, not just the last visited.
Use :Gall repeatedly without any special housekeeping between calls.
Doesn't wait on errors and displays results immediately.
Doesn't allow any autocmd to run, speeding up the overall operation.
Ambivalent features
Doesn't preemptively jump to any occurrence in the list. :cn gets second result or CTRL-w b <enter> to get to the first result directly.
Cons
If there's only one result, you'll have to navigate to it manually with CTRL-w b <enter>.
To navigate to a result in any buffer quickly:
:[count]cn
or
:[count]cp
E.g. :6cn to skip 6 results down the list, and navigate to the correct buffer and line in the "main" window.
Obviously, window navigation is essential:
Ctrl-w w "next window (you'll need this at a bare minimum)
Ctrl-w t Ctrl-w o "go to the top window then close everything else
Ctrl-w c "close the current window, i.e. usually the Quickfix window
:ccl "close Quickfix window
If you close the Quickfix window, then need the results again, just use:
:cw
or
:copen
to get it back.
I made this function a long time ago, and I'm guessing it's probably not the cleanest of solutions, but it has been useful for me:
" Looks for a pattern in the open buffers.
" If list == 'c' then put results in the quickfix list.
" If list == 'l' then put results in the location list.
function! GrepBuffers(pattern, list)
let str = ''
if (a:list == 'l')
let str = 'l'
endif
let str = str . 'vimgrep /' . a:pattern . '/'
for i in range(1, bufnr('$'))
let str = str . ' ' . fnameescape(bufname(i))
endfor
execute str
execute a:list . 'w'
endfunction
" :GrepBuffers('pattern') puts results into the quickfix list
command! -nargs=1 GrepBuffers call GrepBuffers(<args>, 'c')
" :GrepBuffersL('pattern') puts results into the location list
command! -nargs=1 GrepBuffersL call GrepBuffers(<args>, 'l')
An improved (on steroids) version of Waz's answer, with better buffer searching and special case handling, can be found below (The moderators wouldn't let me update Waz's answer anymore :D).
A more fleshed out version with binds for arrow keys to navigate the QuickFix list and F3 to close the QuickFix window can be found here: https://pastebin.com/5AfbY8sm
(When i feel like figuring out how to make a plugin i'll update this answer. I wanted to expedite sharing it for now)
" Looks for a pattern in the buffers.
" Usage :GrepBuffers [pattern] [matchCase] [matchWholeWord] [prefix]
" If pattern is not specified then usage instructions will get printed.
" If matchCase = '1' then exclude matches that do not have the same case. If matchCase = '0' then ignore case.
" If prefix == 'c' then put results in the QuickFix list. If prefix == 'l' then put results in the location list for the current window.
function! s:GrepBuffers(...)
if a:0 > 4
throw "Too many arguments"
endif
if a:0 >= 1
let l:pattern = a:1
else
echo 'Usage :GrepBuffers [pattern] [matchCase] [matchWholeWord] [prefix]'
return
endif
let l:matchCase = 0
if a:0 >= 2
if a:2 !~ '^\d\+$' || a:2 > 1 || a:2 < 0
throw "ArgumentException: matchCase value '" . a:2 . "' is not in the bounds [0,1]."
endif
let l:matchCase = a:2
endif
let l:matchWholeWord = 0
if a:0 >= 3
if a:3 !~ '^\d\+$' || a:3 > 1 || a:3 < 0
throw "ArgumentException: matchWholeWord value '" . a:3 . "' is not in the bounds [0,1]."
endif
let l:matchWholeWord = a:3
endif
let l:prefix = 'c'
if a:0 >= 4
if a:4 != 'c' && a:4 != 'l'
throw "ArgumentException: prefix value '" . a:4 . "' is not 'c' or 'l'."
endif
let l:prefix = a:4
endif
let ignorecase = &ignorecase
let &ignorecase = l:matchCase == 0
try
if l:prefix == 'c'
let l:vimgrep = 'vimgrep'
elseif l:prefix == 'l'
let l:vimgrep = 'lvimgrep'
endif
if l:matchWholeWord
let l:pattern = '\<' . l:pattern . '\>'
endif
let str = 'silent ' . l:vimgrep . ' /' . l:pattern . '/'
for buf in getbufinfo()
if buflisted(buf.bufnr) " Skips unlisted buffers because they are not used for normal editing
if !bufexists(buf.bufnr)
throw 'Buffer does not exist: "' . buf.bufnr . '"'
elseif empty(bufname(buf.bufnr)) && getbufvar(buf.bufnr, '&buftype') != 'quickfix'
if len(getbufline(buf.bufnr, '2')) != 0 || strlen(getbufline(buf.bufnr, '1')[0]) != 0
echohl warningmsg | echomsg 'Skipping unnamed buffer: [' . buf.bufnr . ']' | echohl normal
endif
else
let str = str . ' ' . fnameescape(bufname(buf.bufnr))
endif
endif
endfor
try
execute str
catch /^Vim\%((\a\+)\)\=:E\%(683\|480\):/ "E683: File name missing or invalid pattern --- E480: No match:
" How do you want to handle this exception?
echoerr v:exception
return
endtry
execute l:prefix . 'window'
"catch /.*/
finally
let &ignorecase = ignorecase
endtry
endfunction

Setting filenames in Tab

Can someone tell me how to display filename in the tabs when I open several files using Vim?
Having a name on the tab would make changing to different files much easier.
I think your question was "how do you display only the filename on your tab label". If that was the question, my answer is:
In a gui vim, you would use:
:set guitablabel=%t
However if's in vim, it gets a little more complicate. You have to overwrite the whole line, using :tabline. I modified the example provided in the :help setting-tabline, to add the behaviour you wanted. You would need to add the following code to your vimrc:
set tabline=%!MyTabLine()
function MyTabLine()
let s = ''
for i in range(tabpagenr('$'))
" select the highlighting
if i + 1 == tabpagenr()
let s .= '%#TabLineSel#'
else
let s .= '%#TabLine#'
endif
" set the tab page number (for mouse clicks)
let s .= '%' . (i + 1) . 'T'
" the label is made by MyTabLabel()
let s .= ' %{MyTabLabel(' . (i + 1) . ')} '
endfor
" after the last tab fill with TabLineFill and reset tab page nr
let s .= '%#TabLineFill#%T'
" right-align the label to close the current tab page
if tabpagenr('$') > 1
let s .= '%=%#TabLine#%999Xclose'
endif
return s
endfunction
function MyTabLabel(n)
let buflist = tabpagebuflist(a:n)
let winnr = tabpagewinnr(a:n)
let label = bufname(buflist[winnr - 1])
return fnamemodify(label, ":t")
endfunction
I hope this helps!

Resources