scriptencoding utf-8 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " AutoClose.vim - Automatically close pair of characters: ( with ), [ with ], { with }, etc. " Version: 2.0 " Author: Thiago Alves " Maintainer: Thiago Alves " URL: http://thiagoalves.com.br " Licence: This script is released under the Vim License. " Last modified: 02/02/2011 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " check if script is already loaded if !exists("g:debug_AutoClose") && exists("g:loaded_AutoClose") finish "stop loading the script" endif let g:loaded_AutoClose = 1 let s:global_cpo = &cpo " store compatible-mode in local variable set cpo&vim " go into nocompatible-mode if !exists('g:AutoClosePreserveDotReg') let g:AutoClosePreserveDotReg = 1 endif if g:AutoClosePreserveDotReg " Because dot register preservation code remaps escape we have to remap " some terminal specific escape sequences first if &term =~ 'xterm' || &term =~ 'rxvt' || &term =~ 'screen' || &term =~ 'linux' || &term =~ 'gnome' imap OA imap OB imap OC imap OD imap OH imap OF imap [5~ imap [6~ endif endif """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Functions """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" function! s:GetCharAhead(len) if col('$') == col('.') return "\0" endif return strpart(getline('.'), col('.')-2 + a:len, 1) endfunction function! s:GetCharBehind(len) if col('.') == 1 return "\0" endif return strpart(getline('.'), col('.') - (1 + a:len), 1) endfunction function! s:GetNextChar() return s:GetCharAhead(1) endfunction function! s:GetPrevChar() return s:GetCharBehind(1) endfunction " used to implement automatic deletion of closing character when opening " counterpart is deleted and by space expansion function! s:IsEmptyPair() let l:prev = s:GetPrevChar() let l:next = s:GetNextChar() return (l:next != "\0") && (get(b:AutoClosePairs, l:prev, "\0") == l:next) endfunction function! s:GetCurrentSyntaxRegion() return synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'name') endfunction function! s:GetCurrentSyntaxRegionIf(char) let l:origin_line = getline('.') let l:changed_line = strpart(l:origin_line, 0, col('.')-1) . a:char . strpart(l:origin_line, col('.')-1) call setline('.', l:changed_line) let l:region = synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'name') call setline('.', l:origin_line) return l:region endfunction function! s:IsForbidden(char) let l:result = index(b:AutoCloseProtectedRegions, s:GetCurrentSyntaxRegion()) >= 0 if l:result return l:result endif let l:region = s:GetCurrentSyntaxRegionIf(a:char) let l:result = index(b:AutoCloseProtectedRegions, l:region) >= 0 return l:result || l:region == 'Comment' endfunction function! s:AllowQuote(char, isBS) let l:result = 1 if b:AutoCloseSmartQuote let l:initPos = 1 + (a:isBS ? 1 : 0) let l:charBehind = s:GetCharBehind(l:initPos) let l:prev = l:charBehind let l:backSlashCount = 0 while l:charBehind == '\' let l:backSlashCount = l:backSlashCount + 1 let l:charBehind = s:GetCharBehind(l:initPos + l:backSlashCount) endwhile if l:backSlashCount % 2 let l:result = 0 else if a:char == "'" && l:prev =~ '[a-zA-Z0-9]' let l:result = 0 endif endif endif return l:result endfunction function! s:CountQuotes(char) let l:currPos = col('.')-1 let l:line = strpart(getline('.'), 0, l:currPos) let l:result = 0 if l:currPos >= 0 for [q,closer] in items(b:AutoClosePairs) " only consider twin pairs if q != closer | continue | endif if b:AutoCloseSmartQuote != 0 let l:regex = q . '[ˆ\\' . q . ']*(\\.[ˆ\\' . q . ']*)*' . q else let l:regex = q . '[ˆ' . q . ']*' . q endif let l:closedQuoteIdx = match(l:line, l:regex) while l:closedQuoteIdx >= 0 let l:matchedStr = matchstr(l:line, l:regex, l:closedQuoteIdx) let l:line = strpart(l:line, 0, l:closedQuoteIdx) . strpart(l:line, l:closedQuoteIdx + strlen(l:matchedStr)) let l:closedQuoteIdx = match(l:line, l:regex) endwhile endfor for c in split(l:line, '\zs') if c == a:char let l:result = l:result + 1 endif endfor endif return l:result endfunction " The auto-close buffer is used in a fix of the redo functionality. " As we insert characters after cursor, we remember them and at the moment " that vim would normally collect the last entered string into dot register " (:help ".) - i.e. when esc or a motion key is typed in insert mode - we " erase the inserted symbols and pretend that we have just now typed them. " This way vim picks them up into dot register as well and user can repeat the " typed bit with . command. function! s:PushBuffer(char) if !exists("b:AutoCloseBuffer") let b:AutoCloseBuffer = [] endif call insert(b:AutoCloseBuffer, a:char) endfunction function! s:PopBuffer() if exists("b:AutoCloseBuffer") && len(b:AutoCloseBuffer) > 0 call remove(b:AutoCloseBuffer, 0) endif endfunction function! s:EmptyBuffer() if exists("b:AutoCloseBuffer") let b:AutoCloseBuffer = [] endif endfunction function! s:FlushBuffer() let l:result = '' if exists("b:AutoCloseBuffer") let l:len = len(b:AutoCloseBuffer) if l:len > 0 let l:result = join(b:AutoCloseBuffer, '') . repeat("\", l:len) let b:AutoCloseBuffer = [] call s:EraseNCharsAtCursor(l:len) endif endif return l:result endfunction function! s:InsertStringAtCursor(str) let l:line = getline('.') let l:column = col('.')-2 if l:column < 0 call setline('.', a:str . l:line) else call setline('.', l:line[:l:column] . a:str . l:line[l:column+1:]) endif endfunction function! s:EraseNCharsAtCursor(len) let l:line = getline('.') let l:column = col('.')-2 if l:column < 0 call setline('.', l:line[a:len + 1:]) else call setline('.', l:line[:l:column] . l:line[l:column + a:len + 1:]) endif endfunction " returns the opener, after having inserted its closer if necessary function! s:InsertPair(opener) if ! b:AutoCloseOn || ! has_key(b:AutoClosePairs, a:opener) || s:IsForbidden(a:opener) return a:opener endif let l:save_ve = &ve set ve=all let l:next = s:GetNextChar() " only add closing pair before space or any of the closepair chars let close_before = '\s\|\V\[,.;' . escape(join(keys(b:AutoClosePairs) + values(b:AutoClosePairs), ''), ']').']' if (l:next == "\0" || l:next =~ close_before) && s:AllowQuote(a:opener, 0) call s:InsertStringAtCursor(b:AutoClosePairs[a:opener]) call s:PushBuffer(b:AutoClosePairs[a:opener]) endif exec "set ve=" . l:save_ve return a:opener endfunction " returns the closer, after having eaten identical one if necessary function! s:ClosePair(closer) let l:save_ve = &ve set ve=all if b:AutoCloseOn && s:GetNextChar() == a:closer call s:EraseNCharsAtCursor(1) call s:PopBuffer() endif exec "set ve=" . l:save_ve return a:closer endfunction " in case closer is identical with its opener - heuristically decide which one " is being typed and act accordingly function! s:OpenOrCloseTwinPair(char) if s:CountQuotes(a:char) % 2 == 0 " act as opening char return s:InsertPair(a:char) else " act as closing char return s:ClosePair(a:char) endif endfunction " maintain auto-close buffer when delete key is pressed function! s:Delete() let l:save_ve = &ve set ve=all if exists("b:AutoCloseBuffer") && len(b:AutoCloseBuffer) > 0 && b:AutoCloseBuffer[0] == s:GetNextChar() call s:PopBuffer() endif exec "set ve=" . l:save_ve return "\" endfunction " when backspace is pressed: " - erase an empty pair if backspacing from inside one " - maintain auto-close buffer function! s:Backspace() let l:save_ve = &ve let l:prev = s:GetPrevChar() let l:next = s:GetNextChar() set ve=all if b:AutoCloseOn && s:IsEmptyPair() && (l:prev != l:next || s:AllowQuote(l:prev, 1)) call s:EraseNCharsAtCursor(1) call s:PopBuffer() endif exec "set ve=" . l:save_ve return "\" endfunction function! s:Space() if b:AutoCloseOn && s:IsEmptyPair() call s:PushBuffer("\") return "\\\" else return "\" endif endfunction function! s:Enter() if has_key(b:AutoClosePumvisible, 'ENTER') && pumvisible() let b:snippet_chosen = 1 return b:AutoClosePumvisible['ENTER'] elseif b:AutoCloseOn && s:IsEmptyPair() && stridx( b:AutoCloseExpandEnterOn, s:GetPrevChar() ) >= 0 return "\\O" endif return "\" endfunction function! s:ToggleAutoClose() let b:AutoCloseOn = !b:AutoCloseOn if b:AutoCloseOn echo "AutoClose ON" else echo "AutoClose OFF" endif endfunction " Parse a whitespace separated line of pairs " single characters are assumed to be twin pairs (closer identical to " opener) function! AutoClose#ParsePairs(string) if type(a:string) == type({}) return a:string elseif type(a:string) != type("") echoerr "AutoClose#ParsePairs(): Argument not a dictionary or a string" return {} endif let l:dict = {} for pair in split(a:string) " strlen is length in bytes, we want in (wide) characters let l:pairLen = strlen(substitute(pair,'.','x','g')) if l:pairLen == 1 " assume a twin pair let l:dict[pair] = pair elseif l:pairLen == 2 let l:dict[pair[0]] = pair[1] else echoerr "AutoClose: Bad pair string - a pair longer then two character" echoerr " `- String: " . a:sring echoerr " `- Pair: " . pair . " Pair len: " . l:pairLen endif endfor return l:dict endfunction " this function is made visible for the sake of users function! AutoClose#DefaultPairs() return AutoClose#ParsePairs(g:AutoClosePairs) endfunction function! s:ModifyPairsList(list, pairsToAdd, openersToRemove) return filter( \ extend(a:list, AutoClose#ParsePairs(a:pairsToAdd), "force"), \ "stridx(a:openersToRemove,v:key)<0") endfunction function! AutoClose#DefaultPairsModified(pairsToAdd,openersToRemove) return s:ModifyPairsList(AutoClose#DefaultPairs(), a:pairsToAdd, a:openersToRemove) endfunction " Define variables (in the buffer namespace). function! s:DefineVariables() " All the following variables can be set per buffer or global. " The buffer namespace is used internally let defaults = { \ 'AutoClosePairs': AutoClose#DefaultPairs(), \ 'AutoCloseProtectedRegions': ["Comment", "String", "Character"], \ 'AutoCloseSmartQuote': 1, \ 'AutoCloseOn': 1, \ 'AutoCloseSelectionWrapPrefix': 'a', \ 'AutoClosePumvisible': {"ENTER": "\", "ESC": "\"}, \ 'AutoCloseExpandEnterOn': "", \ 'AutoCloseExpandSpace': 1, \ } " Let the user define if he/she wants the plugin to do special actions when the " popup menu is visible and a movement key is pressed. " Movement keys used in the menu get mapped to themselves " (Up/Down/PageUp/PageDown). for key in s:movementKeys if key == 'ENTER' || key == 'ESC' continue endif let defaults['AutoClosePumvisible'][key] = '' endfor for key in s:pumMovementKeys if key == 'ENTER' || key == 'ESC' continue endif let defaults['AutoClosePumvisible'][key] = '<'.key.'>' endfor if exists ('b:AutoClosePairs') && type('b:AutoClosePairs') == type("") let tmp = AutoClose#ParsePairs(b:AutoClosePairs) unlet b:AutoClosePairs let b:AutoClosePairs = tmp endif " Now handle/assign values for key in keys(defaults) if key == 'AutoClosePumvisible' let tempVisible = defaults['AutoClosePumvisible'] if exists('g:AutoClosePumvisible') && type(eval('g:AutoClosePumvisible')) == type(defaults['AutoClosePumvisible']) for childKey in keys(g:AutoClosePumvisible) let tempVisible[toupper(childKey)] = g:AutoClosePumvisible[childKey] endfor endif if exists('b:AutoClosePumvisible') && type(eval('b:AutoClosePumvisible')) == type(defaults['AutoClosePumvisible']) for childKey in keys(b:AutoClosePumvisible) let tempVisible[toupper(childKey)] = b:AutoClosePumvisible[childKey] endfor endif let b:AutoClosePumvisible = tempVisible else if exists('b:'.key) && type(eval('b:'.key)) == type(defaults[key]) continue elseif exists('g:'.key) && type(eval('g:'.key)) == type(defaults[key]) exec 'let b:' . key . ' = g:' . key else exec 'let b:' . key . ' = ' . string(defaults[key]) endif endif endfor endfunction function! s:CreatePairsMaps() " create appropriate maps to defined open/close characters for key in keys(b:AutoClosePairs) let opener = s:keyName(key) let closer = s:keyName(b:AutoClosePairs[key]) let quoted_opener = s:quoteAndEscape(opener) let quoted_closer = s:quoteAndEscape(closer) exec "xnoremap ". b:AutoCloseSelectionWrapPrefix \ . opener . " `>a" . closer . "`" exec "xnoremap ". b:AutoCloseSelectionWrapPrefix \ . closer . " `>a" . closer . "`" if key == b:AutoClosePairs[key] exec "inoremap " . opener \ . " =OpenOrCloseTwinPair(" . quoted_opener . ")" else exec "inoremap " . opener \ . " =InsertPair(" . quoted_opener . ")" exec "inoremap " . closer \ . " =ClosePair(" . quoted_closer . ")" endif endfor endfunction function! s:CreateExtraMaps() " Extra mapping inoremap =Backspace() inoremap =Delete() if b:AutoCloseExpandSpace inoremap =Space() endif if len(b:AutoCloseExpandEnterOn) > 0 inoremap =Enter() endif if g:AutoClosePreserveDotReg " Fix the re-do feature by flushing the char buffer on key movements (including Escape): for key in s:movementKeys let l:pvisiblemap = b:AutoClosePumvisible[key] let key = "<".key.">" let l:currentmap = maparg(key,"i") if (l:currentmap=="")|let l:currentmap=key|endif if len(l:pvisiblemap) exec "inoremap " . key . " pumvisible() ? '" . l:pvisiblemap . "' : '=FlushBuffer()" . l:currentmap . "'" else exec "inoremap " . key . " =FlushBuffer()" . l:currentmap endif endfor " Flush the char buffer on mouse click: inoremap =FlushBuffer() inoremap =FlushBuffer() endif endfunction function! s:CreateMaps() silent doautocmd FileType call s:DefineVariables() call s:CreatePairsMaps() call s:CreateExtraMaps() let b:loaded_AutoClose = 1 endfunction function! s:IsLoadedOnBuffer() return (exists("b:loaded_AutoClose") && b:loaded_AutoClose) endfunction " map some characters to their key names function! s:keyName(char) let s:keyNames = {'|': '', ' ': ''} return get(s:keyNames,a:char,a:char) endfunction " escape some characters for use in strings function! s:quoteAndEscape(char) let s:escapedChars = {'"': '\"'} return '"' . get(s:escapedChars,a:char,a:char) . '"' endfunction """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Configuration """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" let s:AutoClosePairs_FactoryDefaults = AutoClose#ParsePairs("() {} [] ` \" '") if !exists("g:AutoClosePairs_add") | let g:AutoClosePairs_add = "" | endif if !exists("g:AutoClosePairs_del") | let g:AutoClosePairs_del = "" | endif if !exists("g:AutoClosePairs") let g:AutoClosePairs = s:ModifyPairsList( \ s:AutoClosePairs_FactoryDefaults, \ g:AutoClosePairs_add, \ g:AutoClosePairs_del ) endif let s:movementKeys = split('ESC UP DOWN LEFT RIGHT HOME END PAGEUP PAGEDOWN') " list of keys that get mapped to themselves for pumvisible() let s:pumMovementKeys = split('UP DOWN PAGEUP PAGEDOWN') if has("gui_macvim") call extend(s:movementKeys, split("D-LEFT D-RIGHT D-UP D-DOWN M-LEFT M-RIGHT M-UP M-DOWN")) endif augroup (autoclose) autocmd! autocmd BufNewFile,BufRead,BufEnter * if !IsLoadedOnBuffer() | call CreateMaps() | endif autocmd InsertEnter * call EmptyBuffer() autocmd BufEnter * if mode() == 'i' | call EmptyBuffer() | endif augroup END " Define convenient commands command! AutoCloseOn :let b:AutoCloseOn = 1 command! AutoCloseOff :let b:AutoCloseOn = 0 command! AutoCloseToggle :call s:ToggleAutoClose() " vim:sw=4:sts=4: