sponsor Vim development Vim logo Vim Book Ad

basic Tip #102: smart mapping for tab completion

 tip karma   Rating 824/248, Viewed by 15495 

created:   August 21, 2001 12:17      complexity:   basic
author:   [email protected]      as of Vim:   6.0

I'm used to complete words with <tab>, 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 usefull
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 a <C-N> depending on the context:

function InsertTabWrapper()
      let col = col('.') - 1
      if !col || getline('.')[col - 1] !~ '\k'
          return "\<tab>"
      else
          return "\<c-p>"
      endif
endfunction

then define the appropriate mapping:
inoremap <tab> <c-r>=InsertTabWrapper()<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.
:help i_CTRL-R
Benoit

 rate this tip  Life Changing Helpful Unfulfilling 

<<Change automatically to the directory the file in the current buffer is in | Move to next/previous line with same indentation >>

Additional Notes

[email protected], August 26, 2001 2:01
WOW!
I've wanted this for year and now i've got it!
Thanks for the tip!
[email protected], September 2, 2001 1:23
This is pretty cool. How would we simulate ^X^N, I wonder...
[email protected], September 5, 2001 17:56
this is wonderful.  however it would be helpful if it could be made to cycle through all completions like a proper ctrl-p would.
[email protected], September 5, 2001 20:33
i'm a giant fool.  nevermind.  (when i copied and pasted the snippet, it left trailing whitespace characters.)

thank you for such a killer tip.
Anonymous, September 6, 2001 10:50
With a minor addition you can also go in either direction:

function! InsertTabWrapper(direction)
    let col = col('.') - 1
    if !col || getline('.')[col - 1] !~ '\k'
        return "\<tab>"
    elseif "backward" == a:direction
        return "\<c-p>"
    else
        return "\<c-n>"
    endif
endfunction

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


[email protected], September 25, 2001 19:42
Anonymous,
Good, actually in my setting I had define two wrappers for tab and shift tab effectively achieving the same effect.
By the way I made a mistake in my orginal e-mail, it is .com not .fr
Benoit
Anonymous, December 9, 2001 23:52
This tip is great. But,  when type in 'tab' , it will have a additional space. How to fix it ? thanks.
[email protected], January 16, 2002 9:45
this tip truly does kick ass.
[email protected], January 24, 2002 23:38
This is freaking fabulous. Thankx man !! Its way too cool. i too, have been searching for this for quite some time now. Kudos !
[email protected], April 9, 2002 10:30
See also vimscript#182.
It is a complete system, and the completion remembers the completion mode!
anon, April 29, 2002 22:10
This tip is absolutely fantastic.  In response to the
problem of a space after hitting tab, there is most likely a
space after the line

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

In your vimscript, remove this and you should be fine
anon, May 22, 2002 10:17
Darned space!
Vim user, July 9, 2002 6:52
shift-tab always gives me a 'I', whether I'm in insert mode or not.
Something wrong with my termcap?
AK, August 23, 2002 12:50
This tip is truly kickass..!
[email protected], September 6, 2002 7:50
Anyone knows how to use a list of keywords for the completion??? I've searched the help but couldn't find anything. This is the great tip ever!!!
[email protected], September 13, 2002 6:48
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
[email protected], September 13, 2002 7:06
actually Mikolaj Machowski suggested the function :
fun! Iskcompletion()
let oldisk=&isk;
set isk+=(,)
normal <C-P>
set &isk;=oldisk
endfun

I just merged it with the Tab completion function - Thank You Mikolaj
Anonymous, October 11, 2002 10:38
I am trying to map <F6> to toggle whether this function is enabled or not.  In other words, I want it to toggle the mapping between the default and this one.  More specifically, I actually have two functions--this one and <S-Tab> that completes the whole line, and I would like the <F6> to enable/disable both mappings.  Suggestions?  Thanks :).
[email protected], October 31, 2002 11:22
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.

cat /usr/share/vim/vim61/syntax/sh.vim | grep keyword | grep -v nextgroup | awk '{ $1=""; $2=""; $3=""; print}' | perl -pe 's/\s+/\n/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

It would be nice if there was a built in way to add words to the dictionary from the syntax files.  Is there?
Dean Hougen <[email protected]>, November 26, 2002 17:31
Very nice!
Daniel Shields <[email protected]>, January 15, 2003 7:48
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>

[email protected], February 2, 2003 1:10
Does anyone know how to map Ctrl+Spacebar to do the same thing as Ctrl+N while in insert mode? I've tried these with no luck:

_ represents space

:map! ^V__ ^N <-- this maps JUST the spacebar to ^N, which is seriously annoying
:map! ^V<Space> ^N <-- this does nothing
:map! <C-SPACE> ^N
:map! <C-Space> ^N

I'm using vim 6.1 on Cygwin. Please e-mail me if you know how to do this.
[email protected], February 12, 2003 11:25
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
Anonymous, May 18, 2003 16:02
what about this to dump all the syntax files into properly formatted dict files

for i in /usr/share/vim/syntax/*;do cat $i | grep keyword | grep -v nextgroup | awk '{ $1=""; $2=""; $3=""; print}' | perl -pe 's/\s+/\n/g' | grep -v contained | grep -v '^$' | sort | uniq>~/.vim/dict/`basename $i .vim`.dict;done
[email protected], June 17, 2003 23:23
One small suggestion for daniel elstner's script: replace '[a-z]' with '[^<space><tab>]'.
[email protected] (Rot13ed), August 1, 2003 5:08
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:213/03@04:21] This is inspired by how multi-edit does things.
    " Wonderfull VIMTIP#102 clued me in on how to properly wrap a key like
    " this. (http://vim.sourceforge.net/tips/tip.php?tip_id=102)

    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.

Modify to your hearts content of course.

Happy VIMing!
[email protected], September 18, 2003 6:41
Hi

Cool function(s)

Would it be possible to extend InsertTabWrapper to make a tab pressed at col('.') >= (indent '.') reindent the line as with a == ? I could not find a simple way to do this but I am very much a novice with vim.

Anders Bakken
[email protected], October 8, 2003 9:48
Good Stuff!  

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

EZB
kalita_at_iiprg.cz, October 27, 2003 7:39
This script doesn't work with multibyte (utf-8 chars)
kalita_at_iiprg.cz, October 27, 2003 8:04
Try replace condition with

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

Enjoy
[email protected], January 25, 2004 13:12
is there a way to make tab complete like bash does, ie. complete only the characters that are unique and then let me type another one and press tab again to comlete the rest.
okki, March 27, 2004 4:31
EZB and all others.

I am happy :)
ronATronwaredotorg, April 15, 2004 9:29
Excellent, excellent tips!  Thank you all!
Anonymous, April 24, 2004 23:12
Wow!

This restores my faith in humanity

Thank you.
kyle, May 11, 2004 11:05
>" 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
[email protected], May 11, 2004 11:06
>" 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

How do I get it to do this whenever a file is loaded?
[email protected], July 28, 2004 10:47
Simpelmente espectacular, te pasaste compadre!!
If you have questions or remarks about this site, visit the vimonline development pages. Please use this site responsibly.
Questions about Vim should go to [email protected] after searching the archive. Help Bram help Uganda.
Sponsored by Web Concept Group Inc. SourceForge Logo