" Description: Omni completion script for cpp files " Maintainer: Vissale NEANG " Last Change: 27 sept. 2007 if v:version < 700 echohl WarningMsg echomsg "omni#cpp#complete.vim: Please install vim 7.0 or higher for omni-completion" echohl None finish endif call omni#cpp#settings#Init() let s:OmniCpp_ShowScopeInAbbr = g:OmniCpp_ShowScopeInAbbr let s:OmniCpp_ShowPrototypeInAbbr = g:OmniCpp_ShowPrototypeInAbbr let s:OmniCpp_ShowAccess = g:OmniCpp_ShowAccess let s:szCurrentWorkingDir = getcwd() " Cache data let s:CACHE_TAG_POPUP_ITEMS = {} let s:CACHE_TAG_FILES = {} let s:CACHE_TAG_ENV = '' let s:CACHE_OVERLOADED_FUNCTIONS = {} " Has preview window? let s:hasPreviewWindow = match(&completeopt, 'preview')>=0 let s:hasPreviewWindowOld = s:hasPreviewWindow " Popup item list let s:popupItemResultList = [] " May complete indicator let s:bMayComplete = 0 " Init mappings function! omni#cpp#complete#Init() call omni#cpp#settings#Init() set omnifunc=omni#cpp#complete#Main inoremap omni#cpp#maycomplete#Complete() inoremap . omni#cpp#maycomplete#Dot() inoremap > omni#cpp#maycomplete#Arrow() inoremap : omni#cpp#maycomplete#Scope() endfunc " Find the start position of the completion function! s:FindStartPositionOfCompletion() " Locate the start of the item, including ".", "->" and "[...]". let line = getline('.') let start = col('.') - 1 let lastword = -1 while start > 0 if line[start - 1] =~ '\w' let start -= 1 elseif line[start - 1] =~ '\.' " Searching for dot '.' if lastword == -1 let lastword = start endif let start -= 1 elseif start > 1 && line[start - 2] == '-' && line[start - 1] == '>' " Searching for '->' if lastword == -1 let lastword = start endif let start -= 2 elseif start > 1 && line[start - 2] == ':' && line[start - 1] == ':' " Searching for '::' for namespaces and class if lastword == -1 let lastword = start endif let start -= 2 elseif line[start - 1] == ']' " Skip over [...]. let n = 0 let start -= 1 while start > 0 let start -= 1 if line[start] == '[' if n == 0 break endif let n -= 1 elseif line[start] == ']' " nested [] let n += 1 endif endwhile else break endif endwhile if lastword==-1 " For completion on the current scope let lastword = start endif return lastword endfunc " Returns if szKey1.szKey2 is in the cache " @return " - 0 = key not found " - 1 = szKey1.szKey2 found " - 2 = szKey1.[part of szKey2] found function! s:IsCached(cache, szKey1, szKey2) " Searching key in the result cache let szResultKey = a:szKey1 . a:szKey2 let result = [0, szResultKey] if a:szKey2 != '' let szKey = a:szKey2 while len(szKey)>0 if has_key(a:cache, a:szKey1 . szKey) let result[1] = a:szKey1 . szKey if szKey != a:szKey2 let result[0] = 2 else let result[0] = 1 endif break endif let szKey = szKey[:-2] endwhile else if has_key(a:cache, szResultKey) let result[0] = 1 endif endif return result endfunc " Extend a tag item to a popup item function! s:ExtendTagItemToPopupItem(tagItem, szTypeName) let tagItem = a:tagItem " Add the access let szItemMenu = '' let accessChar = {'public': '+','protected': '#','private': '-'} if g:OmniCpp_ShowAccess if has_key(tagItem, 'access') && has_key(accessChar, tagItem.access) let szItemMenu = szItemMenu.accessChar[tagItem.access] else let szItemMenu = szItemMenu." " endif endif " Formating optional menu string we extract the scope information let szName = substitute(tagItem.name, '.*::', '', 'g') let szItemWord = szName let szAbbr = szName if !g:OmniCpp_ShowScopeInAbbr let szScopeOfTag = omni#cpp#utils#ExtractScope(tagItem) let szItemMenu = szItemMenu.' '.szScopeOfTag[2:] let szItemMenu = substitute(szItemMenu, '\s\+$', '', 'g') else let szAbbr = tagItem.name endif if g:OmniCpp_ShowAccess let szItemMenu = substitute(szItemMenu, '^\s\+$', '', 'g') else let szItemMenu = substitute(szItemMenu, '\(^\s\+\)\|\(\s\+$\)', '', 'g') endif " Formating information for the preview window if index(['f', 'p'], tagItem.kind[0])>=0 let szItemWord .= '(' if g:OmniCpp_ShowPrototypeInAbbr && has_key(tagItem, 'signature') let szAbbr .= tagItem.signature else let szAbbr .= '(' endif endif let szItemInfo = '' if s:hasPreviewWindow let szItemInfo = omni#cpp#utils#GetPreviewWindowStringFromTagItem(tagItem) endif " If a function is a ctor we add a new key in the tagItem if index(['f', 'p'], tagItem.kind[0])>=0 if match(szName, '^\~') < 0 && a:szTypeName =~ '\C\<'.szName.'$' " It's a ctor let tagItem['ctor'] = 1 elseif has_key(tagItem, 'access') && tagItem.access == 'friend' " Friend function let tagItem['friendfunc'] = 1 endif endif " Extending the tag item to a popup item let tagItem['word'] = szItemWord let tagItem['abbr'] = szAbbr let tagItem['menu'] = szItemMenu let tagItem['info'] = szItemInfo let tagItem['dup'] = (s:hasPreviewWindow && index(['f', 'p', 'm'], tagItem.kind[0])>=0) return tagItem endfunc " Get tag popup item list function! s:TagPopupList(szTypeName, szBase) let result = [] " Searching key in the result cache let cacheResult = s:IsCached(s:CACHE_TAG_POPUP_ITEMS, a:szTypeName, a:szBase) " Building the tag query, we don't forget dtors when a:szBase=='' if a:szTypeName!='' " Scope search let szTagQuery = '^' . a:szTypeName . '::' . a:szBase . '\~\?\w\+$' else " Global search let szTagQuery = '^' . a:szBase . '\w\+$' endif " If the result is already in the cache we return it if cacheResult[0] let result = s:CACHE_TAG_POPUP_ITEMS[ cacheResult[1] ] if cacheResult[0] == 2 let result = filter(copy(result), 'v:val.name =~ szTagQuery' ) endif return result endif try " Getting tags let result = omni#common#utils#TagList(szTagQuery) " We extend tag items to popup items call map(result, 's:ExtendTagItemToPopupItem(v:val, a:szTypeName)') " We store the result in a cache if cacheResult[1] != '' let s:CACHE_TAG_POPUP_ITEMS[ cacheResult[1] ] = result endif catch /^TagList:UserInterrupt$/ endtry return result endfunc " Find complete matches for a completion on the global scope function! s:SearchGlobalMembers(szBase) if a:szBase != '' let tagPopupList = s:TagPopupList('', a:szBase) let tagPopupList = filter(copy(tagPopupList), g:omni#cpp#utils#szFilterGlobalScope) call extend(s:popupItemResultList, tagPopupList) endif endfunc " Search class, struct, union members " @param resolvedTagItem: a resolved tag item " @param szBase: string base " @return list of tag items extended to popup items function! s:SearchMembers(resolvedTagItem, szBase) let result = [] if a:resolvedTagItem == {} return result endif " Get type info without the starting '::' let szTagName = omni#cpp#utils#ExtractTypeInfoFromTag(a:resolvedTagItem)[2:] " Unnamed type case. A tag item representing an unnamed type is a variable " ('v') a member ('m') or a typedef ('t') if index(['v', 't', 'm'], a:resolvedTagItem.kind[0])>=0 && has_key(a:resolvedTagItem, 'typeref') " We remove the 'struct:' or 'class:' etc... let szTagName = substitute(a:resolvedTagItem.typeref, '^\w\+:', '', 'g') endif return copy(s:TagPopupList(szTagName, a:szBase)) endfunc " Return if the tag env has changed function! s:HasTagEnvChanged() if s:CACHE_TAG_ENV == &tags return 0 else let s:CACHE_TAG_ENV = &tags return 1 endif endfunc " Return if a tag file has changed in tagfiles() function! s:HasATagFileOrTagEnvChanged() if s:HasTagEnvChanged() let s:CACHE_TAG_FILES = {} return 1 endif let result = 0 for tagFile in tagfiles() if tagFile == "" continue endif if has_key(s:CACHE_TAG_FILES, tagFile) let currentFiletime = getftime(tagFile) if currentFiletime > s:CACHE_TAG_FILES[tagFile] " The file has changed, updating the cache let s:CACHE_TAG_FILES[tagFile] = currentFiletime let result = 1 endif else " We store the time of the file let s:CACHE_TAG_FILES[tagFile] = getftime(tagFile) let result = 1 endif endfor return result endfunc " Initialization call s:HasATagFileOrTagEnvChanged() " Filter same function signatures of base classes function! s:FilterOverloadedFunctions(tagPopupList) let result = [] for tagPopupItem in a:tagPopupList if has_key(tagPopupItem, 'kind') && index(['f', 'p'], tagPopupItem.kind[0])>=0 && has_key(tagPopupItem, 'signature') if !has_key(s:CACHE_OVERLOADED_FUNCTIONS, tagPopupItem.word . tagPopupItem.signature) let s:CACHE_OVERLOADED_FUNCTIONS[tagPopupItem.word . tagPopupItem.signature] = 1 call extend(result, [tagPopupItem]) endif else call extend(result, [tagPopupItem]) endif endfor return result endfunc " Access filter function! s:GetAccessFilter(szFilter, szAccessFilter) let szFilter = a:szFilter if g:OmniCpp_DisplayMode == 0 if a:szAccessFilter == 'public' " We only get public members let szFilter .= "&& v:val.access == 'public'" elseif a:szAccessFilter == 'protected' " We get public and protected members let szFilter .= "&& v:val.access != 'private'" endif endif return szFilter endfunc " Filter class members in the popup menu after a completion with -> or . function! s:FilterClassMembers(tagPopupList, szAccessFilter) let szFilter = "(!has_key(v:val, 'friendfunc') && !has_key(v:val, 'ctor') && has_key(v:val, 'kind') && index(['m', 'p', 'f'], v:val.kind[0])>=0 && has_key(v:val, 'access'))" call filter(a:tagPopupList, s:GetAccessFilter(szFilter, a:szAccessFilter)) call extend(s:popupItemResultList, s:FilterOverloadedFunctions(a:tagPopupList)) endfunc " Filter class scope members in the popup menu after a completion with :: " We only display attribute and functions members that " have an access information. We also display nested " class, struct, union, and enums, typedefs function! s:FilterClassScopeMembers(tagPopupList, szAccessFilter) let szFilter = "!has_key(v:val, 'friendfunc') && has_key(v:val, 'kind') && (index(['m', 'p', 'f'], v:val.kind[0])>=0 && has_key(v:val, 'access'))" let szFilter = s:GetAccessFilter(szFilter, a:szAccessFilter) let szFilter .= "|| index(['c','e','g','s','t','u'], v:val.kind[0])>=0" call filter(a:tagPopupList, szFilter) call extend(s:popupItemResultList, s:FilterOverloadedFunctions(a:tagPopupList)) endfunc " Filter static class members in the popup menu function! s:FilterStaticClassMembers(tagPopupList, szAccessFilter) let szFilter = "!has_key(v:val, 'friendfunc') && has_key(v:val, 'kind') && (index(['m', 'p', 'f'], v:val.kind[0])>=0 && has_key(v:val, 'access') && match(v:val.cmd, '\\Cstatic')!=-1)" let szFilter = s:GetAccessFilter(szFilter, a:szAccessFilter) let szFilter = szFilter . "|| index(['c','e','g','n','s','t','u','v'], v:val.kind[0])>=0" call filter(a:tagPopupList, szFilter) call extend(s:popupItemResultList, s:FilterOverloadedFunctions(a:tagPopupList)) endfunc " Filter scope members in the popup menu function! s:FilterNamespaceScopeMembers(tagPopupList) call extend(s:popupItemResultList, a:tagPopupList) endfunc " Init data at the start of completion function! s:InitComplete() " Reset the popup item list let s:popupItemResultList = [] let s:CACHE_OVERLOADED_FUNCTIONS = {} " Reset includes cache when the current working directory has changed let szCurrentWorkingDir = getcwd() if s:szCurrentWorkingDir != szCurrentWorkingDir let s:szCurrentWorkingDir = szCurrentWorkingDir let g:omni#cpp#includes#CACHE_INCLUDES = {} let g:omni#cpp#includes#CACHE_FILE_TIME = {} endif " Has preview window ? let s:hasPreviewWindow = match(&completeopt, 'preview')>=0 let bResetCache = 0 " Reset tag env or tag files dependent caches if s:HasATagFileOrTagEnvChanged() let bResetCache = 1 endif if (s:OmniCpp_ShowScopeInAbbr != g:OmniCpp_ShowScopeInAbbr) \|| (s:OmniCpp_ShowPrototypeInAbbr != g:OmniCpp_ShowPrototypeInAbbr) \|| (s:OmniCpp_ShowAccess != g:OmniCpp_ShowAccess) let s:OmniCpp_ShowScopeInAbbr = g:OmniCpp_ShowScopeInAbbr let s:OmniCpp_ShowPrototypeInAbbr = g:OmniCpp_ShowPrototypeInAbbr let s:OmniCpp_ShowAccess = g:OmniCpp_ShowAccess let bResetCache = 1 endif if s:hasPreviewWindow != s:hasPreviewWindowOld let s:hasPreviewWindowOld = s:hasPreviewWindow let bResetCache = 1 endif if bResetCache let g:omni#cpp#namespaces#CacheResolve = {} let s:CACHE_TAG_POPUP_ITEMS = {} let g:omni#cpp#utils#CACHE_TAG_INHERITS = {} call garbagecollect() endif " Check for updates for szIncludeName in keys(g:omni#cpp#includes#CACHE_INCLUDES) let fTime = getftime(szIncludeName) let bNeedUpdate = 0 if has_key(g:omni#cpp#includes#CACHE_FILE_TIME, szIncludeName) if fTime != g:omni#cpp#includes#CACHE_FILE_TIME[szIncludeName] let bNeedUpdate = 1 endif else let g:omni#cpp#includes#CACHE_FILE_TIME[szIncludeName] = fTime let bNeedUpdate = 1 endif if bNeedUpdate " We have to update include list and namespace map of this file call omni#cpp#includes#GetList(szIncludeName, 1) call omni#cpp#namespaces#GetMapFromBuffer(szIncludeName, 1) endif endfor let s:bDoNotComplete = 0 endfunc " This function is used for the 'omnifunc' option. function! omni#cpp#complete#Main(findstart, base) if a:findstart "call omni#common#debug#Start() call s:InitComplete() " Note: if s:bMayComplete==1 g:omni#cpp#items#data is build by MayComplete functions if !s:bMayComplete " If the cursor is in a comment we go out if omni#cpp#utils#IsCursorInCommentOrString() " Returning -1 is not enough we have to set a variable to let " the second call of omni#cpp#complete knows that the " cursor was in a comment " Why is there a second call when the first call returns -1 ? let s:bDoNotComplete = 1 return -1 endif " We get items here (whend a:findstart==1) because GetItemsToComplete() " depends on the cursor position. " When a:findstart==0 the cursor position is modified let g:omni#cpp#items#data = omni#cpp#items#Get(omni#cpp#utils#TokenizeCurrentInstruction()) endif " Get contexts stack let s:contextStack = omni#cpp#namespaces#GetContexts() " Reinit of may complete indicator let s:bMayComplete = 0 return s:FindStartPositionOfCompletion() endif " If the cursor is in a comment we return an empty result if s:bDoNotComplete let s:bDoNotComplete = 0 return [] endif if len(g:omni#cpp#items#data)==0 " A) CURRENT_SCOPE_COMPLETION_MODE " 1) Displaying data of each context let szAccessFilter = 'all' for szCurrentContext in s:contextStack if szCurrentContext == '::' continue endif let resolvedTagItem = omni#cpp#utils#GetResolvedTagItem(s:contextStack, omni#cpp#utils#CreateTypeInfo(szCurrentContext)) if resolvedTagItem != {} " We don't search base classes because bases classes are " already in the context stack let tagPopupList = s:SearchMembers(resolvedTagItem, a:base) if index(['c','s'], resolvedTagItem.kind[0])>=0 " It's a class or struct call s:FilterClassScopeMembers(tagPopupList, szAccessFilter) let szAccessFilter = 'protected' else " It's a namespace or union, we display all members call s:FilterNamespaceScopeMembers(tagPopupList) endif endif endfor " 2) Displaying global scope members if g:OmniCpp_GlobalScopeSearch call s:SearchGlobalMembers(a:base) endif else let typeInfo = omni#cpp#items#ResolveItemsTypeInfo(s:contextStack, g:omni#cpp#items#data) if typeInfo != {} if g:omni#cpp#items#data[-1].kind == 'itemScope' " B) SCOPE_COMPLETION_MODE if omni#cpp#utils#GetTypeInfoString(typeInfo)=='' call s:SearchGlobalMembers(a:base) else for resolvedTagItem in omni#cpp#utils#GetResolvedTags(s:contextStack, typeInfo) let tagPopupList = s:SearchMembers(resolvedTagItem, a:base) if index(['c','s'], resolvedTagItem.kind[0])>=0 let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(resolvedTagItem) if g:OmniCpp_DisplayMode==0 " We want to complete a class or struct " If this class is a base class so we display all class members if index(s:contextStack, szTypeInfo)<0 let szAccessFilter = 'public' call s:FilterStaticClassMembers(tagPopupList, szAccessFilter) else let szAccessFilter = (s:contextStack[0] == szTypeInfo)? 'all' : 'protected' call s:FilterClassScopeMembers(tagPopupList, szAccessFilter) endif else if index(s:contextStack, szTypeInfo)<0 let szAccessFilter = 'public' else let szAccessFilter = (s:contextStack[0] == szTypeInfo)? 'all' : 'protected' endif call s:FilterClassScopeMembers(tagPopupList, szAccessFilter) endif else " We want to complete a namespace call s:FilterNamespaceScopeMembers(tagPopupList) endif endfor endif else " C) CLASS_MEMBERS_COMPLETION_MODE for resolvedTagItem in omni#cpp#utils#GetResolvedTags(s:contextStack, typeInfo) let szTypeInfo = omni#cpp#utils#ExtractTypeInfoFromTag(resolvedTagItem) if index(s:contextStack, szTypeInfo)<0 let szAccessFilter = 'public' else let szAccessFilter = (s:contextStack[0] == szTypeInfo)? 'all' : 'protected' endif call s:FilterClassMembers(s:SearchMembers(resolvedTagItem, a:base), szAccessFilter) endfor endif endif endif "call omni#common#debug#End() return s:popupItemResultList endfunc