Tip 102 Printable Monobook Previous Next created 2001 · complexity basic · author benoit cerrina · version 6.0

I'm used to complete words with the Tab key, however when editing source I can't just map that to Vim keyword completion because I sometime need to insert real tabs, since it mostly happen when at the beginning of the line or after a ; and before a one line comma (java, c++ or perl anyone)

I've come to find the following really useful.

This is how you can map the Tab key in insert mode while still being able to use it when at the start of a line or when the preceding char is not a keyword character.

in a script file in a plugin directory or in your .vimrc file:

first define a function which returns a Tab or the appropriate completion, depending on the context:

function! Smart_TabComplete() let line = getline('.') " current line let substr = strpart(line, -1, col('.')+1) " from the start of the current " line to one character right " of the cursor let substr = matchstr(substr, "[^ \t]*$") " word till cursor if (strlen(substr)==0) " nothing to match on empty string return "\<tab>" endif let has_period = match(substr, '\.') != -1 " position of period, if any let has_slash = match(substr, '\/') != -1 " position of slash, if any if (!has_period && !has_slash) return "\<C-X>\<C-P>" " existing text matching elseif ( has_slash ) return "\<C-X>\<C-F>" " file matching else return "\<C-X>\<C-O>" " plugin matching endif endfunction

Then define the appropriate mappings:

inoremap <tab> <c-r>=Smart_TabComplete()<CR>

The trick here is the use of the <c-r>= in insert mode to be able to call your function without leaving insert mode. See :help i_CTRL-R.

TO DO



Clean up! This is a nice tip...

Use an <expr> mapping instead of <C-R>=

See also SuperTab. It is a complete system, and the completion remembers the completion mode!

One behavior of the tab completion script above may be undesirable for some users (a bug, or a use case I don't understand?): it checks up to the character right of the cursor, so that it may "complete" a blank string.

For example, placing the cursor to the left of "foo" in the following line:

" foo

... and pressing Tab would attempt to "complete" a word, instead of inserting a tab. If this behavior is undesirable, simply remove the "+1" from "col('.')+1" in the script above.

for those of us that would like to use dictionary files for filetypes ( in order to use function prototypes et. al.) you can modify the 'iskeyword' option temporarily to complete function names for example : given a dictionary with abs(VALUE)

I could type :

ab<TAB>

and it would expand to abs(VALUE)

function! InsertTabWrapper(direction) let oldisk=&isk "save the iskeyword options set isk+=(,),, "add '(' ')' and ',' character let col = col('.') - 1 if !col || getline('.')[col - 1] !~ '\k' return "\<tab>" elseif "backward" == a:direction return "\<c-n>" else return "\<c-p>" endif set &isk=oldisk "restore the iskeyword options endfunction

fun! Iskcompletion() let oldisk=&isk set isk+=(,) normal <C-P> set &isk=oldisk endfun

If you want to use keywords in completion, you'll need to make dictionary files for each of the languages whose keywords you want to use.

e.g.

grep keyword /usr/share/vim/vim61/syntax/sh.vim | grep -v nextgroup | awk '{ $1=""; $2=""; $3=""; print}' | perl -pe 's/\s+/

/g' | grep -v contained | grep -v '^$' | sort | uniq > /home/user/.vim/dict/sh.dict

the above command will probably need tweaking depending on the syntax file.

and in your .vimrc:

" tell complete to look in the dictionary set complete-=k complete+=k " load the dictionary according to syntax :au BufReadPost * if exists("b:current_syntax") :au BufReadPost * let &dictionary = substitute("~/.vim/dict/FT.dict", "FT", b:current_syntax, "") :au BufReadPost * endif

I prefer to be able to toggle tab completion manually. The following function works for me:

" toggle tab completion function! TabCompletion() if mapcheck("\<tab>", "i") != "" :iunmap <tab> :iunmap <s-tab> :iunmap <c-tab> echo "tab completion off" else :imap <tab> <c-n> :imap <s-tab> <c-p> :imap <c-tab> <c-x><c-l> echo "tab completion on" endif endfunction map <Leader>tc :call TabCompletion()<CR>

Have to chime in... here's my silly attempt at making the Tab key more intelligent. The function below (derived work of course) allows one to get the original effect of <Tab> if the previous character is a space. I.e., you can type:

foo<Space><Tab>

and it'll end up as foo<Tab> with the space deleted. This is *not* the same as <C-v><Tab>, which would always insert a real Tab character rather than honoring 'softtabstop'.

" Intelligent tab completion inoremap <silent> <Tab> <C-r>=<SID>InsertTabWrapper(1)<CR> inoremap <silent> <S-Tab> <C-r>=<SID>InsertTabWrapper(-1)<CR> function! <SID>InsertTabWrapper(direction) let idx = col('.') - 1 let str = getline('.') if a:direction > 0 && idx >= 2 && str[idx - 1] == ' ' \&& str[idx - 2] =~? '[a-z]' if &softtabstop && idx % &softtabstop == 0 return "\<BS>\<Tab>\<Tab>" else return "\<BS>\<Tab>" endif elseif idx == 0 || str[idx - 1] !~? '[a-z]' return "\<Tab>" elseif a:direction > 0 return "\<C-p>" else return "\<C-n>" endif endfunction

What about this to dump all the syntax files into properly formatted dict files

for i in /usr/share/vim/syntax/*;do grep keyword $i | grep -v nextgroup | awk '{ $1=""; $2=""; $3=""; print}' | perl -pe 's/\s+/

/g' | grep -v contained | grep -v '^$' | sort | uniq>~/.vim/dict/`basename $i .vim`.dict;done

One small suggestion for daniel elstner's script: replace '[a-z]' with '[^<Space><tab>]'.

My derivation of this wonderful tip: (aimed at C++ // comments)

This as it happens has nothing to do with Tab nor completion.

function! SpecialCR() " Feral This is inspired by how multi-edit does things. let DaLine = getline('.') if match(DaLine, '\c^\s*//$') > -1 " [Feral:213/03@04:59] Just eat the // chars " return "\<BS>\<BS>" " "This method: " // -[Feral:213/03@04:59]---------------------------------------------- " // " 75 is the column we wish to not go beyond. " 2 = ' -' " 20 = my time stamp: [Feral:213/03@04:52] " 1 = '-' let AmountForFiller = 75 - (virtcol('.')+2+20+1) let Filler = "" while strlen(Filler) < AmountForFiller let Filler = Filler.'-' endwhile let DaLine = " -[Feral:".strftime('%j/%y@%H:%M')."]-".Filler."\<CR>" return DaLine elseif match(DaLine, '\c^\s*//\s$') > -1 return "\<BS>\<BS>\<BS>" else return "\<CR>" endif endfunction inoremap <CR> <c-r>=SpecialCR()<CR>

This will make <CR> eat C++ line comments when they are the only think on the line, or more precisely: "//\s" will be get 3 backspaces which should erase it and "//" will get a simple timestamp separator.

Here's the section of my vimrc that has all this wired up (this is on a windows box, I jumped on a unix machine to run the shell script that makes all the dict files). My addition is Ctrl-TAB to begin a keyword search instead of hitting Ctrl-X Ctrl-K, then I can regular TAB through the entries.

Watch out for those dang spaces at the end of the lines!

Thanks to everybody who contributed, this is great.

" Remap TAB to keyword completion function! InsertTabWrapper(direction) let col = col('.') - 1 if !col || getline('.')[col - 1] !~ '\k' return "\<tab>" elseif "backward" == a:direction return "\<c-p>" elseif "forward" == a:direction return "\<c-n>" else return "\<c-x>\<c-k>" endif endfunction inoremap <tab> <c-r>=InsertTabWrapper ("forward")<CR> inoremap <s-tab> <c-r>=InsertTabWrapper ("backward")<CR> inoremap <c-tab> <c-r>=InsertTabWrapper ("startkey")<CR> " toggle tab completion function! ToggleTabCompletion() if mapcheck("\<tab>", "i") != "" :iunmap <tab> :iunmap <s-tab> :iunmap <c-tab> echo "tab completion off" else :imap <tab> <c-n> :imap <s-tab> <c-p> :imap <c-tab> <c-x><c-l> echo "tab completion on" endif endfunction map <Leader>tc :call ToggleTabCompletion()<CR> " tell complete to look in the dictionary set complete-=k complete+=k " load the dictionary according to syntax :au BufReadPost * if exists("b:current_syntax") :au BufReadPost * let &dictionary = substitute("C:\\vim\\vimfiles\\dict\\FT.dict", "FT", b:current_syntax, "") :au BufReadPost * endif

This script doesn't work with multibyte (utf-8 chars)

Try replace condition with

!col || strpart(getline('.'), col-1, col) =~ '\s'

your tab-completion tip is very nice. Although my kde-konsole didn't recognize the shift-tab, <s-tab> combination.

I solved this by adding the following line to /usr/share/apps/konsole/linux.keytab

key Backtab : "\E[Z"

and set the keyboard settings in konsole to "linux console".

In the following bit, it's more useful if you substituted "BufEnter" for "BufReadPost". At least, for those who switch between Vim windows of differing file types (for example I go between perl and cpp and bash quit a bit)

>" load the dictionary according to syntax >:au BufReadPost * if exists("b:current_syntax") >:au BufReadPost * let &dictionary = substitute("C:\\vim\\vimfiles\\dict\\FT.dict", "FT", b:current_syntax, "") >:au BufReadPost * endif

In :help ins-completion you can find following settings.

function! CleverTab() if strpart( getline('.'), 0, col('.')-1 ) =~ '^\s*$' return "\<Tab>" else return "\<C-N>" endfunction inoremap <Tab> <C-R>=CleverTab()<CR>

It works fine, but this never uses "omni-completion" introduced from Vim 7. To use omni-completion if available, then it should be followings.

function! CleverTab() if pumvisible() return "\<C-N>" endif if strpart( getline('.'), 0, col('.')-1 ) =~ '^\s*$' return "\<Tab>" elseif exists('&omnifunc') && &omnifunc != '' return "\<C-X>\<C-O>" else return "\<C-N>" endif endfunction inoremap <Tab> <C-R>=CleverTab()<CR>

Avoid flicker by using an expr mapping [ edit | edit source ]

All of the above techniques using <C-R>=...<CR> cause flicker in my xterm, caused by 3 or 4 full screen redraws. This can be very slow when dealing with a large syntax-rich screen. One of the TODOs recommends using an <expr> instead, and this worked perfectly for me, avoiding all the redraws! I simply changed:

inoremap <silent> <tab> <c-r>=InsertTabWrapper ("forward")<cr>

into:

inoremap <expr> <silent> <tab> InsertTabWrapper("forward")

I can highly recommend this approach. If you agree, perhaps we should update the scripts on this page.