I sometimes want Vim to read tab-formatted files where the most reasonable formatting implies a non-uniform tab width. In other words, I want a tab stop at positions:
5, 30, 50, 60, 70, 80
How can I do this in Vim?
If you don't actually need to change the tabstops and can get away with just inserting the correct number of spaces, I would suggest you script it. Here's a quick and dirty version that might do what you want:
let s:tabstops = [0, 5, 30, 50, 60, 70, 80]
fun! Find_next(pos)
if a:pos > min(s:tabstops) && a:pos < max(s:tabstops)
let my_count = 0
while my_count < len(s:tabstops) - 1
if a:pos > get(s:tabstops, my_count) && a:pos < get(s:tabstops, my_count+1)
return get(s:tabstops, my_count+1)
endif
let my_count = my_count + 1
endwhile
return -1
endif
return -1
endfun
fun! Tabbing()
let pos = col('.')
let next_stop = Find_next(pos)
let the_command = "normal i"
let my_count = 0
while my_count < next_stop - pos
let the_command = the_command . " "
let my_count = my_count + 1
endwhile
let the_command = the_command . ""
execute the_command
endfun
imap <TAB> j<ESC>:call Tabbing()<CR>lxi
Currently no. Not with any official builds.
However, if you're willing to invest a little effort on your side, I remember there was a patch for something like that. Check out vim's patches page.
Related
I have a list of symbols, and want to use the Vim8 timers API to loop over the symbols list every 80ms and return that symbol. I worked out something like this:
let s:frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
let s:numTestimonials = len(s:frames)
let s:start = 0
function! PrintValues()
return s:frames[s:start]
let s:start = (s:start) + 1 % s:numTestimonials
endfunction
let timer = timer_start(80, 'PrintValues', {'repeat': -1})
But as soon as it reaches the last symbol in the list, it will throw an error, E684: list index out of range: 10. Any ideas how to accomplish what I want?
You need to have the 1 inside parentheses before the mod
let s:start = (s:start + 1) % s:numTestimonials
1 % s:numTestimonials is always 1 and is evaluated before adding s:start
Some little changes made to your code (Tested on vim 7.4):
let timer = timer_start(500, 'PrintValues', {'repeat': -1})
let s:frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
let s:numTestimonials = len(s:frames)
let s:start = 0
function! PrintValues(timer)
execute "normal! i".s:frames[s:start]
let s:start = (s:start + 1) % s:numTestimonials
endfunction
How do I get the width of 3 (marked with green color in the image) in vim script?
If there is no signs column, and there are no other "special columns", I can get it with
winwidth(0) - (max([len(line('$')), &numberwidth-1]) + 1)
I think, you should be able to get that width using:
:set virtualedit=all
:norm! g$
:echo virtcol('.')
Alternatively, you could check, whether a signcolumn is present (e.g. using redir)
:redir =>a |exe "sil sign place buffer=".bufnr('')|redir end
:let signlist=split(a, '\n')
:let width=winwidth(0) - ((&number||&relativenumber) ? &numberwidth : 0) - &foldcolumn - (len(signlist) > 1 ? 2 : 0)
My ingo-library plugin has a ingo#window#dimensions#NetWindowWidth() function for that.
Answering because I can't comment yet:
Christian's answer gives the wrong result in the case that the actual number of lines in the file exceeds &numberwidth (because &numberwidth is just a minimum, as kshenoy pointed out). The fix is pretty simple, though, just take the max() of &numberwidth and the number of digits in the last line in the buffer (plus one to account for the padding vim adds):
redir =>a | exe "silent sign place buffer=".bufnr('') | redir end
let signlist = split(a, '\n')
let lineno_cols = max([&numberwidth, strlen(line('$')) + 1])
return winwidth(0)
\ - &foldcolumn
\ - ((&number || &relativenumber) ? lineno_cols : 0)
\ - (len(signlist) > 2 ? 2 : 0)
Kale's answer corrected one corner case where the number of lines is exceeding what &numberwidth can display. Here I fix another corner case where the signcolumn option is not set to auto
function! BufWidth()
let width = winwidth(0)
let numberwidth = max([&numberwidth, strlen(line('$'))+1])
let numwidth = (&number || &relativenumber)? numberwidth : 0
let foldwidth = &foldcolumn
if &signcolumn == 'yes'
let signwidth = 2
elseif &signcolumn == 'auto'
let signs = execute(printf('sign place buffer=%d', bufnr('')))
let signs = split(signs, "\n")
let signwidth = len(signs)>2? 2: 0
else
let signwidth = 0
endif
return width - numwidth - foldwidth - signwidth
endfunction
None of the above answers take into account the following points -
Plugins use sign-groups (if available), so simply running exe "silent sign place buffer=".bufnr('') does not show the sign's placed in the plugin's group
Neovim has support for variable signcolumn width
So this is the answer that finally set the ball rolling for me. Of course it is influenced by all of the above answers -
function! BufferWidth()
let width = winwidth(0)
let numberwidth = max([&numberwidth, strlen(line('$')) + 1])
let numwidth = (&number || &relativenumber) ? numberwidth : 0
let foldwidth = &foldcolumn
if &signcolumn == 'yes'
let signwidth = 2
elseif &signcolumn =~ 'yes'
let signwidth = &signcolumn
let signwidth = split(signwidth, ':')[1]
let signwidth *= 2 " each signcolumn is 2-char wide
elseif &signcolumn == 'auto'
let supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614')
let signlist = execute(printf('sign place ' . (supports_sign_groups ? 'group=* ' : '') . 'buffer=%d', bufnr('')))
let signlist = split(signlist, "\n")
let signwidth = len(signlist) > 2 ? 2 : 0
elseif &signcolumn =~ 'auto'
let signwidth = 0
if len(sign_getplaced(bufnr(),{'group':'*'})[0].signs)
let signwidth = 0
for l:sign in sign_getplaced(bufnr(),{'group':'*'})[0].signs
let lnum = l:sign.lnum
let signs = len(sign_getplaced(bufnr(),{'group':'*', 'lnum':lnum})[0].signs)
let signwidth = (signs > signwidth ? signs : signwidth)
endfor
endif
let signwidth *= 2 " each signcolumn is 2-char wide
else
let signwidth = 0
endif
return width - numwidth - foldwidth - signwidth
endfunction
Starting from Vim 8.2.3627, getwininfo()'s output has a textoff containing the
number of columns occupied by any 'foldcolumn', 'signcolumn' and line number in front of the text
therefore subtracting that to the width entry, e.g. computing
getwininfo(win_getid()).width - getwininfo(win_getid()).textoff
should give the desired result.
Before textoff was available, it seems to me that the followinig computation does cut it:
let textoff = max([&numberwidth, (&number ? len(line('$')) + 1 : (&relativenumber ? winfo.height + 1 : 0))])
\ + &foldcolumn
\ + (empty(sign_getplaced(bufname(), {'group': '*'})[0].signs) ? 0 : 2)
I made use of both solutions in this plugin of mine for showing soft-wrapped lines.
I have following nagios config snippet. I could not come up with tabularize command to tabularize parameter values which are only separated by tabs/spaces.
define service{
use local-service,srv-pnp
name http
service_description http
check_command check_http!-w 5 -c 10 -H www.kpoint.com -u /
register 0
}
EDIT:
The expected output is
define service{
use local-service,srv-pnp
name http
service_description http
check_command check_http!-w 5 -c 10 -H www.kpoint.com -u /
register 0
}
Is there any alternative to tabularize?
Following command does the trick.
:Tabularize /^\s*\w*
The link, though does not answer the exact question, has the answer.
Very dirty and quickly way to do it:
function! GetOffsetSpacesAndReplace()
let [line1,col1] = getpos("'<")[1:2]
let [line2,col2] = getpos("'>")[1:2]
let max_len = 0
for i in range(line1, line2)
let my_line = getline(i)
let matching_str = matchstr(my_line, '^\s*\w*\s*')
if len(matching_str) > max_len
let max_len = len(matching_str)
endif
endfor
for i in range(line1, line2)
let my_line = getline(i)
let matching_str = matchstr(my_line, '^\s*\w*\s*')
let col_pos = len(matching_str)
call setpos('.', [0, i, col_pos, 0])
let #s=' '
if max_len - col_pos > 0
execute 'normal! "s'.(max_len - col_pos).'p'
endif
endfor
return 1
endfunction
vmap <silent> <buffer> <F3> :call GetOffsetSpacesAndReplace()<CR>
This function allow you to select visually the inner block and then run the function to indent as you wished.
This is dirty mostly because it answer only your requirements.
In order to use a more flexible tool to do indentation, you should probably check the excellent plugin vim-easy-align.
I'm using it every day without any problems.
I would like to have the following feature in VIM (GVIM in particular). I think Sublime Text has something like that:
In the "normal" case the tab name should be just the file's name, but...
If there are two files opened with the same name but in different directories, I would like to see a tab name parent folder name + file name.
Example:
When there are tabs for the following files:
c:\my\dir\with\files\justAfile.txt
c:\my\dir\with\files\myfile.txt
c:\my\dir\with\backup\myfile.txt
Tab names would be then:
justAfile.txt | files\myfile.txt | backup\myfile.txt
Is this doable with some clever configuration?
In GVIM, you can customize the tab labels with the 'guitablabel' option.
In terminal Vim; there's no 'guitablabel' equivalent; one has to render the entire 'tabline'. Fortunately, the Vim help has an example which delegates the label rendering to a separate function, so re-using your custom function is pretty easy.
The help pages for the mentioned options link to examples; you probably have to use fnamemodify() to canonicalize all buffers' paths to full absolute paths, find the common base directory, and then strip that off the paths.
On the other hand, if it's okay for you to :cd to the base directory, you'll get that kind of tab label pretty much out-of-the-box.
Assuming the following files:
z.txt
a/b/c/d.txt
a/b/f/d.txt
My current setup will make the tabline look like so (I reversed-engineered the behavior from Sublime Text 2):
z.txt | d.txt - c | d.txt - f
My code has a lot of extras like treating Nerdtree/FZF tabs specially, and naming tabs according to the left-most buffer when there are splits. You can remove these extras yourself if you don't want them, or change anything you don't like. I also assumed Unix only, and terminal VIM only (GVIM would need minor tweaking I guess).
I am providing the code below without guarantee, as a starting point for you to customize according to your needs.
set tabline=%!GetTabLine()
function! GetTabLine()
let tabs = BuildTabs()
let line = ''
for i in range(len(tabs))
let line .= (i+1 == tabpagenr()) ? '%#TabLineSel#' : '%#TabLine#'
let line .= '%' . (i + 1) . 'T'
let line .= ' ' . tabs[i].uniq_name . ' '
endfor
let line .= '%#TabLineFill#%T'
return line
endfunction
function! BuildTabs()
let tabs = []
for i in range(tabpagenr('$'))
let tabnum = i + 1
let buflist = tabpagebuflist(tabnum)
let file_path = ''
let tab_name = bufname(buflist[0])
if tab_name =~ 'NERD_tree' && len(buflist) > 1
let tab_name = bufname(buflist[1])
end
let is_custom_name = 0
if tab_name == ''
let tab_name = '[No Name]'
let is_custom_name = 1
elseif tab_name =~ 'fzf'
let tab_name = 'FZF'
let is_custom_name = 1
else
let file_path = fnamemodify(tab_name, ':p')
let tab_name = fnamemodify(tab_name, ':p:t')
end
let tab = {
\ 'name': tab_name,
\ 'uniq_name': tab_name,
\ 'file_path': file_path,
\ 'is_custom_name': is_custom_name
\ }
call add(tabs, tab)
endfor
call CalculateTabUniqueNames(tabs)
return tabs
endfunction
function! CalculateTabUniqueNames(tabs)
for tab in a:tabs
if tab.is_custom_name | continue | endif
let tab_common_path = ''
for other_tab in a:tabs
if tab.name != other_tab.name || tab.file_path == other_tab.file_path
\ || other_tab.is_custom_name
continue
endif
let common_path = GetCommonPath(tab.file_path, other_tab.file_path)
if tab_common_path == '' || len(common_path) < len(tab_common_path)
let tab_common_path = common_path
endif
endfor
if tab_common_path == '' | continue | endif
let common_path_has_immediate_child = 0
for other_tab in a:tabs
if tab.name == other_tab.name && !other_tab.is_custom_name
\ && tab_common_path == fnamemodify(other_tab.file_path, ':h')
let common_path_has_immediate_child = 1
break
endif
endfor
if common_path_has_immediate_child
let tab_common_path = fnamemodify(common_path, ':h')
endif
let path = tab.file_path[len(tab_common_path)+1:-1]
let path = fnamemodify(path, ':~:.:h')
let dirs = split(path, '/', 1)
if len(dirs) >= 5
let path = dirs[0] . '/.../' . dirs[-1]
endif
let tab.uniq_name = tab.name . ' - ' . path
endfor
endfunction
function! GetCommonPath(path1, path2)
let dirs1 = split(a:path1, '/', 1)
let dirs2 = split(a:path2, '/', 1)
let i_different = 0
for i in range(len(dirs1))
if get(dirs1, i) != get(dirs2, i)
let i_different = i
break
endif
endfor
return join(dirs1[0:i_different-1], '/')
endfunction
As Ingo suggests you can use guitablabel. On my installation its only configured to show the file name (:echo &guitablabel reports %M%t). To set this to show the relative path do :set guitablabel=%M%f. Like Ingo says, use :cd DIRECTORY to set the home directory, and :pwd to see where its currently set.
See :help statusline for (many) more formatting options.
Here's my solution that makes the tabname the directory---which is usually a good proxy for the project that tab is meant to represent. This solution can be modified to show the filename if there is only one buffer (modification shown below).
This solution draws a tiny bit from Jerome's. I'm not doing anything as complex as they are, so mine is 5x shorter.
Also, this solution places the tab number alongside the name, making it easy to bounce around, meaning the tabs will look like this: 1:log 2:doc 3:vimfiles and 2gt will move to the second tab.
set tabline=%!TabLine()
function! TabLine()
let line = ''
for i in range(tabpagenr('$'))
let line .= (i+1 == tabpagenr()) ? '%#TabLineSel#' : '%#TabLine#'
let line .= '%' . (i + 1) . 'T'
let line .= TabLabel(i + 1) . ' '
endfor
let line .= '%#TabLineFill#%T'
return line
endfunction
function! TabLabel(n)
" Return list of buffer numbers for each window pane open in tab.
let panelist = tabpagebuflist(a:n)
" See :help setting-tabline then search MyTabLabel if you want to
" use use the active window. I use the topmost pane, which let's
" me rename the tab just by putting a window from a different
" directory in the first position.
let filepath = bufname(panelist[0])
let dirname = fnamemodify(filepath, ':p:h:t')
return a:n . ':' . dirname
endfunction
The modification to show the filename if only one buffer is visible:
function! TabLabel(n)
" Return list of buffer numbers for each window pane open in tab.
let panelist = tabpagebuflist(a:n)
" See :help setting-tabline then search MyTabLabel if you want to
" use use the active window. I use the topmost pane, which let's
" me rename the tab just by putting a window from a different
" directory in the first position.
let filepath = bufname(panelist[0])
let dirname = fnamemodify(filepath, ':p:h:t')
let filename = fnamemodify(filepath, ':t')
let tabname = len(panelist) > 1 ? dirname : filename
return a:n . ':' . tabname
endfunction
It would be very nice to have an option that would show all the column numbers of the current line or maybe of all the buffer, so I could know where exactly to navigate. Is there such an option or do i have to program it myself (nooo XD)?
:h 'statusline'
It is as easy as defining exactly what you what to see printed. e.g.
" RulerStr() comes from http://www.vanhemert.co.uk/vim/vimacros/ruler2.vim
function! RulerStr()
let columns = &columns
let inc = 0
let str = ""
while (inc < columns)
let inc10 = inc / 10 + 1
let buffer = "."
if (inc10 > 9)
let buffer = ""
endif
let str .= "....+..." . buffer . inc10
let inc += 10
endwhile
let str = strpart(str, 0, columns)
return str
endfunction
let s:saved_stl = {}
function! s:ToggleRuler()
let buf = bufnr('%')
if has_key(s:saved_stl, buf)
let &l:stl = s:saved_stl[buf]
unlet s:saved_stl[buf]
else
let s:saved_stl[buf] = &l:stl
setlocal stl=%{RulerStr()}
endif
endfunction
nnoremap <silent> µ :call <sid>ToggleRuler()<cr>
You can use "set ruler". It will show the line number and column position at the bottom.